├── .github ├── dependabot.yml └── workflows │ ├── package_and_test.yml │ ├── release.yml │ └── tests.yml ├── .gitignore ├── CHANGES.md ├── LICENSE ├── README.md ├── hyperspy_gui_ipywidgets ├── __init__.py ├── axes.py ├── conftest.py ├── custom_widgets.py ├── hyperspy_extension.yaml ├── microscope_parameters.py ├── model.py ├── preferences.py ├── roi.py ├── tests │ ├── __init__.py │ ├── test_axes.py │ ├── test_import.py │ ├── test_microscope_parameters.py │ ├── test_model.py │ ├── test_preferences.py │ ├── test_roi.py │ ├── test_tools.py │ └── utils.py ├── tools.py └── utils.py ├── images └── preferences_gui.png ├── prepare_release.py ├── pyproject.toml └── releasing_guide.md /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/package_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Package & Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | package_and_test: 7 | name: Package and Test 8 | # Use the "reusable workflow" from the hyperspy organisation 9 | uses: hyperspy/.github/.github/workflows/package_and_test.yml@main 10 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | # Reusable workflow are not supported with trusted publisher 3 | # https://github.com/pypa/gh-action-pypi-publish/issues/166 4 | # copy and paste most of 5 | # https://github.com/hyperspy/.github/blob/main/.github/workflows/release_pure_python.yml 6 | 7 | # This workflow builds the wheels "on tag". 8 | # If run from the hyperspy/hyperspy repository, the wheels will be uploaded to pypi ; 9 | # otherwise, the wheels will be available as a github artifact. 10 | on: 11 | push: 12 | # Sequence of patterns matched against refs/tags 13 | tags: 14 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 15 | 16 | jobs: 17 | package_and_test: 18 | name: Package and Test 19 | # Use the "reusable workflow" from the hyperspy organisation 20 | uses: hyperspy/.github/.github/workflows/package_and_test.yml@main 21 | 22 | upload_to_pypi: 23 | needs: [package_and_test] 24 | runs-on: ubuntu-latest 25 | name: Upload to pypi 26 | permissions: 27 | # IMPORTANT: this permission is mandatory for trusted publishing 28 | id-token: write 29 | steps: 30 | - name: Download dist 31 | uses: actions/download-artifact@v4 32 | 33 | - name: Display downloaded files 34 | run: | 35 | ls -shR 36 | working-directory: dist 37 | 38 | - uses: pypa/gh-action-pypi-publish@release/v1 39 | if: ${{ startsWith(github.ref, 'refs/tags/') && github.repository_owner == 'hyperspy' }} 40 | # See https://docs.pypi.org/trusted-publishers/using-a-publisher/ 41 | 42 | create_github_release: 43 | # If zenodo is setup to create a DOI automatically on a GitHub release, 44 | # this step will trigger the mining of the DOI 45 | needs: upload_to_pypi 46 | permissions: 47 | contents: write 48 | name: Create GitHub Release 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Checkout code 52 | uses: actions/checkout@v4 53 | - name: Create Release 54 | if: ${{ startsWith(github.ref, 'refs/tags/') && github.repository_owner == 'hyperspy' }} 55 | uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 56 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | run_test_site: 7 | name: py${{ matrix.PYTHON_VERSION }}${{ matrix.LABEL }} 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 30 10 | env: 11 | MPLBACKEND: agg 12 | PIP_SELECTOR: '[tests]' 13 | PYTEST_ARGS: --pyargs hyperspy_gui_ipywidgets 14 | PYTEST_ARGS_COVERAGE: --cov=. --cov-report=xml 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | PYTHON_VERSION: ['3.10', '3.11', '3.13'] 19 | LABEL: [-release, -dev] 20 | PIP_ARGS: [""] 21 | include: 22 | - PYTHON_VERSION: '3.12' 23 | PIP_ARGS: --pre 24 | LABEL: -dev-pre_release 25 | - PYTHON_VERSION: '3.12' 26 | LABEL: -release-minimum 27 | - PYTHON_VERSION: '3.12' 28 | LABEL: -dev-minimum 29 | - PYTHON_VERSION: '3.9' 30 | LABEL: -release 31 | - PYTHON_VERSION: '3.9' 32 | LABEL: -oldest 33 | # Matching pyproject.toml 34 | DEPENDENCIES: hyperspy==2.3.0 ipywidgets==8.0 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | with: 39 | fetch-depth: 0 40 | 41 | - name: get repository name 42 | shell: bash 43 | run: echo "REPOSITORY_NAME=${GITHUB_REPOSITORY#*/}" >> $GITHUB_ENV 44 | 45 | - name: Fetch tags upstream 46 | if: ${{ github.repository_owner != 'hyperspy' }} 47 | # Needs to fetch the tags from upstream to get the 48 | # correct version with setuptools_scm 49 | run: | 50 | git remote add upstream https://github.com/hyperspy/${{ env.REPOSITORY_NAME }}.git 51 | git fetch upstream --tags 52 | 53 | - uses: actions/setup-python@v5 54 | name: Install Python 55 | with: 56 | python-version: ${{ matrix.PYTHON_VERSION }} 57 | 58 | - name: Display version 59 | run: | 60 | python --version 61 | pip --version 62 | 63 | - name: Install HyperSpy (dev) 64 | if: contains( matrix.LABEL, 'dev') 65 | run: | 66 | pip install git+https://github.com/hyperspy/hyperspy.git@RELEASE_next_minor 67 | 68 | - name: Install exSpy (dev) 69 | if: contains( matrix.LABEL, 'dev') 70 | run: | 71 | pip install git+https://github.com/hyperspy/exspy.git 72 | 73 | - name: Install 74 | run: | 75 | pip install ${{ matrix.PIP_ARGS }} .'${{ env.PIP_SELECTOR }}' 76 | 77 | - name: Pip list 78 | run: | 79 | pip list 80 | 81 | - name: Install oldest supported version 82 | if: contains( matrix.LABEL, 'oldest') 83 | run: | 84 | pip install ${{ matrix.DEPENDENCIES }} 85 | 86 | - name: Pip list 87 | run: | 88 | pip list 89 | 90 | - name: Run test suite 91 | run: | 92 | pytest ${{ env.PYTEST_ARGS }} ${{ env.PYTEST_ARGS_COVERAGE }} 93 | 94 | - name: Upload coverage to Codecov 95 | if: ${{ always() }} && ${{ env.PYTEST_ARGS_COVERAGE }} 96 | uses: codecov/codecov-action@v5 97 | env: 98 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # Jetbrains settings 104 | .idea/ -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | .. 2 | Add a single entry in the corresponding section below. 3 | See https://keepachangelog.com for details 4 | 5 | ## v2.2.0 (UNRELEASED) 6 | 7 | ## v2.1.0 (2025-03-02) 8 | 9 | * Add widgets for `Signal1D.remove_baselines` ([#70](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/70)). 10 | * Add support for python 3.13; drop support for python 3.8 ([#77](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/77)). 11 | 12 | 13 | ## v2.0.3 (2024-10-11) 14 | * Fix test suite after `eXSpy` API changes in [hyperspy/exspy#59](https://github.com/hyperspy/exspy/pull/59) ([#69](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/69)) 15 | 16 | ## v2.0.2 (2024-06-09) 17 | * Fix incorrect library version ([#64](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/64)) 18 | 19 | ## v2.0.1 (2024-05-08) 20 | * Add releasing guide and release script ([#59](https://github.com/hyperspy/hyperspy_gui_traitsui/pull/59)). 21 | * Add `ipympl` dependency as convenience ([#58])(https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/58). 22 | * Fix regression with editable installation ([#55](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/55)). 23 | 24 | 25 | ## v2.0 (2023-11-15) 26 | * Consolidate package metadata in `pyproject.toml` ([#49](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/49)). 27 | * Support HyperSpy 2.0 and set HyperSpy requirement to >=2.0 ([#48](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/48)). 28 | 29 | ## v1.5.0 (2022-04-26) 30 | 31 | * Improve rendering changelog on github and fix hyperlinks in `README.md` ([#41](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/41)). 32 | * Speed up import time by importing submodules lazily and drop support for python 3.6 ([#40](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/40)). 33 | * Add python 3.10 to github CI and update github actions versions ([#42](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/42)). 34 | * Add GUI for the calibration method of signal2D ([#2](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/2)) 35 | 36 | ## v1.4.0 (2021-04-13) 37 | 38 | The is a minor release 39 | 40 | * Add iterpath to fit component GUI ([#36](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/36)). 41 | * Add support for ipywidgets development version 8.0 ([#35](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/35)). 42 | * Make axes gui compatible with non-unform axis ([#34](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/34)). 43 | * Use GitHub Actions to run the test suite and make release ([#33](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/33)). 44 | * Fix ipywidgets requirement (>=7.0) ([#32](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/32)). 45 | * Improve README.md ([#31](https://github.com/hyperspy/hyperspy_gui_ipywidgets/pull/31)). 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyperspy_gui_ipywidgets 2 | [![Tests](https://github.com/hyperspy/hyperspy_gui_ipywidgets/workflows/Tests/badge.svg)](https://github.com/hyperspy/hyperspy_gui_ipywidgets/actions) 3 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/hyperspy_gui_ipywidgets.svg)](https://pypi.org/project/hyperspy-gui-ipywidgets) 4 | [![PyPI](https://img.shields.io/pypi/v/hyperspy_gui_ipywidgets.svg)](https://pypi.org/project/hyperspy-gui-ipywidgets) 5 | [![Anaconda Cloud](https://anaconda.org/conda-forge/hyperspy-gui-ipywidgets/badges/version.svg)](https://anaconda.org/conda-forge/hyperspy-gui-ipywidgets) 6 | 7 | 8 | **hyperspy_gui_ipywidgets** provides ipywidgets graphic user interface (GUI) elements for hyperspy. 9 | 10 | 11 | ## Installation 12 | 13 | ### Option 1: With pip 14 | Make sure you have 15 | [pip installed](https://pip.pypa.io/en/stable/installing/) and run: 16 | 17 | ```bash 18 | pip install hyperspy_gui_ipywidgets 19 | ``` 20 | 21 | ### Option 2: With Anaconda 22 | 23 | Install anaconda for your platform and run 24 | 25 | ```bash 26 | conda install hyperspy-gui-ipywidgets -c conda-forge 27 | 28 | ``` 29 | 30 | ## Running the tests 31 | 32 | py.test is required to run the tests. 33 | 34 | ```bash 35 | pip install "hyperspy_gui_ipywidgets[test]" 36 | py.test --pyargs hyperspy_gui_ipywidgets 37 | ``` 38 | 39 | ## Usage 40 | 41 | Please refer to the [HyperSpy documentation](http://hyperspy.org/hyperspy-doc/current/index.html) for details. Example (to run in the [Jupyter Notebook](http://jupyter.org/)): 42 | 43 | ```python 44 | 45 | import hyperspy.api as hs 46 | hs.preferences.gui(toolkit="ipywidgets") 47 | ``` 48 | ![HyperSpy preferences ipywidget](https://github.com/hyperspy/hyperspy_gui_ipywidgets/raw/main/images/preferences_gui.png "HyperSpy preferences ipywidget") 49 | 50 | 51 | ## Development 52 | 53 | Contributions through pull requests are welcome. See the 54 | [HyperSpy Developer Guide](http://hyperspy.org/hyperspy-doc/current/dev_guide.html). 55 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2007-2025 The HyperSpy developers 3 | # 4 | # This file is part of HyperSpy. 5 | # 6 | # HyperSpy is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # HyperSpy is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with HyperSpy. If not, see . 18 | 19 | 20 | import importlib 21 | from importlib.metadata import version 22 | from pathlib import Path 23 | 24 | 25 | __version__ = version("hyperspy_gui_ipywidgets") 26 | 27 | # For development version, `setuptools_scm` will be used at build time 28 | # to get the dev version, in case of missing vcs information (git archive, 29 | # shallow repository), the fallback version defined in pyproject.toml will 30 | # be used 31 | 32 | # if we have a editable install from a git repository try to use 33 | # `setuptools_scm` to find a more accurate version: 34 | # `importlib.metadata` will provide the version at installation 35 | # time and for editable version this may be different 36 | 37 | # we only do that if we have enough git history, e.g. not shallow checkout 38 | _root = Path(__file__).resolve().parents[1] 39 | if (_root / ".git").exists() and not (_root / ".git/shallow").exists(): 40 | try: 41 | # setuptools_scm may not be installed 42 | from setuptools_scm import get_version 43 | 44 | __version__ = get_version(_root) 45 | except ImportError: # pragma: no cover 46 | # setuptools_scm not install, we keep the existing __version__ 47 | pass 48 | 49 | 50 | __all__ = [ 51 | 'axes', 52 | 'microscope_parameters', 53 | 'model', 54 | 'preferences', 55 | 'roi', 56 | 'tools', 57 | '__version__', 58 | ] 59 | 60 | 61 | def __dir__(): 62 | return sorted(__all__) 63 | 64 | 65 | def __getattr__(name): 66 | # lazy loading of module: this is only call when the attribute "name" is not found 67 | # in the module 68 | # See https://peps.python.org/pep-0562/ 69 | if name in __all__: 70 | return importlib.import_module( 71 | "." + name, 'hyperspy_gui_ipywidgets' 72 | ) 73 | raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 74 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/axes.py: -------------------------------------------------------------------------------- 1 | import ipywidgets 2 | import numpy as np 3 | 4 | from hyperspy_gui_ipywidgets.utils import ( 5 | labelme, add_display_arg, set_title_container 6 | ) 7 | from link_traits import link 8 | 9 | 10 | @add_display_arg 11 | def ipy_navigation_sliders(obj, **kwargs): 12 | return get_ipy_navigation_sliders(obj, **kwargs) 13 | 14 | 15 | def get_ipy_navigation_sliders(obj, in_accordion=False, 16 | random_position_button=False, 17 | **kwargs): 18 | continuous_update = ipywidgets.Checkbox(True, 19 | description="Continuous update") 20 | wdict = {} 21 | wdict["continuous_update"] = continuous_update 22 | widgets = [] 23 | for i, axis in enumerate(obj): 24 | axis_dict = {} 25 | wdict["axis{}".format(i)] = axis_dict 26 | iwidget = ipywidgets.IntSlider( 27 | min=0, 28 | max=axis.size - 1, 29 | readout=True, 30 | description="index" 31 | ) 32 | link((continuous_update, "value"), 33 | (iwidget, "continuous_update")) 34 | link((axis, "index"), (iwidget, "value")) 35 | if hasattr(axis, "scale"): 36 | vwidget = ipywidgets.BoundedFloatText( 37 | min=axis.low_value, 38 | max=axis.high_value, 39 | step=axis.scale, 40 | description="value" 41 | # readout_format=".lf" 42 | ) 43 | else: 44 | vwidget = ipywidgets.BoundedFloatText( 45 | min=0, 46 | max=axis.size - 1, 47 | #step=1, 48 | disabled=True, 49 | description="value" 50 | ) 51 | link((continuous_update, "value"), 52 | (vwidget, "continuous_update")) 53 | link((axis, "value"), (vwidget, "value")) 54 | link((axis, "high_value"), (vwidget, "max")) 55 | link((axis, "low_value"), (vwidget, "min")) 56 | if hasattr(axis, "scale"): 57 | link((axis, "scale"), (vwidget, "step")) 58 | name = ipywidgets.Label(str(axis), 59 | layout=ipywidgets.Layout(width="15%")) 60 | units = ipywidgets.Label(layout=ipywidgets.Layout(width="5%")) 61 | link((axis, "name"), (name, "value")) 62 | link((axis, "units"), (units, "value")) 63 | bothw = ipywidgets.HBox([name, iwidget, vwidget, units]) 64 | # labeled_widget = labelme(str(axis), bothw) 65 | widgets.append(bothw) 66 | axis_dict["value"] = vwidget 67 | axis_dict["index"] = iwidget 68 | axis_dict["units"] = units 69 | 70 | if random_position_button: 71 | random_nav_position = ipywidgets.Button( 72 | description="Set random navigation position.", 73 | tooltip="Set random navigation position, useful to check the " 74 | "method parameters.", 75 | layout=ipywidgets.Layout(width="auto")) 76 | 77 | def _random_navigation_position_fired(b): 78 | am = obj[0].axes_manager 79 | index = np.random.randint(0, am._max_index) 80 | am.indices = np.unravel_index(index, 81 | tuple(am._navigation_shape_in_array))[::-1] 82 | random_nav_position.on_click(_random_navigation_position_fired) 83 | 84 | wdict["random_nav_position_button"] = random_nav_position 85 | widgets.append(random_nav_position) 86 | 87 | widgets.append(continuous_update) 88 | box = ipywidgets.VBox(widgets) 89 | if in_accordion: 90 | box = ipywidgets.Accordion((box,)) 91 | set_title_container(box, ["Navigation sliders"]) 92 | return {"widget": box, "wdict": wdict} 93 | 94 | 95 | @add_display_arg 96 | def _get_axis_widgets(obj): 97 | widgets = [] 98 | wd = {} 99 | name = ipywidgets.Text() 100 | widgets.append(labelme(ipywidgets.Label("Name"), name)) 101 | link((obj, "name"), (name, "value")) 102 | wd["name"] = name 103 | 104 | size = ipywidgets.IntText(disabled=True) 105 | widgets.append(labelme("Size", size)) 106 | link((obj, "size"), (size, "value")) 107 | wd["size"] = size 108 | 109 | index_in_array = ipywidgets.IntText(disabled=True) 110 | widgets.append(labelme("Index in array", index_in_array)) 111 | link((obj, "index_in_array"), (index_in_array, "value")) 112 | wd["index_in_array"] = index_in_array 113 | if obj.navigate: 114 | index = ipywidgets.IntSlider(min=0, max=obj.size - 1) 115 | widgets.append(labelme("Index", index)) 116 | link((obj, "index"), (index, "value")) 117 | wd["index"] = index 118 | 119 | value = ipywidgets.FloatSlider( 120 | min=obj.low_value, 121 | max=obj.high_value, 122 | ) 123 | wd["value"] = value 124 | widgets.append(labelme("Value", value)) 125 | link((obj, "value"), (value, "value")) 126 | link((obj, "high_value"), (value, "max")) 127 | link((obj, "low_value"), (value, "min")) 128 | if hasattr(obj, "scale"): 129 | link((obj, "scale"), (value, "step")) 130 | 131 | units = ipywidgets.Text() 132 | widgets.append(labelme("Units", units)) 133 | link((obj, "units"), (units, "value")) 134 | wd["units"] = units 135 | 136 | if hasattr(obj, "scale"): 137 | scale = ipywidgets.FloatText() 138 | widgets.append(labelme("Scale", scale)) 139 | link((obj, "scale"), (scale, "value")) 140 | wd["scale"] = scale 141 | 142 | if hasattr(obj, "offset"): 143 | offset = ipywidgets.FloatText() 144 | widgets.append(labelme("Offset", offset)) 145 | link((obj, "offset"), (offset, "value")) 146 | wd["offset"] = offset 147 | 148 | if "_expression" in obj.__dict__.keys(): 149 | expression = ipywidgets.Text(disabled=True) 150 | widgets.append(labelme("Expression", expression)) 151 | link((obj, "_expression"), (expression, "value")) 152 | wd["expression"] = expression 153 | for i in range(len(obj.parameters_list)): 154 | parameter = ipywidgets.FloatText() 155 | widgets.append(labelme(obj.parameters_list[i], parameter)) 156 | link((obj, obj.parameters_list[i]), (parameter, "value")) 157 | wd["parameter"] = parameter 158 | if hasattr(obj.x, 'scale'): 159 | scale = ipywidgets.FloatText() 160 | widgets.append(labelme("x scale", scale)) 161 | link((obj.x, "scale"), (scale, "value")) 162 | wd["scale"] = scale 163 | if hasattr(obj.x, "offset"): 164 | offset = ipywidgets.FloatText() 165 | widgets.append(labelme("x offset", offset)) 166 | link((obj.x, "offset"), (offset, "value")) 167 | wd["offset"] = offset 168 | 169 | return { 170 | "widget": ipywidgets.VBox(widgets), 171 | "wdict": wd 172 | } 173 | 174 | 175 | @add_display_arg 176 | def ipy_axes_gui(obj, **kwargs): 177 | wdict = {} 178 | nav_widgets = [] 179 | sig_widgets = [] 180 | i = 0 181 | for axis in obj.navigation_axes: 182 | wd = _get_axis_widgets(axis, display=False) 183 | nav_widgets.append(wd["widget"]) 184 | wdict["axis{}".format(i)] = wd["wdict"] 185 | i += 1 186 | for j, axis in enumerate(obj.signal_axes): 187 | wd = _get_axis_widgets(axis, display=False) 188 | sig_widgets.append(wd["widget"]) 189 | wdict["axis{}".format(i + j)] = wd["wdict"] 190 | nav_accordion = ipywidgets.Accordion(nav_widgets) 191 | sig_accordion = ipywidgets.Accordion(sig_widgets) 192 | nav_titles = [f"Axis {i}" for i in range(obj.navigation_dimension)] 193 | set_title_container(nav_accordion, nav_titles) 194 | sig_titles = [f"Axis {j+obj.navigation_dimension+1}" for j in 195 | range(obj.signal_dimension)] 196 | set_title_container(sig_accordion, sig_titles) 197 | 198 | tabs = ipywidgets.HBox([nav_accordion, sig_accordion]) 199 | return { 200 | "widget": tabs, 201 | "wdict": wdict, 202 | } 203 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/conftest.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | matplotlib.use('agg') 3 | 4 | import hyperspy.api as hs 5 | 6 | hs.preferences.GUIs.enable_traitsui_gui = False 7 | hs.preferences.GUIs.enable_ipywidgets_gui = True 8 | 9 | # Use matplotlib fixture to clean up figure, setup backend, etc. 10 | from matplotlib.testing.conftest import mpl_test_settings # noqa: F401 11 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/custom_widgets.py: -------------------------------------------------------------------------------- 1 | import ipywidgets 2 | import traitlets 3 | 4 | 5 | class OddIntSlider(ipywidgets.IntSlider): 6 | 7 | @traitlets.validate('value') 8 | def _validate_value(self, proposal): 9 | value = proposal['value'] 10 | if not self.value % 2: 11 | value += 1 12 | if self.min > value or self.max < value: 13 | value = min(max(value, self.min), self.max) 14 | return value 15 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/hyperspy_extension.yaml: -------------------------------------------------------------------------------- 1 | GUI: 2 | widgets: 3 | ipywidgets: 4 | hyperspy.interactive_range_selector: 5 | module: hyperspy_gui_ipywidgets.tools 6 | function: interactive_range_ipy 7 | hyperspy.navigation_sliders: 8 | module: hyperspy_gui_ipywidgets.axes 9 | function: ipy_navigation_sliders 10 | hyperspy.Preferences: 11 | module: hyperspy_gui_ipywidgets.preferences 12 | function: show_preferences_widget 13 | exspy.Preferences: 14 | module: hyperspy_gui_ipywidgets.preferences 15 | function: show_exspy_preferences_widget 16 | hyperspy.DataAxis: 17 | module: hyperspy_gui_ipywidgets.axes 18 | function: _get_axis_widgets 19 | hyperspy.AxesManager: 20 | module: hyperspy_gui_ipywidgets.axes 21 | function: ipy_axes_gui 22 | hyperspy.Parameter: 23 | module: hyperspy_gui_ipywidgets.model 24 | function: get_parameter_widget 25 | hyperspy.Component: 26 | module: hyperspy_gui_ipywidgets.model 27 | function: get_component_widget 28 | exspy.EELSCLEdge_Component: 29 | module: hyperspy_gui_ipywidgets.model 30 | function: get_eelscl_widget 31 | exspy.EELSSpectrum.print_edges_table: 32 | module: hyperspy_gui_ipywidgets.tools 33 | function: print_edges_table_ipy 34 | hyperspy.ScalableFixedPattern_Component: 35 | module: hyperspy_gui_ipywidgets.model 36 | function: get_scalable_fixed_patter_widget 37 | hyperspy.Signal1D.calibrate: 38 | module: hyperspy_gui_ipywidgets.tools 39 | function: calibrate_ipy 40 | hyperspy.Signal1D.smooth_savitzky_golay: 41 | module: hyperspy_gui_ipywidgets.tools 42 | function: smooth_savitzky_golay_ipy 43 | hyperspy.Signal1D.smooth_lowess: 44 | module: hyperspy_gui_ipywidgets.tools 45 | function: smooth_lowess_ipy 46 | hyperspy.Signal1D.smooth_total_variation: 47 | module: hyperspy_gui_ipywidgets.tools 48 | function: smooth_tv_ipy 49 | hyperspy.Signal1D.smooth_butterworth: 50 | module: hyperspy_gui_ipywidgets.tools 51 | function: smooth_butterworth 52 | hyperspy.Signal1D.contrast_editor: 53 | module: hyperspy_gui_ipywidgets.tools 54 | function: image_constast_editor_ipy 55 | hyperspy.Signal1D.remove_background: 56 | module: hyperspy_gui_ipywidgets.tools 57 | function: remove_background_ipy 58 | hyperspy.Signal1D.remove_baseline: 59 | module: hyperspy_gui_ipywidgets.tools 60 | function: remove_baseline_ipy 61 | hyperspy.Signal1D.spikes_removal_tool: 62 | module: hyperspy_gui_ipywidgets.tools 63 | function: spikes_removal_ipy 64 | hyperspy.Signal2D.find_peaks: 65 | module: hyperspy_gui_ipywidgets.tools 66 | function: find_peaks2D_ipy 67 | hyperspy.Signal2D.calibrate: 68 | module: hyperspy_gui_ipywidgets.tools 69 | function: calibrate2d_ipy 70 | hyperspy.Point1DROI: 71 | module: hyperspy_gui_ipywidgets.roi 72 | function: point1d_roi_ipy 73 | hyperspy.Point2DROI: 74 | module: hyperspy_gui_ipywidgets.roi 75 | function: point_2d_ipy 76 | hyperspy.SpanROI: 77 | module: hyperspy_gui_ipywidgets.roi 78 | function: span_roi_ipy 79 | hyperspy.RectangularROI: 80 | module: hyperspy_gui_ipywidgets.roi 81 | function: rectangular_roi_ipy 82 | hyperspy.CircleROI: 83 | module: hyperspy_gui_ipywidgets.roi 84 | function: circle_roi_ipy 85 | hyperspy.Line2DROI: 86 | module: hyperspy_gui_ipywidgets.roi 87 | function: line2d_roi_ipy 88 | hyperspy.Model: 89 | module: hyperspy_gui_ipywidgets.model 90 | function: get_model_widget 91 | hyperspy.Model1D.fit_component: 92 | module: hyperspy_gui_ipywidgets.model 93 | function: fit_component_ipy 94 | exspy.microscope_parameters_EELS: 95 | module: hyperspy_gui_ipywidgets.microscope_parameters 96 | function: eels_microscope_parameter_ipy 97 | exspy.microscope_parameters_EDS_TEM: 98 | module: hyperspy_gui_ipywidgets.microscope_parameters 99 | function: eds_tem_microscope_parameter_ipy 100 | exspy.microscope_parameters_EDS_SEM: 101 | module: hyperspy_gui_ipywidgets.microscope_parameters 102 | function: eds_sem_microscope_parameter_ipy 103 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/microscope_parameters.py: -------------------------------------------------------------------------------- 1 | 2 | import ipywidgets 3 | 4 | from hyperspy_gui_ipywidgets.utils import ( 5 | add_display_arg, float2floattext, get_label) 6 | 7 | from link_traits import link 8 | 9 | 10 | def _set_microscope_parameters(obj, **kwargs): 11 | traits = obj.traits() 12 | widgets = [] 13 | wdict = {} 14 | for trait_name in obj.editable_traits(): 15 | if trait_name in ("mapping", "signal"): 16 | continue 17 | trait = traits[trait_name] 18 | widget = float2floattext( 19 | trait, get_label(trait, trait_name)) 20 | widgets.append(widget) 21 | wdict[trait_name] = widget.children[1] 22 | link((obj, trait_name), 23 | (widget.children[1], "value")) 24 | store_button = ipywidgets.Button( 25 | description="Store", 26 | tooltip="Store the values in metadata") 27 | store_button.on_click(obj.store) 28 | wdict["store_button"] = store_button 29 | container = ipywidgets.VBox([ipywidgets.VBox(widgets), store_button]) 30 | return { 31 | "widget": container, 32 | "wdict": wdict} 33 | 34 | 35 | @add_display_arg 36 | def eels_microscope_parameter_ipy(obj, **kwargs): 37 | return(_set_microscope_parameters(obj=obj, **kwargs)) 38 | 39 | 40 | @add_display_arg 41 | def eds_sem_microscope_parameter_ipy(obj, **kwargs): 42 | return(_set_microscope_parameters(obj=obj, **kwargs)) 43 | 44 | 45 | @add_display_arg 46 | def eds_tem_microscope_parameter_ipy(obj, **kwargs): 47 | return(_set_microscope_parameters(obj=obj, **kwargs)) 48 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/model.py: -------------------------------------------------------------------------------- 1 | import ipywidgets 2 | from ipywidgets import ( 3 | Accordion, FloatSlider, FloatText, Layout, HBox, VBox, Checkbox, Button, 4 | HTML) 5 | import numpy as np 6 | 7 | from link_traits import link, dlink 8 | from hyperspy_gui_ipywidgets.utils import ( 9 | add_display_arg, labelme, set_title_container, enum2dropdown 10 | ) 11 | 12 | 13 | def _interactive_slider_bounds(obj, index=None): 14 | """Guesstimates the bounds for the slider. They will probably have to 15 | be changed later by the user. 16 | 17 | """ 18 | pad = 10. 19 | _min, _max, step = None, None, None 20 | value = obj.value if index is None else obj.value[index] 21 | if obj.bmin is not None: 22 | _min = obj.bmin 23 | if obj.bmax is not None: 24 | _max = obj.bmax 25 | if _max is None and _min is not None: 26 | _max = value + pad 27 | if _min is None and _max is not None: 28 | _min = value - pad 29 | if _min is None and _max is None: 30 | if obj.component and obj is obj.component._position and \ 31 | obj._axes_manager is not None: 32 | axis = obj._axes_manager.signal_axes[-1] 33 | _min = axis.axis.min() 34 | _max = axis.axis.max() 35 | step = np.abs(axis.scale) 36 | else: 37 | _max = value + pad 38 | _min = value - pad 39 | if step is None: 40 | step = (_max - _min) * 0.001 41 | return {'min': _min, 'max': _max, 'step': step} 42 | 43 | 44 | def _get_value_widget(obj, index=None): 45 | wdict = {} 46 | widget_bounds = _interactive_slider_bounds(obj, index=index) 47 | thismin = FloatText(value=widget_bounds['min'], 48 | description='min', 49 | layout=Layout(flex='0 1 auto', 50 | width='auto'),) 51 | thismax = FloatText(value=widget_bounds['max'], 52 | description='max', 53 | layout=Layout(flex='0 1 auto', 54 | width='auto'),) 55 | current_value = obj.value if index is None else obj.value[index] 56 | if index is None: 57 | current_name = obj.name 58 | else: 59 | current_name = '{}'.format(index) 60 | widget = FloatSlider(value=current_value, 61 | min=thismin.value, 62 | max=thismax.value, 63 | step=widget_bounds['step'], 64 | description=current_name, 65 | layout=Layout(flex='1 1 auto', width='auto')) 66 | 67 | def on_min_change(change): 68 | if widget.max > change['new']: 69 | widget.min = change['new'] 70 | widget.step = np.abs(widget.max - widget.min) * 0.001 71 | 72 | def on_max_change(change): 73 | if widget.min < change['new']: 74 | widget.max = change['new'] 75 | widget.step = np.abs(widget.max - widget.min) * 0.001 76 | 77 | thismin.observe(on_min_change, names='value') 78 | thismax.observe(on_max_change, names='value') 79 | # We store the link in the widget so that they are not deleted by the 80 | # garbage collector 81 | thismin._link = dlink((obj, "bmin"), (thismin, "value")) 82 | thismax._link = dlink((obj, "bmax"), (thismax, "value")) 83 | if index is not None: # value is tuple, expanding 84 | def _interactive_tuple_update(value): 85 | """Callback function for the widgets, to update the value 86 | """ 87 | obj.value = obj.value[:index] + (value['new'],) +\ 88 | obj.value[index + 1:] 89 | widget.observe(_interactive_tuple_update, names='value') 90 | else: 91 | link((obj, "value"), (widget, "value")) 92 | 93 | container = HBox((thismin, widget, thismax)) 94 | wdict["value"] = widget 95 | wdict["min"] = thismin 96 | wdict["max"] = thismax 97 | return { 98 | "widget": container, 99 | "wdict": wdict, 100 | } 101 | 102 | 103 | @add_display_arg 104 | def get_parameter_widget(obj, **kwargs): 105 | """Creates interactive notebook widgets for the parameter, if 106 | available. 107 | 108 | """ 109 | if obj._number_of_elements == 1: 110 | return _get_value_widget(obj) 111 | else: 112 | wdict = {} 113 | par_widgets = [] 114 | for i in range(obj._number_of_elements): 115 | thiswd = _get_value_widget(obj=obj, index=i) 116 | par_widgets.append(thiswd["widget"]) 117 | wdict["element{}".format(i)] = thiswd["wdict"] 118 | update = Button( 119 | description="Update", 120 | tooltip="Unlike most other widgets, the multivalue parameter " 121 | "widgets do not update automatically when the value of the " 122 | "changes by other means. Use this button to update the values" 123 | "manually") 124 | 125 | def on_update_clicked(b): 126 | for value, container in zip(obj.value, par_widgets): 127 | 128 | minwidget = container.children[0] 129 | vwidget = container.children[1] 130 | maxwidget = container.children[2] 131 | if value < vwidget.min: 132 | minwidget.value = value 133 | elif value > vwidget.max: 134 | maxwidget.value = value 135 | vwidget.value = value 136 | update.on_click(on_update_clicked) 137 | wdict["update_button"] = update 138 | container = Accordion([VBox([update] + par_widgets)]) 139 | set_title_container(container, [obj.name]) 140 | 141 | return { 142 | "widget": container, 143 | "wdict": wdict, 144 | } 145 | 146 | 147 | @add_display_arg 148 | def get_component_widget(obj, **kwargs): 149 | """Creates interactive notebook widgets for all component parameters, 150 | if available. 151 | 152 | """ 153 | wdict = {} 154 | active = Checkbox(description='active', value=obj.active) 155 | wdict["active"] = active 156 | link((obj, "active"), (active, "value")) 157 | container = VBox([active]) 158 | for parameter in obj.parameters: 159 | pardict = parameter.gui( 160 | toolkit="ipywidgets", display=False)["ipywidgets"] 161 | wdict["parameter_{}".format(parameter.name)] = pardict["wdict"] 162 | container.children += pardict["widget"], 163 | return { 164 | "widget": container, 165 | "wdict": wdict, 166 | } 167 | 168 | 169 | @add_display_arg 170 | def get_model_widget(obj, **kwargs): 171 | """Creates interactive notebook widgets for all components and 172 | parameters, if available. 173 | 174 | """ 175 | children = [] 176 | wdict = {} 177 | for component in obj: 178 | idict = component.gui( 179 | display=False, 180 | toolkit="ipywidgets")["ipywidgets"] 181 | children.append(idict["widget"]) 182 | wdict["component_{}".format(component.name)] = idict["wdict"] 183 | accordion = Accordion(children=children) 184 | set_title_container(accordion, [comp.name for comp in obj]) 185 | return { 186 | "widget": accordion, 187 | "wdict": wdict 188 | } 189 | 190 | 191 | @add_display_arg 192 | def get_eelscl_widget(obj, **kwargs): 193 | """Create ipywidgets for the EELSCLEdge component. 194 | 195 | """ 196 | wdict = {} 197 | active = Checkbox(description='active', value=obj.active) 198 | fine_structure = Checkbox(description='Fine structure', 199 | value=obj.fine_structure_active) 200 | fs_smoothing = FloatSlider(description='Fine structure smoothing', 201 | min=0, max=1, step=0.001, 202 | value=obj.fine_structure_smoothing) 203 | link((obj, "active"), (active, "value")) 204 | link((obj, "fine_structure_active"), 205 | (fine_structure, "value")) 206 | link((obj, "fine_structure_smoothing"), 207 | (fs_smoothing, "value")) 208 | container = VBox([active, fine_structure, fs_smoothing]) 209 | wdict["active"] = active 210 | wdict["fine_structure"] = fine_structure 211 | wdict["fs_smoothing"] = fs_smoothing 212 | for parameter in [obj.intensity, obj.effective_angle, 213 | obj.onset_energy]: 214 | pdict = parameter.gui( 215 | toolkit="ipywidgets", display=False)["ipywidgets"] 216 | container.children += pdict["widget"], 217 | wdict["parameter_{}".format(parameter.name)] = pdict["wdict"] 218 | return { 219 | "widget": container, 220 | "wdict": wdict, 221 | } 222 | 223 | 224 | @add_display_arg 225 | def get_scalable_fixed_patter_widget(obj, **kwargs): 226 | cdict = get_component_widget(obj, display=False) 227 | wdict = cdict["wdict"] 228 | container = cdict["widget"] 229 | interpolate = Checkbox(description='interpolate', 230 | value=obj.interpolate) 231 | wdict["interpolate"] = interpolate 232 | link((obj, "interpolate"), (interpolate, "value")) 233 | container.children = (container.children[0], interpolate) + \ 234 | container.children[1:] 235 | return { 236 | "widget": container, 237 | "wdict": wdict, 238 | } 239 | 240 | 241 | @add_display_arg 242 | def fit_component_ipy(obj, **kwargs): 243 | wdict = {} 244 | only_current = Checkbox() 245 | iterpath = enum2dropdown(obj.traits()["iterpath"]) 246 | 247 | def disable_iterpath(change): 248 | iterpath.disabled = change.new 249 | only_current.observe(disable_iterpath, "value") 250 | 251 | wdict["only_current"] = only_current 252 | wdict["iterpath"] = iterpath 253 | help_text = HTML( 254 | "Click on the signal figure and drag to the right to select a" 255 | "range. Press `Fit` to fit the component in that range. If only " 256 | "current is unchecked the fit is performed in the whole dataset.", 257 | layout=ipywidgets.Layout(width="auto")) 258 | wdict["help_text"] = help_text 259 | 260 | help = Accordion(children=[help_text], selected_index=None) 261 | set_title_container(help, ["Help"]) 262 | 263 | link((obj, "only_current"), (only_current, "value")) 264 | link((obj, "iterpath"), (iterpath, "value")) 265 | fit = Button( 266 | description="Fit", 267 | tooltip="Fit in the selected signal range") 268 | close = Button( 269 | description="Close", 270 | tooltip="Close widget and remove span selector from the signal figure.") 271 | wdict["close_button"] = close 272 | wdict["fit_button"] = fit 273 | 274 | def on_fit_clicked(b): 275 | obj._fit_fired() 276 | fit.on_click(on_fit_clicked) 277 | box = VBox([ 278 | labelme("Only current", only_current), 279 | labelme("Iterpath", wdict["iterpath"]), 280 | help, 281 | HBox((fit, close)) 282 | ]) 283 | 284 | def on_close_clicked(b): 285 | obj.span_selector_switch(False) 286 | box.close() 287 | close.on_click(on_close_clicked) 288 | return { 289 | "widget": box, 290 | "wdict": wdict, 291 | } 292 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/preferences.py: -------------------------------------------------------------------------------- 1 | import traits 2 | import ipywidgets 3 | 4 | from link_traits import link 5 | from hyperspy_gui_ipywidgets.utils import ( 6 | labelme, add_display_arg, float2floattext, get_label, str2text, 7 | set_title_container 8 | ) 9 | 10 | 11 | def bool2checkbox(trait, label): 12 | description_tooltip = trait.desc if trait.desc else "" 13 | widget = ipywidgets.Checkbox() 14 | widget.description_tooltip = description_tooltip 15 | return labelme(widget=widget, label=label) 16 | 17 | 18 | def directory2unicode(trait, label): 19 | description_tooltip = trait.desc if trait.desc else "" 20 | widget = ipywidgets.Text() 21 | widget.description_tooltip = description_tooltip 22 | return labelme(widget=widget, label=label) 23 | 24 | 25 | def enum2dropdown(trait, label): 26 | widget = ipywidgets.Dropdown( 27 | options=trait.trait_type.values) 28 | return labelme(widget=widget, label=label) 29 | 30 | 31 | def range2floatrangeslider(trait, label): 32 | description_tooltip = trait.desc if trait.desc else "" 33 | widget = ipywidgets.FloatSlider( 34 | min=trait.trait_type._low, 35 | max=trait.trait_type._high, 36 | description_tooltip=description_tooltip,) 37 | return labelme(widget=widget, label=label) 38 | 39 | 40 | # Trait types must be converted to the appropriate ipywidget 41 | TRAITS2IPYWIDGETS = { 42 | traits.trait_types.CBool: bool2checkbox, 43 | traits.trait_types.Bool: bool2checkbox, 44 | traits.trait_types.CFloat: float2floattext, 45 | traits.trait_types.Directory: directory2unicode, 46 | traits.trait_types.File: directory2unicode, 47 | traits.trait_types.Range: range2floatrangeslider, 48 | traits.trait_types.Enum: enum2dropdown, 49 | traits.trait_types.Str: str2text, 50 | } 51 | 52 | 53 | @add_display_arg 54 | def show_preferences_widget(obj, **kwargs): 55 | ipytabs = {} 56 | wdict = {} 57 | for tab in obj.editable_traits(): 58 | tabdict = {} 59 | wdict["tab_{}".format(tab)] = tabdict 60 | ipytab = [] 61 | tabtraits = getattr(obj, tab).traits() 62 | for trait_name in getattr(obj, tab).editable_traits(): 63 | trait = tabtraits[trait_name] 64 | widget = TRAITS2IPYWIDGETS[type(trait.trait_type)]( 65 | trait, get_label(trait, trait_name)) 66 | ipytab.append(widget) 67 | tabdict[trait_name] = widget.children[1] 68 | link((getattr(obj, tab), trait_name), 69 | (widget.children[1], "value")) 70 | ipytabs[tab] = ipywidgets.VBox(ipytab) 71 | # This defines the order of the tab in the widget 72 | titles = ["General", "GUIs", "Plot"] 73 | ipytabs_ = ipywidgets.Tab( 74 | children=[ipytabs[title] for title in titles]) 75 | set_title_container(ipytabs_, titles) 76 | save_button = ipywidgets.Button( 77 | description="Save", 78 | tooltip="Make changes permanent") 79 | wdict["save_button"] = save_button 80 | 81 | def on_button_clicked(b): 82 | obj.save() 83 | 84 | save_button.on_click(on_button_clicked) 85 | 86 | container = ipywidgets.VBox([ipytabs_, save_button]) 87 | return { 88 | "widget": container, 89 | "wdict": wdict, 90 | } 91 | 92 | 93 | @add_display_arg 94 | def show_exspy_preferences_widget(obj, **kwargs): 95 | ipytabs = {} 96 | wdict = {} 97 | for tab in obj.editable_traits(): 98 | tabdict = {} 99 | wdict["tab_{}".format(tab)] = tabdict 100 | ipytab = [] 101 | tabtraits = getattr(obj, tab).traits() 102 | for trait_name in getattr(obj, tab).editable_traits(): 103 | trait = tabtraits[trait_name] 104 | widget = TRAITS2IPYWIDGETS[type(trait.trait_type)]( 105 | trait, get_label(trait, trait_name)) 106 | ipytab.append(widget) 107 | tabdict[trait_name] = widget.children[1] 108 | link((getattr(obj, tab), trait_name), 109 | (widget.children[1], "value")) 110 | ipytabs[tab] = ipywidgets.VBox(ipytab) 111 | # This defines the order of the tab in the widget 112 | titles = ["EELS", "EDS"] 113 | ipytabs_ = ipywidgets.Tab( 114 | children=[ipytabs[title] for title in titles]) 115 | set_title_container(ipytabs_, titles) 116 | save_button = ipywidgets.Button( 117 | description="Save", 118 | tooltip="Make changes permanent") 119 | wdict["save_button"] = save_button 120 | 121 | def on_button_clicked(b): 122 | obj.save() 123 | 124 | save_button.on_click(on_button_clicked) 125 | 126 | container = ipywidgets.VBox([ipytabs_, save_button]) 127 | return { 128 | "widget": container, 129 | "wdict": wdict, 130 | } 131 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/roi.py: -------------------------------------------------------------------------------- 1 | import ipywidgets 2 | import traitlets 3 | 4 | from hyperspy_gui_ipywidgets.utils import ( 5 | labelme, labelme_sandwich, enum2dropdown, add_display_arg, ) 6 | from link_traits import link 7 | 8 | 9 | @add_display_arg 10 | def span_roi_ipy(obj, **kwargs): 11 | wdict = {} 12 | left = ipywidgets.FloatText(description="Left") 13 | right = ipywidgets.FloatText(description="Right") 14 | link((obj, "left"), (left, "value")) 15 | link((obj, "right"), (right, "value")) 16 | wdict["left"] = left 17 | wdict["right"] = right 18 | container = ipywidgets.HBox([left, right]) 19 | return { 20 | "widget": container, 21 | "wdict": wdict, 22 | } 23 | 24 | 25 | @add_display_arg 26 | def point1d_roi_ipy(obj, **kwargs): 27 | wdict = {} 28 | value = ipywidgets.FloatText(description="value") 29 | wdict["value"] = value 30 | link((obj, "value"), (value, "value")) 31 | return { 32 | "widget": value, 33 | "wdict": wdict, 34 | } 35 | 36 | 37 | @add_display_arg 38 | def point_2d_ipy(obj, **kwargs): 39 | wdict = {} 40 | x = ipywidgets.FloatText(description="x") 41 | y = ipywidgets.FloatText(description="y") 42 | wdict["x"] = x 43 | wdict["y"] = y 44 | link((obj, "x"), (x, "value")) 45 | link((obj, "y"), (y, "value")) 46 | container = ipywidgets.HBox([x, y]) 47 | return { 48 | "widget": container, 49 | "wdict": wdict, 50 | } 51 | 52 | 53 | @add_display_arg 54 | def rectangular_roi_ipy(obj, **kwargs): 55 | wdict = {} 56 | left = ipywidgets.FloatText(description="left") 57 | right = ipywidgets.FloatText(description="right") 58 | link((obj, "left"), (left, "value")) 59 | link((obj, "right"), (right, "value")) 60 | container1 = ipywidgets.HBox([left, right]) 61 | top = ipywidgets.FloatText(description="top") 62 | bottom = ipywidgets.FloatText(description="bottom") 63 | link((obj, "top"), (top, "value")) 64 | link((obj, "bottom"), (bottom, "value")) 65 | container2 = ipywidgets.HBox([top, bottom]) 66 | container = ipywidgets.VBox([container1, container2]) 67 | wdict["left"] = left 68 | wdict["right"] = right 69 | wdict["top"] = top 70 | wdict["bottom"] = bottom 71 | return { 72 | "widget": container, 73 | "wdict": wdict, 74 | } 75 | 76 | 77 | @add_display_arg 78 | def circle_roi_ipy(obj, **kwargs): 79 | wdict = {} 80 | x = ipywidgets.FloatText(description="x") 81 | y = ipywidgets.FloatText(description="y") 82 | link((obj, "cx"), (x, "value")) 83 | link((obj, "cy"), (y, "value")) 84 | container1 = ipywidgets.HBox([x, y]) 85 | radius = ipywidgets.FloatText(description="radius") 86 | inner_radius = ipywidgets.FloatText(description="inner_radius") 87 | link((obj, "r"), (radius, "value")) 88 | link((obj, "r_inner"), (inner_radius, "value")) 89 | container2 = ipywidgets.HBox([radius, inner_radius]) 90 | container = ipywidgets.VBox([container1, container2]) 91 | wdict["cx"] = x 92 | wdict["cy"] = y 93 | wdict["radius"] = radius 94 | wdict["inner_radius"] = inner_radius 95 | return { 96 | "widget": container, 97 | "wdict": wdict, 98 | } 99 | 100 | 101 | @add_display_arg 102 | def line2d_roi_ipy(obj, **kwargs): 103 | wdict = {} 104 | x1 = ipywidgets.FloatText(description="x1") 105 | y1 = ipywidgets.FloatText(description="x2") 106 | link((obj, "x1"), (x1, "value")) 107 | link((obj, "y1"), (y1, "value")) 108 | container1 = ipywidgets.HBox([x1, y1]) 109 | x2 = ipywidgets.FloatText(description="x2") 110 | y2 = ipywidgets.FloatText(description="y2") 111 | link((obj, "x2"), (x2, "value")) 112 | link((obj, "y2"), (y2, "value")) 113 | container2 = ipywidgets.HBox([x2, y2]) 114 | linewidth = ipywidgets.FloatText(description="linewidth") 115 | link((obj, "linewidth"), (linewidth, "value")) 116 | container = ipywidgets.VBox([container1, container2, linewidth]) 117 | wdict["x1"] = x1 118 | wdict["x2"] = x2 119 | wdict["y1"] = y1 120 | wdict["y2"] = y2 121 | wdict["linewidth"] = linewidth 122 | return { 123 | "widget": container, 124 | "wdict": wdict, 125 | } 126 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperspy/hyperspy_gui_ipywidgets/073f47ba36940f8d0fbd51e38f2b9d628c10aa04/hyperspy_gui_ipywidgets/tests/__init__.py -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/test_axes.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | import hyperspy.api as hs 5 | from hyperspy_gui_ipywidgets.tests.utils import KWARGS 6 | 7 | 8 | def check_axis_attributes(axes_manager, widgets_dict, index, attributes): 9 | for attribute in attributes: 10 | assert (widgets_dict["axis{}".format(index)][attribute].value == 11 | getattr(axes_manager[index], attribute)) 12 | 13 | 14 | class TestAxes: 15 | 16 | def setup_method(self, method): 17 | self.s = hs.signals.Signal1D(np.empty((2, 3, 4))) 18 | am = self.s.axes_manager 19 | am[0].scale = 0.5 20 | am[0].name = "a" 21 | am[0].units = "eV" 22 | am[1].scale = 1000 23 | am[1].name = "b" 24 | am[1].units = "meters" 25 | am[2].scale = 5 26 | am[2].name = "c" 27 | am[2].units = "e" 28 | am.indices = (2, 1) 29 | 30 | def test_navigation_sliders(self): 31 | s = self.s 32 | am = self.s.axes_manager 33 | wd = s.axes_manager.gui_navigation_sliders( 34 | **KWARGS)["ipywidgets"]["wdict"] 35 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=0, 36 | attributes=("value", "index", "units")) 37 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=1, 38 | attributes=("value", "index", "units")) 39 | wd["axis0"]["value"].value = 1.5 40 | am[0].units = "cm" 41 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=0, 42 | attributes=("value", "index", "units")) 43 | 44 | def test_axes_manager_gui(self): 45 | s = self.s 46 | am = self.s.axes_manager 47 | wd = s.axes_manager.gui(**KWARGS)["ipywidgets"]["wdict"] 48 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=0, 49 | attributes=("value", "index", "units", 50 | "index_in_array", "name", 51 | "size", "scale", "offset")) 52 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=1, 53 | attributes=("value", "index", "units", 54 | "index_in_array", "name", "size", 55 | "scale", "offset")) 56 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=2, 57 | attributes=("units", "index_in_array", 58 | "name", "size", "scale", 59 | "offset")) 60 | wd["axis0"]["value"].value = 1.5 61 | wd["axis0"]["name"].name = "parrot" 62 | wd["axis0"]["offset"].name = -1 63 | wd["axis0"]["scale"].name = 1e-10 64 | wd["axis0"]["units"].value = "cm" 65 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=0, 66 | attributes=("value", "index", "units", 67 | "index_in_array", "name", 68 | "size", "scale", "offset")) 69 | 70 | def test_non_uniform_axes(): 71 | try: 72 | from hyperspy.axes import UniformDataAxis 73 | except ImportError: 74 | pytest.skip("HyperSpy version doesn't support non-uniform axis") 75 | 76 | dict0 = {'scale': 1.0, 'size': 2, } 77 | dict1 = {'expression': 'a / (x+b)', 'a': 1240, 'b': 1, 'size': 3, 78 | 'name': 'plumage', 'units': 'beautiful'} 79 | dict2 = {'axis': np.arange(4), 'name': 'norwegianblue', 'units': 'ex'} 80 | dict3 = {'expression': 'a / (x+b)', 'a': 1240, 'b': 1, 'x': dict2, 81 | 'name': 'pushing up', 'units': 'the daisies'} 82 | s = hs.signals.Signal1D(np.empty((3, 2, 4, 4)), axes=[dict0, dict1, dict2, dict3]) 83 | s.axes_manager[0].navigate = False 84 | 85 | am = s.axes_manager 86 | wd = s.axes_manager.gui(**KWARGS)["ipywidgets"]["wdict"] 87 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=0, 88 | attributes=("name", "units", "size", "index", 89 | "value", "index_in_array",)) 90 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=2, 91 | attributes=("name", "units", "size", 92 | "index_in_array")) 93 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=3, 94 | attributes=("name", "units", "size", 95 | "index_in_array")) 96 | wd2 = s.axes_manager.gui_navigation_sliders( 97 | **KWARGS)["ipywidgets"]["wdict"] 98 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=0, 99 | attributes=("name", "units", "size", "index", 100 | "value", "index_in_array",)) 101 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=2, 102 | attributes=("name", "units", "size", 103 | "index_in_array")) 104 | check_axis_attributes(axes_manager=am, widgets_dict=wd, index=3, 105 | attributes=("name", "units", "size", 106 | "index_in_array")) 107 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/test_import.py: -------------------------------------------------------------------------------- 1 | 2 | def test_import_version(): 3 | from hyperspy_gui_ipywidgets import __version__ 4 | 5 | 6 | def test_import(): 7 | import hyperspy_gui_ipywidgets 8 | for obj_name in hyperspy_gui_ipywidgets.__all__: 9 | getattr(hyperspy_gui_ipywidgets, obj_name) 10 | 11 | 12 | def test_import_import_error(): 13 | import hyperspy_gui_ipywidgets 14 | try: 15 | hyperspy_gui_ipywidgets.inexisting_module 16 | except AttributeError: 17 | pass 18 | 19 | 20 | def test_dir(): 21 | import hyperspy_gui_ipywidgets 22 | d = dir(hyperspy_gui_ipywidgets) 23 | assert d == ['__version__', 24 | 'axes', 25 | 'microscope_parameters', 26 | 'model', 27 | 'preferences', 28 | 'roi', 29 | 'tools' 30 | ] 31 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/test_microscope_parameters.py: -------------------------------------------------------------------------------- 1 | from numpy.random import random 2 | import pytest 3 | 4 | import hyperspy.api as hs 5 | from hyperspy_gui_ipywidgets.tests.utils import KWARGS 6 | 7 | 8 | exspy = pytest.importorskip("exspy") 9 | 10 | 11 | class TestSetMicroscopeParameters: 12 | 13 | def setup_method(self, method): 14 | self.s = hs.signals.Signal1D((2, 3, 4)) 15 | 16 | def _perform_t(self, signal_type): 17 | s = self.s 18 | s.set_signal_type(signal_type) 19 | md = s.metadata 20 | wd = s.set_microscope_parameters(**KWARGS)["ipywidgets"]["wdict"] 21 | if signal_type == "EELS": 22 | mapping = exspy.signals.eels.EELSTEMParametersUI.mapping 23 | elif signal_type == "EDS_SEM": 24 | mapping = exspy.signals.eds_sem.EDSSEMParametersUI.mapping 25 | elif signal_type == "EDS_TEM": 26 | mapping = exspy.signals.eds_tem.EDSTEMParametersUI.mapping 27 | for key, widget in wd.items(): 28 | if "button" not in key: 29 | widget.value = random() 30 | button = wd["store_button"] 31 | button._click_handlers(button) # Trigger it 32 | for item, name in mapping.items(): 33 | assert md.get_item(item) == wd[name].value 34 | 35 | def test_eels(self): 36 | self._perform_t(signal_type="EELS") 37 | 38 | def test_eds_tem(self): 39 | self._perform_t(signal_type="EDS_TEM") 40 | 41 | def test_eds_sem(self): 42 | self._perform_t(signal_type="EDS_SEM") 43 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/test_model.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | from numpy.random import random 4 | import pytest 5 | 6 | import hyperspy.api as hs 7 | from hyperspy.component import Component, Parameter 8 | from hyperspy_gui_ipywidgets.tests.utils import KWARGS 9 | from hyperspy.models.model1d import ComponentFit 10 | 11 | 12 | def test_parameter(): 13 | p = Parameter() 14 | p.bmin = None 15 | p.bmax = 10 16 | p.value = 1.5 17 | wd = p.gui(**KWARGS)["ipywidgets"]["wdict"] 18 | assert wd["value"].value == p.value 19 | assert wd["max"].value == p.bmax 20 | wd["value"].value = -4 21 | p.bmin = -10 22 | p.bmax = 0 23 | assert wd["value"].value == p.value 24 | assert wd["min"].value == p.bmin 25 | assert wd["max"].value == p.bmax 26 | 27 | 28 | def test_multivalue_parameter(): 29 | p = Parameter() 30 | p._number_of_elements = 2 31 | p.value = (1.5, 3) 32 | wd = p.gui(**KWARGS)["ipywidgets"]["wdict"] 33 | assert wd["element0"]["value"].value == p.value[0] 34 | assert wd["element1"]["value"].value == p.value[1] 35 | wd["element0"]["value"].value = -4 36 | wd["element1"]["value"].value = -3 37 | assert wd["element0"]["value"].value == p.value[0] 38 | assert wd["element1"]["value"].value == p.value[1] 39 | 40 | wd["update_button"]._click_handlers(wd["update_button"]) # Trigger it 41 | # TODO: bounds 42 | 43 | 44 | def test_component(): 45 | c = Component(["a", "b"]) 46 | c.a.value = 3 47 | c.b.value = 2 48 | c.active = False 49 | wd = c.gui(**KWARGS)["ipywidgets"]["wdict"] 50 | assert wd["active"].value == c.active 51 | assert wd["parameter_a"]["value"].value == c.a.value 52 | assert wd["parameter_b"]["value"].value == c.b.value 53 | wd["active"].value = True 54 | wd["parameter_b"]["value"].value = 34 55 | wd["parameter_a"]["value"].value = 31 56 | assert wd["active"].value == c.active 57 | assert wd["parameter_a"]["value"].value == c.a.value 58 | assert wd["parameter_b"]["value"].value == c.b.value 59 | 60 | 61 | def test_model(): 62 | s = hs.signals.Signal1D([0]) 63 | m = s.create_model() 64 | c = Component(["a", "b"]) 65 | d = Component(["a", "b"]) 66 | m.extend((c, d)) 67 | c.name = "c" 68 | d.name = "d" 69 | c.active = False 70 | d.active = True 71 | wd = m.gui(**KWARGS)["ipywidgets"]["wdict"] 72 | assert wd["component_c"]["active"].value == c.active 73 | assert wd["component_d"]["active"].value == d.active 74 | 75 | 76 | def test_eels_component(): 77 | exspy = pytest.importorskip("exspy") 78 | s = exspy.signals.EELSSpectrum(np.empty((500,))) 79 | s.add_elements(("C",)) 80 | s.set_microscope_parameters(100, 10, 10) 81 | m = s.create_model(auto_background=False) 82 | c = m.components.C_K 83 | c.active = False 84 | c.fine_structure_smoothing = 0.1 85 | c.fine_structure_active = True 86 | wd = m.gui(**KWARGS)["ipywidgets"]["wdict"]["component_C_K"] 87 | assert wd["active"].value == c.active 88 | assert wd["fs_smoothing"].value == c.fine_structure_smoothing 89 | assert wd["fine_structure"].value == c.fine_structure_active 90 | assert "parameter_fine_structure_coeff" not in wd 91 | wd["active"].value = not c.active 92 | wd["fs_smoothing"].value = 0.2 93 | wd["fine_structure"].value = not c.fine_structure_active 94 | assert wd["active"].value == c.active 95 | assert wd["fs_smoothing"].value == c.fine_structure_smoothing 96 | assert wd["fine_structure"].value == c.fine_structure_active 97 | 98 | 99 | def test_scalable_fixed_pattern(): 100 | s = hs.signals.Signal1D(np.ones((500,))) 101 | m = s.create_model() 102 | c = hs.model.components1D.ScalableFixedPattern(s) 103 | c.name = "sfp" 104 | m.append(c) 105 | c.intepolate = not c.interpolate 106 | wd = m.gui(**KWARGS)["ipywidgets"]["wdict"]["component_sfp"] 107 | assert wd["interpolate"].value == c.interpolate 108 | wd["interpolate"].value = not c.interpolate 109 | assert wd["interpolate"].value == c.interpolate 110 | 111 | 112 | def test_fit_component(): 113 | np.random.seed(0) 114 | s = hs.signals.Signal1D(np.random.normal(size=1000, loc=1)).get_histogram() 115 | s = hs.stack([s, s], axis=0) 116 | m = s.create_model() 117 | m.extend([hs.model.components1D.Gaussian(), 118 | hs.model.components1D.Gaussian()]) 119 | g1, g2 = m 120 | g1.centre.value = 0 121 | g2.centre.value = 8 122 | fc = ComponentFit(model=m, component=g1) 123 | fc.ss_left_value = -2 124 | fc.ss_right_value = 4 125 | fc.only_current = not fc.only_current 126 | wd = fc.gui(**KWARGS)["ipywidgets"]["wdict"] 127 | wd["fit_button"]._click_handlers(wd["fit_button"]) # Trigger it 128 | assert wd["only_current"].value == fc.only_current 129 | wd["only_current"].value = not fc.only_current 130 | assert wd["only_current"].value == fc.only_current 131 | assert g2.centre.value == 8 132 | np.testing.assert_allclose(g1.centre.value, 0.804, rtol=1E-2) 133 | np.testing.assert_allclose(g1.sigma.value, 0.965, rtol=1E-2) 134 | 135 | assert wd["iterpath"].disabled == True 136 | fc.only_current = False 137 | assert wd["iterpath"].disabled == False 138 | 139 | wd["close_button"]._click_handlers(wd["close_button"]) # Trigger it 140 | 141 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/test_preferences.py: -------------------------------------------------------------------------------- 1 | 2 | import ipywidgets 3 | from numpy.random import random, uniform 4 | import pytest 5 | 6 | import hyperspy.api as hs 7 | from hyperspy_gui_ipywidgets.tests.utils import KWARGS 8 | 9 | 10 | module_list = [hs] 11 | try: 12 | import exspy 13 | module_list.append(exspy) 14 | except Exception: 15 | # exspy is not installed 16 | pass 17 | 18 | 19 | @pytest.mark.parametrize("module", module_list) 20 | def test_preferences(module): 21 | wd = module.preferences.gui(**KWARGS)["ipywidgets"]["wdict"] 22 | for tabkey, tabvalue in wd.items(): 23 | if tabkey.startswith("tab_"): 24 | for key, value in tabvalue.items(): 25 | assert getattr( 26 | getattr(module.preferences, tabkey[4:]), key) == value.value 27 | value_bk = value.value 28 | if isinstance(value, ipywidgets.Checkbox): 29 | value.value = not value 30 | elif isinstance(value, ipywidgets.FloatText): 31 | value.value = random() 32 | elif isinstance(value, ipywidgets.Text): 33 | value.value = "qwerty" 34 | elif isinstance(value, ipywidgets.FloatSlider): 35 | value.value = uniform(low=value.min, high=value.max) 36 | elif isinstance(value, ipywidgets.Dropdown): 37 | options = set(value.options) - set(value.value) 38 | value.value = options.pop() 39 | assert getattr( 40 | getattr(module.preferences, tabkey[4:]), key) == value.value 41 | value.value = value_bk 42 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/test_roi.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | 4 | import hyperspy.api as hs 5 | from hyperspy_gui_ipywidgets.tests.utils import KWARGS 6 | 7 | 8 | def test_span_roi(): 9 | roi = hs.roi.SpanROI(left=0, right=10) 10 | wd = roi.gui(**KWARGS)["ipywidgets"]["wdict"] 11 | assert wd["left"].value == 0 12 | assert wd["right"].value == 10 13 | wd["left"].value = -10 14 | wd["right"].value = 0 15 | assert roi.left == -10 16 | assert roi.right == 0 17 | 18 | 19 | def test_point_1d_roi(): 20 | roi = hs.roi.Point1DROI(value=5.5) 21 | wd = roi.gui(**KWARGS)["ipywidgets"]["wdict"] 22 | assert wd["value"].value == 5.5 23 | wd["value"].value = 0 24 | assert roi.value == 0 25 | 26 | 27 | def test_point2d(): 28 | roi = hs.roi.Point2DROI(x=0, y=10) 29 | wd = roi.gui(**KWARGS)["ipywidgets"]["wdict"] 30 | assert wd["x"].value == 0 31 | assert wd["y"].value == 10 32 | wd["x"].value = -10 33 | wd["y"].value = 0 34 | assert roi.x == -10 35 | assert roi.y == 0 36 | 37 | 38 | def test_rectangular_roi(): 39 | roi = hs.roi.RectangularROI(left=0, right=10, top=-10, bottom=0) 40 | wd = roi.gui(**KWARGS)["ipywidgets"]["wdict"] 41 | assert wd["left"].value == 0 42 | assert wd["right"].value == 10 43 | assert wd["top"].value == -10 44 | assert wd["bottom"].value == 0 45 | wd["left"].value = -10 46 | wd["right"].value = 0 47 | wd["bottom"].value = 1.2 48 | wd["top"].value = 1.1 49 | assert roi.left == -10 50 | assert roi.right == 0 51 | assert roi.top == 1.1 52 | assert roi.bottom == 1.2 53 | 54 | 55 | def test_circle_roi(): 56 | roi = hs.roi.CircleROI(cx=0, cy=0, r=1, r_inner=0.5) 57 | wd = roi.gui(**KWARGS)["ipywidgets"]["wdict"] 58 | assert wd["cx"].value == 0 59 | assert wd["cy"].value == 0 60 | assert wd["radius"].value == 1 61 | assert wd["inner_radius"].value == 0.5 62 | wd["cx"].value = 1 63 | wd["cy"].value = 2 64 | wd["radius"].value = 4 65 | wd["inner_radius"].value = 1.5 66 | assert roi.cx == 1 67 | assert roi.cy == 2 68 | assert roi.r == 4 69 | assert roi.r_inner == 1.5 70 | 71 | 72 | def test_line2d_roi(): 73 | roi = hs.roi.Line2DROI(x1=0, x2=10, y1=0, y2=10, linewidth=2) 74 | wd = roi.gui(**KWARGS)["ipywidgets"]["wdict"] 75 | assert wd["x1"].value == 0 76 | assert wd["x2"].value == 10 77 | assert wd["y1"].value == 0 78 | assert wd["y2"].value == 10 79 | assert wd["linewidth"].value == 2 80 | wd["x1"].value = 12 81 | wd["x2"].value = 23 82 | wd["y1"].value = -12 83 | wd["y2"].value = -23 84 | wd["linewidth"].value = 100 85 | assert roi.x1 == 12 86 | assert roi.x2 == 23 87 | assert roi.y1 == -12 88 | assert roi.y2 == -23 89 | assert roi.linewidth == 100 90 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/test_tools.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | import hyperspy.api as hs 5 | from hyperspy_gui_ipywidgets.tests.utils import KWARGS 6 | from hyperspy.signal_tools import ( 7 | Signal1DCalibration, 8 | Signal2DCalibration, 9 | ImageContrastEditor, 10 | ) 11 | from hyperspy.utils.baseline_removal_tool import BaselineRemoval 12 | 13 | 14 | class TestTools: 15 | 16 | def setup_method(self, method): 17 | self.s = hs.signals.Signal1D(1 + np.arange(100)**2) 18 | self.s.change_dtype('float') 19 | self.s.axes_manager[0].offset = 10 20 | self.s.axes_manager[0].scale = 2 21 | self.s.axes_manager[0].units = "m" 22 | 23 | def test_calibrate(self): 24 | s = self.s 25 | cal = Signal1DCalibration(s) 26 | cal.ss_left_value = 10 27 | cal.ss_right_value = 30 28 | wd = cal.gui(**KWARGS)["ipywidgets"]["wdict"] 29 | wd["new_left"].value = 0 30 | wd["new_right"].value = 10 31 | wd["units"].value = "nm" 32 | wd["apply_button"]._click_handlers(wd["apply_button"]) # Trigger it 33 | assert s.axes_manager[0].scale == 1 34 | assert s.axes_manager[0].offset == 0 35 | assert s.axes_manager[0].units == "nm" 36 | 37 | def test_calibrate_from_s(self): 38 | s = self.s 39 | wd = s.calibrate(**KWARGS)["ipywidgets"]["wdict"] 40 | wd["left"].value = 10 41 | wd["right"].value = 30 42 | wd["new_left"].value = 1 43 | wd["new_right"].value = 11 44 | wd["units"].value = "nm" 45 | assert wd["offset"].value == 1 46 | assert wd["scale"].value == 1 47 | wd["apply_button"]._click_handlers(wd["apply_button"]) # Trigger it 48 | assert s.axes_manager[0].scale == 1 49 | assert s.axes_manager[0].offset == 1 50 | assert s.axes_manager[0].units == "nm" 51 | 52 | def test_smooth_sg(self): 53 | s = self.s 54 | s.add_gaussian_noise(0.1) 55 | s2 = s.deepcopy() 56 | wd = s.smooth_savitzky_golay(**KWARGS)["ipywidgets"]["wdict"] 57 | wd["window_length"].value = 11 58 | wd["polynomial_order"].value = 5 59 | wd["differential_order"].value = 1 60 | wd["color"].value = "red" 61 | wd["apply_button"]._click_handlers(wd["apply_button"]) # Trigger it 62 | s2.smooth_savitzky_golay(polynomial_order=5, window_length=11, 63 | differential_order=1) 64 | np.testing.assert_allclose(s.data, s2.data) 65 | 66 | def test_smooth_lowess(self): 67 | s = self.s 68 | s.add_gaussian_noise(0.1) 69 | s2 = s.deepcopy() 70 | wd = s.smooth_lowess(**KWARGS)["ipywidgets"]["wdict"] 71 | wd["smoothing_parameter"].value = 0.9 72 | wd["number_of_iterations"].value = 3 73 | wd["color"].value = "red" 74 | wd["apply_button"]._click_handlers(wd["apply_button"]) # Trigger it 75 | s2.smooth_lowess(smoothing_parameter=0.9, number_of_iterations=3) 76 | np.testing.assert_allclose(s.data, s2.data) 77 | 78 | def test_smooth_tv(self): 79 | s = self.s 80 | s.add_gaussian_noise(0.1) 81 | s2 = s.deepcopy() 82 | wd = s.smooth_tv(**KWARGS)["ipywidgets"]["wdict"] 83 | wd["smoothing_parameter"].value = 300 84 | wd["color"].value = "red" 85 | wd["apply_button"]._click_handlers(wd["apply_button"]) # Trigger it 86 | s2.smooth_tv(smoothing_parameter=300) 87 | np.testing.assert_allclose(s.data, s2.data) 88 | 89 | def test_filter_butterworth(self): 90 | s = self.s 91 | s.add_gaussian_noise(0.1) 92 | s2 = s.deepcopy() 93 | wd = s.filter_butterworth(**KWARGS)["ipywidgets"]["wdict"] 94 | wd["cutoff"].value = 0.5 95 | wd["order"].value = 3 96 | wd["type"].value = "high" 97 | wd["color"].value = "red" 98 | wd["apply_button"]._click_handlers(wd["apply_button"]) # Trigger it 99 | s2.filter_butterworth( 100 | cutoff_frequency_ratio=0.5, 101 | order=3, 102 | type="high") 103 | np.testing.assert_allclose(s.data, s2.data) 104 | 105 | def test_remove_background(self): 106 | s = self.s 107 | s.add_gaussian_noise(0.1) 108 | s2 = s.remove_background( 109 | signal_range=(15., 50.), 110 | background_type='Polynomial', 111 | polynomial_order=2, 112 | fast=False, 113 | zero_fill=True) 114 | wd = s.remove_background(**KWARGS)["ipywidgets"]["wdict"] 115 | assert wd["polynomial_order"].layout.display == "none" # not visible 116 | wd["background_type"].value = "Polynomial" 117 | assert wd["polynomial_order"].layout.display == "" # visible 118 | wd["polynomial_order"].value = 2 119 | wd["fast"].value = False 120 | wd["zero_fill"] = True 121 | wd["left"].value = 15. 122 | wd["right"].value = 50. 123 | wd["apply_button"]._click_handlers(wd["apply_button"]) # Trigger it 124 | np.testing.assert_allclose(s.data[2:], s2.data[2:], atol=1E-5) 125 | np.testing.assert_allclose(np.zeros(2), s2.data[:2]) 126 | 127 | def test_constrast_editor(self): 128 | # To get this test to work, matplotlib backend needs to set to 'Agg' 129 | np.random.seed(1) 130 | im = hs.signals.Signal2D(np.random.random((32, 32))) 131 | im.plot() 132 | ceditor = ImageContrastEditor(im._plot.signal_plot) 133 | ceditor.ax.figure.canvas.draw_idle() 134 | wd = ceditor.gui(**KWARGS)["ipywidgets"]["wdict"] 135 | assert wd["linthresh"].layout.display == "none" # not visible 136 | assert wd["linscale"].layout.display == "none" # not visible 137 | assert wd["gamma"].layout.display == "none" # not visible 138 | wd["bins"].value = 50 139 | assert ceditor.bins == 50 140 | wd["norm"].value = 'Log' 141 | assert ceditor.norm == 'Log' 142 | assert wd["linthresh"].layout.display == "none" # not visible 143 | assert wd["linscale"].layout.display == "none" # not visible 144 | wd["norm"].value = 'Symlog' 145 | assert ceditor.norm == 'Symlog' 146 | assert wd["linthresh"].layout.display == "" # visible 147 | assert wd["linscale"].layout.display == "" # visible 148 | assert wd["linthresh"].value == 0.01 # default value 149 | assert wd["linscale"].value == 0.1 # default value 150 | 151 | wd["linthresh"].value = 0.1 152 | assert ceditor.linthresh == 0.1 153 | wd["linscale"].value = 0.2 154 | assert ceditor.linscale == 0.2 155 | 156 | 157 | wd["norm"].value = 'Linear' 158 | percentile = [1.0, 99.0] 159 | wd["percentile"].value = percentile 160 | assert ceditor.vmin_percentile == percentile[0] 161 | assert ceditor.vmax_percentile == percentile[1] 162 | assert im._plot.signal_plot.vmin == f'{percentile[0]}th' 163 | assert im._plot.signal_plot.vmax == f'{percentile[1]}th' 164 | 165 | wd["norm"].value = 'Power' 166 | assert ceditor.norm == 'Power' 167 | assert wd["gamma"].layout.display == "" # visible 168 | assert wd["gamma"].value == 1.0 # default value 169 | wd["gamma"].value = 0.1 170 | assert ceditor.gamma == 0.1 171 | 172 | assert wd["auto"].value is True # default value 173 | wd["auto"].value = False 174 | assert ceditor.auto is False 175 | 176 | wd["left"].value = 0.2 177 | assert ceditor.ss_left_value == 0.2 178 | wd["right"].value = 0.5 179 | assert ceditor.ss_right_value == 0.5 180 | # Setting the span selector programmatically from the widgets will 181 | # need to be implemented properly 182 | wd["apply_button"]._click_handlers(wd["apply_button"]) # Trigger it 183 | # assert im._plot.signal_plot.vmin == 0.2 184 | # assert im._plot.signal_plot.vmax == 0.5 185 | 186 | # Reset to default values 187 | wd["reset_button"]._click_handlers(wd["reset_button"]) # Trigger it 188 | assert im._plot.signal_plot.vmin == '0.0th' 189 | assert im._plot.signal_plot.vmax == '100.0th' 190 | 191 | def test_eels_table_tool(self): 192 | exspy = pytest.importorskip("exspy") 193 | s = exspy.data.EELS_MnFe(True) 194 | s.plot() 195 | try: 196 | # exspy API from 0.3 197 | # https://github.com/hyperspy/exspy/pull/59 198 | from exspy import _signal_tools 199 | except ImportError: 200 | from exspy import signal_tools as _signal_tools 201 | 202 | er = _signal_tools.EdgesRange(s) 203 | 204 | er.ss_left_value = 500 205 | er.ss_right_value = 550 206 | 207 | wd = er.gui(**KWARGS)["ipywidgets"]["wdict"] 208 | wd["update"]._click_handlers(wd["update"]) # refresh the table 209 | assert wd["units"].value == 'eV' 210 | assert wd["left"].value == 500 211 | assert wd["right"].value == 550 212 | assert len(wd['gb'].children) == 44 # 9 edges displayed 213 | 214 | wd['major'].value = True 215 | wd["update"]._click_handlers(wd["update"]) # refresh the table 216 | assert len(wd['gb'].children) == 24 # 6 edges displayed 217 | assert wd['gb'].children[4].description == 'Sb_M4' 218 | 219 | wd['order'].value = 'ascending' 220 | wd["update"]._click_handlers(wd["update"]) # refresh the table 221 | assert wd['gb'].children[4].description == 'V_L3' 222 | 223 | wd["reset"]._click_handlers(wd["reset"]) # reset the selector 224 | assert len(wd['gb'].children) == 4 # only header displayed 225 | 226 | 227 | def test_calibration_2d(): 228 | s = hs.signals.Signal2D(np.zeros((100, 100))) 229 | cal2d = Signal2DCalibration(s) 230 | wd = cal2d.gui(**KWARGS)["ipywidgets"]["wdict"] 231 | cal2d.x0, cal2d.x1, cal2d.y0, cal2d.y1 = 50, 70, 80, 80 232 | wd["new_length"].value = 10 233 | wd["units"].value = "mm" 234 | wd["apply_button"]._click_handlers(wd["apply_button"]) 235 | assert s.axes_manager[0].scale == 0.5 236 | assert s.axes_manager[1].scale == 0.5 237 | assert s.axes_manager[0].units == "mm" 238 | assert s.axes_manager[1].units == "mm" 239 | 240 | 241 | def test_spikes_removal_tool(): 242 | s = hs.signals.Signal1D(np.ones((2, 3, 30))) 243 | s.add_gaussian_noise(std=1, random_state=0) 244 | 245 | # The maximum value that we expect after removing a spikes 246 | max_value_after_spike_removal = 10 247 | 248 | # Add three spikes 249 | s.data[1, 0, 1] += 40 250 | s.data[0, 2, 29] += 20 251 | s.data[1, 2, 14] += 100 252 | wd = s.spikes_removal_tool(**KWARGS)["ipywidgets"]["wdict"] 253 | 254 | def next(): 255 | wd["next_button"]._click_handlers(wd["next_button"]) 256 | 257 | def previous(): 258 | wd["previous_button"]._click_handlers(wd["previous_button"]) 259 | 260 | def remove(): 261 | wd["remove_button"]._click_handlers(wd["remove_button"]) 262 | wd["threshold"].value = 25 263 | next() 264 | assert s.axes_manager.indices == (0, 1) 265 | wd["threshold"].value = 15 266 | assert s.axes_manager.indices == (0, 0) 267 | next() 268 | assert s.axes_manager.indices == (2, 0) 269 | next() 270 | assert s.axes_manager.indices == (0, 1) 271 | previous() 272 | assert s.axes_manager.indices == (2, 0) 273 | wd["add_noise"].value = False 274 | remove() 275 | assert s.data[0, 2, 29] < max_value_after_spike_removal 276 | assert s.axes_manager.indices == (0, 1) 277 | remove() 278 | assert s.data[1, 0, 1] < max_value_after_spike_removal 279 | assert s.axes_manager.indices == (2, 1) 280 | np.random.seed(1) 281 | wd["add_noise"].value = True 282 | wd["spline_order"].value = 1 283 | remove() 284 | assert s.data[1, 2, 14] < max_value_after_spike_removal 285 | # After going through the whole dataset, come back to (0, 0) position 286 | assert s.axes_manager.indices == (0, 0) 287 | 288 | 289 | def test_remove_baseline(): 290 | pytest.importorskip("pybaselines") 291 | s = hs.data.two_gaussians().inav[:5, :5] 292 | s.plot() 293 | 294 | br = BaselineRemoval(s) 295 | wd = br.gui(**KWARGS)["ipywidgets"]["wdict"] 296 | br.algorithm = "Asymmetric Least Squares" 297 | assert wd["algorithm"].value == "Asymmetric Least Squares" 298 | br.algorithm = "Adaptive Smoothness Penalized Least Squares" 299 | br.lam = 1e7 300 | assert wd["lam"].value == 1e7 301 | br.apply() 302 | assert s.isig[:10].data.mean() < 5 303 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tests/utils.py: -------------------------------------------------------------------------------- 1 | 2 | KWARGS = { 3 | "toolkit": "ipywidgets", 4 | "display": False, 5 | } 6 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/tools.py: -------------------------------------------------------------------------------- 1 | import ipywidgets 2 | import traits.api as t 3 | 4 | from link_traits import link 5 | from hyperspy.signal_tools import ( 6 | SPIKES_REMOVAL_INSTRUCTIONS, 7 | IMAGE_CONTRAST_EDITOR_HELP_IPYWIDGETS 8 | ) 9 | from hyperspy.utils.baseline_removal_tool import PARAMETERS_ALGORITHMS 10 | 11 | from hyperspy_gui_ipywidgets.utils import (labelme, enum2dropdown, 12 | add_display_arg, set_title_container) 13 | from hyperspy_gui_ipywidgets.custom_widgets import OddIntSlider 14 | from hyperspy_gui_ipywidgets.axes import get_ipy_navigation_sliders 15 | 16 | 17 | @add_display_arg 18 | def interactive_range_ipy(obj, **kwargs): 19 | # Define widgets 20 | wdict = {} 21 | axis = obj.axis 22 | left = ipywidgets.FloatText(disabled=True) 23 | right = ipywidgets.FloatText(disabled=True) 24 | units = ipywidgets.Label() 25 | help_text = ipywidgets.HTML( 26 | "Click on the signal figure and drag to the right to select a signal " 27 | "range. Press `Apply` to perform the operation or `Close` to cancel.",) 28 | help = ipywidgets.Accordion(children=[help_text], selected_index=None) 29 | set_title_container(help, ["Help"]) 30 | close = ipywidgets.Button( 31 | description="Close", 32 | tooltip="Close widget and remove span selector from the signal figure.") 33 | apply = ipywidgets.Button( 34 | description="Apply", 35 | tooltip="Perform the operation using the selected range.") 36 | wdict["left"] = left 37 | wdict["right"] = right 38 | wdict["units"] = units 39 | wdict["help_text"] = help_text 40 | wdict["close_button"] = close 41 | wdict["apply_button"] = apply 42 | 43 | # Connect 44 | link((obj, "ss_left_value"), (left, "value")) 45 | link((obj, "ss_right_value"), (right, "value")) 46 | link((axis, "units"), (units, "value")) 47 | 48 | def on_apply_clicked(b): 49 | if obj.ss_left_value != obj.ss_right_value: 50 | obj.span_selector_switch(False) 51 | for method, cls in obj.on_close: 52 | method(cls, obj.ss_left_value, obj.ss_right_value) 53 | obj.span_selector_switch(True) 54 | apply.on_click(on_apply_clicked) 55 | 56 | box = ipywidgets.VBox([ 57 | ipywidgets.HBox([left, units, ipywidgets.Label("-"), right, units]), 58 | help, 59 | ipywidgets.HBox((apply, close)) 60 | ]) 61 | 62 | def on_close_clicked(b): 63 | obj.span_selector_switch(False) 64 | box.close() 65 | close.on_click(on_close_clicked) 66 | return { 67 | "widget": box, 68 | "wdict": wdict, 69 | } 70 | 71 | 72 | @add_display_arg 73 | def calibrate2d_ipy(obj, **kwargs): 74 | # Define widgets 75 | wdict = {} 76 | length = ipywidgets.FloatText(disabled=True, description="Current length") 77 | scale = ipywidgets.FloatText(disabled=True, description="Scale") 78 | new_length = ipywidgets.FloatText(disabled=False, description="New length") 79 | units = ipywidgets.Text(description="Units") 80 | unitsl = ipywidgets.Label(layout=ipywidgets.Layout(width="10%")) 81 | help_text = ipywidgets.HTML( 82 | "Click on the signal figure and drag line to some feature with a " 83 | "known size. Set the new length, then press `Apply` to update both " 84 | "the x- and y-dimensions in the signal, or press `Close` to cancel. " 85 | "The units can also be set with `Units`" 86 | ) 87 | wdict["help_text"] = help_text 88 | help = ipywidgets.Accordion(children=[help_text], selected_index=None) 89 | set_title_container(help, ["Help"]) 90 | close = ipywidgets.Button( 91 | description="Close", 92 | tooltip="Close widget and remove line from the signal figure.", 93 | ) 94 | apply = ipywidgets.Button( 95 | description="Apply", tooltip="Set the x- and y-scaling with the `scale` value." 96 | ) 97 | 98 | # Connect 99 | link((obj, "length"), (length, "value")) 100 | link((obj, "new_length"), (new_length, "value")) 101 | link((obj, "units"), (units, "value")) 102 | link((obj, "units"), (unitsl, "value")) 103 | link((obj, "scale"), (scale, "value")) 104 | 105 | def on_apply_clicked(b): 106 | obj.apply() 107 | obj.on = False 108 | box.close() 109 | 110 | apply.on_click(on_apply_clicked) 111 | 112 | box = ipywidgets.VBox( 113 | [ 114 | ipywidgets.HBox([new_length, unitsl]), 115 | length, 116 | scale, 117 | units, 118 | help, 119 | ipywidgets.HBox((apply, close)), 120 | ] 121 | ) 122 | 123 | def on_close_clicked(b): 124 | obj.on = False 125 | box.close() 126 | 127 | close.on_click(on_close_clicked) 128 | 129 | wdict["length"] = length 130 | wdict["scale"] = scale 131 | wdict["new_length"] = new_length 132 | wdict["units"] = units 133 | wdict["close_button"] = close 134 | wdict["apply_button"] = apply 135 | 136 | return { 137 | "widget": box, 138 | "wdict": wdict, 139 | } 140 | 141 | 142 | @add_display_arg 143 | def calibrate_ipy(obj, **kwargs): 144 | # Define widgets 145 | wdict = {} 146 | left = ipywidgets.FloatText(disabled=True, description="Left") 147 | right = ipywidgets.FloatText(disabled=True, description="Right") 148 | offset = ipywidgets.FloatText(disabled=True, description="Offset") 149 | scale = ipywidgets.FloatText(disabled=True, description="Scale") 150 | new_left = ipywidgets.FloatText(disabled=False, description="New left") 151 | new_right = ipywidgets.FloatText(disabled=False, description="New right") 152 | units = ipywidgets.Text(description="Units") 153 | unitsl = ipywidgets.Label(layout=ipywidgets.Layout(width="10%")) 154 | help_text = ipywidgets.HTML( 155 | "Click on the signal figure and drag to the right to select a signal " 156 | "range. Set the new left and right values and press `Apply` to update " 157 | "the calibration of the axis with the new values or press " 158 | " `Close` to cancel.",) 159 | wdict["help_text"] = help_text 160 | help = ipywidgets.Accordion(children=[help_text], selected_index=None) 161 | set_title_container(help, ["Help"]) 162 | close = ipywidgets.Button( 163 | description="Close", 164 | tooltip="Close widget and remove span selector from the signal figure.", 165 | ) 166 | apply = ipywidgets.Button( 167 | description="Apply", 168 | tooltip="Set the axis calibration with the `offset` and `scale` values above.", 169 | ) 170 | 171 | # Connect 172 | link((obj, "ss_left_value"), (left, "value")) 173 | link((obj, "ss_right_value"), (right, "value")) 174 | link((obj, "left_value"), (new_left, "value")) 175 | link((obj, "right_value"), (new_right, "value")) 176 | link((obj, "units"), (units, "value")) 177 | link((obj, "units"), (unitsl, "value")) 178 | link((obj, "offset"), (offset, "value")) 179 | link((obj, "scale"), (scale, "value")) 180 | 181 | def on_apply_clicked(b): 182 | if (new_left.value, new_right.value) != (0, 0): 183 | # traitlets does not support undefined, therefore we need to makes 184 | # sure that the values are updated in the obj if they make sense 185 | if new_left.value == 0 and obj.left_value is t.Undefined: 186 | obj.left_value = 0 187 | elif new_right.value == 0 and obj.right_value is t.Undefined: 188 | obj.right_value = 0 189 | # This is the default value, we need to update 190 | obj.apply() 191 | 192 | apply.on_click(on_apply_clicked) 193 | 194 | box = ipywidgets.VBox( 195 | [ 196 | ipywidgets.HBox([new_left, unitsl]), 197 | ipywidgets.HBox([new_right, unitsl]), 198 | ipywidgets.HBox([left, unitsl]), 199 | ipywidgets.HBox([right, unitsl]), 200 | ipywidgets.HBox([offset, unitsl]), 201 | scale, 202 | units, 203 | help, 204 | ipywidgets.HBox((apply, close)), 205 | ] 206 | ) 207 | 208 | def on_close_clicked(b): 209 | obj.span_selector_switch(False) 210 | box.close() 211 | 212 | close.on_click(on_close_clicked) 213 | 214 | wdict["left"] = left 215 | wdict["right"] = right 216 | wdict["offset"] = offset 217 | wdict["scale"] = scale 218 | wdict["new_left"] = new_left 219 | wdict["new_right"] = new_right 220 | wdict["units"] = units 221 | wdict["close_button"] = close 222 | wdict["apply_button"] = apply 223 | 224 | return { 225 | "widget": box, 226 | "wdict": wdict, 227 | } 228 | 229 | 230 | @add_display_arg 231 | def print_edges_table_ipy(obj, **kwargs): 232 | # Define widgets 233 | wdict = {} 234 | axis = obj.axis 235 | style_d = {'description_width': 'initial'} 236 | layout_d = {'width': '50%'} 237 | left = ipywidgets.FloatText(disabled=True, layout={'width': '25%'}) 238 | right = ipywidgets.FloatText(disabled=True, layout={'width': '25%'}) 239 | units = ipywidgets.Label(style=style_d) 240 | major = ipywidgets.Checkbox(value=False, description='Only major edge', 241 | indent=False, layout=layout_d) 242 | complmt = ipywidgets.Checkbox(value=False, description='Complementary edge', 243 | indent=False, layout=layout_d) 244 | order = ipywidgets.Dropdown(options=['closest', 'ascending', 'descending'], 245 | value='closest', 246 | description='Sort energy by: ', 247 | disabled=False, 248 | style=style_d 249 | ) 250 | update = ipywidgets.Button(description='Refresh table', layout={'width': 'initial'}) 251 | gb = ipywidgets.GridBox(layout=ipywidgets.Layout( 252 | grid_template_columns="70px 125px 75px 250px")) 253 | help_text = ipywidgets.HTML( 254 | "Click on the signal figure and drag to the right to select a signal " 255 | "range. Drag the rectangle or change its border to display edges in " 256 | "different signal range. Select edges to show their positions " 257 | "on the signal.",) 258 | help = ipywidgets.Accordion(children=[help_text], selected_index=None) 259 | set_title_container(help, ["Help"]) 260 | close = ipywidgets.Button(description="Close", tooltip="Close the widget.") 261 | reset = ipywidgets.Button(description="Reset", 262 | tooltip="Reset the span selector.") 263 | 264 | header = ('

{}

') 267 | entry = ('

{}

') 269 | 270 | wdict["left"] = left 271 | wdict["right"] = right 272 | wdict["units"] = units 273 | wdict["help"] = help 274 | wdict["major"] = major 275 | wdict["update"] = update 276 | wdict["complmt"] = complmt 277 | wdict["order"] = order 278 | wdict["gb"] = gb 279 | wdict["reset"] = reset 280 | wdict["close"] = close 281 | 282 | # Connect 283 | link((obj, "ss_left_value"), (left, "value")) 284 | link((obj, "ss_right_value"), (right, "value")) 285 | link((axis, "units"), (units, "value")) 286 | link((obj, "only_major"), (major, "value")) 287 | link((obj, "complementary"), (complmt, "value")) 288 | link((obj, "order"), (order, "value")) 289 | 290 | def update_table(change): 291 | edges, energy, relevance, description = obj.update_table() 292 | 293 | # header 294 | items = [ipywidgets.HTML(header.format('edge')), 295 | ipywidgets.HTML(header.format('onset energy (eV)')), 296 | ipywidgets.HTML(header.format('relevance')), 297 | ipywidgets.HTML(header.format('description'))] 298 | 299 | # rows 300 | obj.btns = [] 301 | for k, edge in enumerate(edges): 302 | if edge in obj.active_edges or \ 303 | edge in obj.active_complementary_edges: 304 | btn_state = True 305 | else: 306 | btn_state = False 307 | 308 | btn = ipywidgets.ToggleButton(value=btn_state, 309 | description=edge, 310 | layout=ipywidgets.Layout(width='70px')) 311 | btn.observe(obj.update_active_edge, names='value') 312 | obj.btns.append(btn) 313 | 314 | wenergy = ipywidgets.HTML(entry.format(str(energy[k]))) 315 | wrelv = ipywidgets.HTML(entry.format(str(relevance[k]))) 316 | wdes = ipywidgets.HTML(entry.format(str(description[k]))) 317 | items.extend([btn, wenergy, wrelv, wdes]) 318 | 319 | gb.children = items 320 | update.on_click(update_table) 321 | major.observe(update_table) 322 | 323 | def on_complementary_toggled(change): 324 | obj.update_table() 325 | obj.check_btn_state() 326 | complmt.observe(on_complementary_toggled) 327 | 328 | def on_order_changed(change): 329 | obj._get_edges_info_within_energy_axis() 330 | update_table(change) 331 | order.observe(on_order_changed) 332 | 333 | def on_close_clicked(b): 334 | obj.span_selector_switch(False) 335 | box.close() 336 | close.on_click(on_close_clicked) 337 | 338 | def on_reset_clicked(b): 339 | # ss_left_value is linked with left.value, this can prevent cyclic 340 | # referencing 341 | obj._clear_markers() 342 | obj.span_selector_switch(False) 343 | left.value = 0 344 | right.value = 0 345 | obj.span_selector_switch(True) 346 | update_table(b) 347 | reset.on_click(on_reset_clicked) 348 | 349 | energy_box = ipywidgets.HBox([left, units, ipywidgets.Label("-"), right, 350 | units]) 351 | check_box = ipywidgets.HBox([major, complmt]) 352 | control_box = ipywidgets.VBox([energy_box, update, order, check_box]) 353 | 354 | box = ipywidgets.VBox([ 355 | ipywidgets.HBox([gb, control_box]), 356 | help, 357 | ipywidgets.HBox([reset, close]), 358 | ]) 359 | 360 | return { 361 | "widget": box, 362 | "wdict": wdict, 363 | } 364 | 365 | @add_display_arg 366 | def smooth_savitzky_golay_ipy(obj, **kwargs): 367 | wdict = {} 368 | window_length = OddIntSlider( 369 | value=3, step=2, min=3, max=max(int(obj.axis.size * 0.25), 3)) 370 | polynomial_order = ipywidgets.IntSlider(value=3, min=1, 371 | max=window_length.value - 1) 372 | # Polynomial order must be less than window length 373 | 374 | def update_bound(change): 375 | polynomial_order.max = change.new - 1 376 | window_length.observe(update_bound, "value") 377 | differential_order = ipywidgets.IntSlider(value=0, min=0, max=10) 378 | color = ipywidgets.ColorPicker() 379 | close = ipywidgets.Button( 380 | description="Close", 381 | tooltip="Close widget and remove the smoothed line from the signal figure.") 382 | apply = ipywidgets.Button( 383 | description="Apply", 384 | tooltip="Perform the operation using the selected range.") 385 | link((obj, "polynomial_order"), (polynomial_order, "value")) 386 | link((obj, "window_length"), (window_length, "value")) 387 | link((obj, "differential_order"), 388 | (differential_order, "value")) 389 | # Differential order must be less or equal to polynomial_order 390 | link((polynomial_order, "value"), 391 | (differential_order, "max")) 392 | link((obj, "line_color_ipy"), (color, "value")) 393 | box = ipywidgets.VBox([ 394 | labelme("Window length", window_length), 395 | labelme("polynomial order", polynomial_order), 396 | labelme("Differential order", differential_order), 397 | labelme("Color", color), 398 | ipywidgets.HBox((apply, close)) 399 | ]) 400 | 401 | wdict["window_length"] = window_length 402 | wdict["polynomial_order"] = polynomial_order 403 | wdict["differential_order"] = differential_order 404 | wdict["color"] = color 405 | wdict["close_button"] = close 406 | wdict["apply_button"] = apply 407 | 408 | def on_apply_clicked(b): 409 | obj.apply() 410 | apply.on_click(on_apply_clicked) 411 | 412 | def on_close_clicked(b): 413 | obj.close() 414 | box.close() 415 | close.on_click(on_close_clicked) 416 | return { 417 | "widget": box, 418 | "wdict": wdict, 419 | } 420 | 421 | 422 | @add_display_arg 423 | def smooth_lowess_ipy(obj, **kwargs): 424 | wdict = {} 425 | smoothing_parameter = ipywidgets.FloatSlider(min=0, max=1) 426 | number_of_iterations = ipywidgets.IntText() 427 | color = ipywidgets.ColorPicker() 428 | close = ipywidgets.Button( 429 | description="Close", 430 | tooltip="Close widget and remove the smoothed line from the signal figure.") 431 | apply = ipywidgets.Button( 432 | description="Apply", 433 | tooltip="Perform the operation using the selected range.") 434 | link((obj, "smoothing_parameter"), 435 | (smoothing_parameter, "value")) 436 | link((obj, "number_of_iterations"), 437 | (number_of_iterations, "value")) 438 | link((obj, "line_color_ipy"), (color, "value")) 439 | box = ipywidgets.VBox([ 440 | labelme("Smoothing parameter", smoothing_parameter), 441 | labelme("Number of iterations", number_of_iterations), 442 | labelme("Color", color), 443 | ipywidgets.HBox((apply, close)) 444 | ]) 445 | wdict["smoothing_parameter"] = smoothing_parameter 446 | wdict["number_of_iterations"] = number_of_iterations 447 | wdict["color"] = color 448 | wdict["close_button"] = close 449 | wdict["apply_button"] = apply 450 | 451 | def on_apply_clicked(b): 452 | obj.apply() 453 | apply.on_click(on_apply_clicked) 454 | 455 | def on_close_clicked(b): 456 | obj.close() 457 | box.close() 458 | close.on_click(on_close_clicked) 459 | return { 460 | "widget": box, 461 | "wdict": wdict, 462 | } 463 | 464 | 465 | @add_display_arg 466 | def smooth_tv_ipy(obj, **kwargs): 467 | wdict = {} 468 | smoothing_parameter = ipywidgets.FloatSlider(min=0.1, max=1000) 469 | smoothing_parameter_max = ipywidgets.FloatText( 470 | value=smoothing_parameter.max) 471 | color = ipywidgets.ColorPicker() 472 | close = ipywidgets.Button( 473 | description="Close", 474 | tooltip="Close widget and remove the smoothed line from the signal figure.") 475 | apply = ipywidgets.Button( 476 | description="Apply", 477 | tooltip="Perform the operation using the selected range.") 478 | link((obj, "smoothing_parameter"), 479 | (smoothing_parameter, "value")) 480 | link((smoothing_parameter_max, "value"), 481 | (smoothing_parameter, "max")) 482 | link((obj, "line_color_ipy"), (color, "value")) 483 | wdict["smoothing_parameter"] = smoothing_parameter 484 | wdict["smoothing_parameter_max"] = smoothing_parameter_max 485 | wdict["color"] = color 486 | wdict["close_button"] = close 487 | wdict["apply_button"] = apply 488 | box = ipywidgets.VBox([ 489 | labelme("Weight", smoothing_parameter), 490 | labelme("Weight max", smoothing_parameter_max), 491 | labelme("Color", color), 492 | ipywidgets.HBox((apply, close)) 493 | ]) 494 | 495 | def on_apply_clicked(b): 496 | obj.apply() 497 | apply.on_click(on_apply_clicked) 498 | 499 | def on_close_clicked(b): 500 | obj.close() 501 | box.close() 502 | close.on_click(on_close_clicked) 503 | return { 504 | "widget": box, 505 | "wdict": wdict, 506 | } 507 | 508 | 509 | @add_display_arg 510 | def smooth_butterworth(obj, **kwargs): 511 | wdict = {} 512 | cutoff = ipywidgets.FloatSlider(min=0.01, max=1.) 513 | order = ipywidgets.IntText() 514 | type_ = ipywidgets.Dropdown(options=("low", "high")) 515 | color = ipywidgets.ColorPicker() 516 | close = ipywidgets.Button( 517 | description="Close", 518 | tooltip="Close widget and remove the smoothed line from the signal figure.") 519 | apply = ipywidgets.Button( 520 | description="Apply", 521 | tooltip="Perform the operation using the selected range.") 522 | link((obj, "cutoff_frequency_ratio"), (cutoff, "value")) 523 | link((obj, "type"), (type_, "value")) 524 | link((obj, "order"), (order, "value")) 525 | wdict["cutoff"] = cutoff 526 | wdict["order"] = order 527 | wdict["type"] = type_ 528 | wdict["color"] = color 529 | wdict["close_button"] = close 530 | wdict["apply_button"] = apply 531 | box = ipywidgets.VBox([ 532 | labelme("Cutoff frequency ration", cutoff), 533 | labelme("Type", type_), 534 | labelme("Order", order), 535 | ipywidgets.HBox((apply, close)) 536 | ]) 537 | 538 | def on_apply_clicked(b): 539 | obj.apply() 540 | apply.on_click(on_apply_clicked) 541 | 542 | def on_close_clicked(b): 543 | obj.close() 544 | box.close() 545 | close.on_click(on_close_clicked) 546 | return { 547 | "widget": box, 548 | "wdict": wdict, 549 | } 550 | 551 | @add_display_arg 552 | def image_constast_editor_ipy(obj, **kwargs): 553 | wdict = {} 554 | left = ipywidgets.FloatText(disabled=True, description="Vmin") 555 | right = ipywidgets.FloatText(disabled=True, description="Vmax") 556 | bins = ipywidgets.IntText(description="Bins") 557 | norm = ipywidgets.Dropdown(options=("Linear", "Power", "Log", "Symlog"), 558 | description="Norm", 559 | value=obj.norm) 560 | percentile = ipywidgets.FloatRangeSlider(value=[0.0, 100.0], 561 | min=0.0, max=100.0, step=0.1, 562 | description="Vmin/vmax percentile", 563 | readout_format='.1f') 564 | gamma = ipywidgets.FloatSlider(1.0, min=0.1, max=3.0, description="Gamma") 565 | linthresh = ipywidgets.FloatSlider(0.01, min=0.001, max=1.0, step=0.001, 566 | description="Linear threshold") 567 | linscale = ipywidgets.FloatSlider(0.1, min=0.001, max=10.0, step=0.001, 568 | description="Linear scale") 569 | auto = ipywidgets.Checkbox(True, description="Auto") 570 | help_text = ipywidgets.HTML(IMAGE_CONTRAST_EDITOR_HELP_IPYWIDGETS) 571 | wdict["help_text"] = help_text 572 | help = ipywidgets.Accordion(children=[help_text], selected_index=None) 573 | set_title_container(help, ["Help"]) 574 | close = ipywidgets.Button( 575 | description="Close", 576 | tooltip="Close widget.") 577 | apply = ipywidgets.Button( 578 | description="Apply", 579 | tooltip="Use the selected range to re-calculate the histogram.") 580 | reset = ipywidgets.Button( 581 | description="Reset", 582 | tooltip="Reset the settings to their initial values.") 583 | wdict["left"] = left 584 | wdict["right"] = right 585 | wdict["bins"] = bins 586 | wdict["norm"] = norm 587 | wdict["percentile"] = percentile 588 | wdict["gamma"] = gamma 589 | wdict["linthresh"] = linthresh 590 | wdict["linscale"] = linscale 591 | wdict["auto"] = auto 592 | wdict["close_button"] = close 593 | wdict["apply_button"] = apply 594 | wdict["reset_button"] = reset 595 | 596 | def transform_vmin(value): 597 | return (value, percentile.upper) 598 | 599 | def transform_vmin_inv(value): 600 | return value[0] 601 | 602 | def transform_vmax(value): 603 | return (percentile.lower, value) 604 | 605 | def transform_vmax_inv(value): 606 | return value[1] 607 | 608 | # Connect 609 | link((obj, "ss_left_value"), (left, "value")) 610 | link((obj, "ss_right_value"), (right, "value")) 611 | link((obj, "bins"), (bins, "value")) 612 | link((obj, "norm"), (norm, "value")) 613 | link((obj, "vmin_percentile"), (percentile, "value"), 614 | (transform_vmin, transform_vmin_inv)) 615 | link((obj, "vmax_percentile"), (percentile, "value"), 616 | (transform_vmax, transform_vmax_inv)) 617 | link((obj, "gamma"), (gamma, "value")) 618 | link((obj, "linthresh"), (linthresh, "value")) 619 | link((obj, "linscale"), (linscale, "value")) 620 | link((obj, "auto"), (auto, "value")) 621 | 622 | def display_parameters(change): 623 | # Necessary for the initialisation 624 | v = change if isinstance(change, str) else change.new 625 | if v == "Symlog": 626 | linthresh.layout.display = "" 627 | linscale.layout.display = "" 628 | else: 629 | linthresh.layout.display = "none" 630 | linscale.layout.display = "none" 631 | if v == "Power": 632 | gamma.layout.display = "" 633 | else: 634 | gamma.layout.display = "none" 635 | display_parameters(obj.norm) 636 | norm.observe(display_parameters, "value") 637 | 638 | def disable_parameters(change): 639 | # Necessary for the initialisation 640 | v = change if isinstance(change, bool) else change.new 641 | percentile.disabled = not v 642 | 643 | disable_parameters(obj.auto) 644 | auto.observe(disable_parameters, "value") 645 | 646 | def on_apply_clicked(b): 647 | obj.apply() 648 | apply.on_click(on_apply_clicked) 649 | 650 | def on_reset_clicked(b): 651 | obj.reset() 652 | reset.on_click(on_reset_clicked) 653 | 654 | box = ipywidgets.VBox([left, 655 | right, 656 | auto, 657 | percentile, 658 | bins, 659 | norm, 660 | gamma, 661 | linthresh, 662 | linscale, 663 | help, 664 | ipywidgets.HBox((apply, reset, close)), 665 | ]) 666 | 667 | def on_close_clicked(b): 668 | obj.close() 669 | box.close() 670 | close.on_click(on_close_clicked) 671 | return { 672 | "widget": box, 673 | "wdict": wdict, 674 | } 675 | 676 | 677 | @add_display_arg 678 | def remove_background_ipy(obj, **kwargs): 679 | wdict = {} 680 | left = ipywidgets.FloatText(disabled=True, description="Left") 681 | right = ipywidgets.FloatText(disabled=True, description="Right") 682 | red_chisq = ipywidgets.FloatText(disabled=True, description="red-χ²") 683 | link((obj, "ss_left_value"), (left, "value")) 684 | link((obj, "ss_right_value"), (right, "value")) 685 | link((obj, "red_chisq"), (red_chisq, "value")) 686 | fast = ipywidgets.Checkbox(description="Fast") 687 | zero_fill = ipywidgets.Checkbox(description="Zero Fill") 688 | help_text = ipywidgets.HTML( 689 | "Click on the signal figure and drag to the right to select a " 690 | "range. Press `Apply` to remove the background in the whole dataset. " 691 | "If \"Fast\" is checked, the background parameters are estimated " 692 | "using a fast (analytical) method that can compromise accuracy. " 693 | "When unchecked, non-linear least squares is employed instead. " 694 | "If \"Zero Fill\" is checked, all the channels prior to the fitting " 695 | "region will be set to zero. " 696 | "Otherwise the background subtraction will be performed in the " 697 | "pre-fitting region as well.",) 698 | wdict["help_text"] = help_text 699 | help = ipywidgets.Accordion(children=[help_text], selected_index=None) 700 | set_title_container(help, ["Help"]) 701 | close = ipywidgets.Button( 702 | description="Close", 703 | tooltip="Close widget and remove span selector from the signal figure.") 704 | apply = ipywidgets.Button( 705 | description="Apply", 706 | tooltip="Remove the background in the whole dataset.") 707 | 708 | polynomial_order = ipywidgets.IntText(description="Polynomial order") 709 | background_type = enum2dropdown(obj.traits()["background_type"]) 710 | background_type.description = "Background type" 711 | 712 | def enable_poly_order(change): 713 | if change.new == "Polynomial": 714 | polynomial_order.layout.display = "" 715 | else: 716 | polynomial_order.layout.display = "none" 717 | background_type.observe(enable_poly_order, "value") 718 | link((obj, "background_type"), (background_type, "value")) 719 | # Trigger the function that controls the visibility of poly order as 720 | # setting the default value doesn't trigger it. 721 | 722 | class Dummy: 723 | new = background_type.value 724 | enable_poly_order(change=Dummy()) 725 | link((obj, "polynomial_order"), (polynomial_order, "value")) 726 | link((obj, "fast"), (fast, "value")) 727 | link((obj, "zero_fill"), (zero_fill, "value")) 728 | wdict["left"] = left 729 | wdict["right"] = right 730 | wdict["red_chisq"] = red_chisq 731 | wdict["fast"] = fast 732 | wdict["zero_fill"] = zero_fill 733 | wdict["polynomial_order"] = polynomial_order 734 | wdict["background_type"] = background_type 735 | wdict["apply_button"] = apply 736 | box = ipywidgets.VBox([ 737 | left, right, red_chisq, 738 | background_type, 739 | polynomial_order, 740 | fast, 741 | zero_fill, 742 | help, 743 | ipywidgets.HBox((apply, close)), 744 | ]) 745 | 746 | def on_apply_clicked(b): 747 | obj.apply() 748 | obj.span_selector_switch(False) 749 | box.close() 750 | apply.on_click(on_apply_clicked) 751 | 752 | def on_close_clicked(b): 753 | obj.span_selector_switch(False) 754 | box.close() 755 | close.on_click(on_close_clicked) 756 | return { 757 | "widget": box, 758 | "wdict": wdict, 759 | } 760 | 761 | 762 | @add_display_arg 763 | def spikes_removal_ipy(obj, **kwargs): 764 | wdict = {} 765 | threshold = ipywidgets.FloatText() 766 | add_noise = ipywidgets.Checkbox() 767 | default_spike_width = ipywidgets.IntText() 768 | spline_order = ipywidgets.IntSlider(min=1, max=10) 769 | progress_bar = ipywidgets.IntProgress(max=len(obj.coordinates) - 1) 770 | help_text = ipywidgets.HTML( 771 | value=SPIKES_REMOVAL_INSTRUCTIONS.replace('\n', '
')) 772 | help = ipywidgets.Accordion(children=[help_text], selected_index=None) 773 | set_title_container(help, ["Help"]) 774 | 775 | show_diff = ipywidgets.Button( 776 | description="Show derivative histogram", 777 | tooltip="This figure is useful to estimate the threshold.", 778 | layout=ipywidgets.Layout(width="auto")) 779 | close = ipywidgets.Button( 780 | description="Close", 781 | tooltip="Close widget and remove span selector from the signal figure.") 782 | next = ipywidgets.Button( 783 | description="Find next", 784 | tooltip="Find next spike") 785 | previous = ipywidgets.Button( 786 | description="Find previous", 787 | tooltip="Find previous spike") 788 | remove = ipywidgets.Button( 789 | description="Remove spike", 790 | tooltip="Remove spike and find next one.") 791 | wdict["threshold"] = threshold 792 | wdict["add_noise"] = add_noise 793 | wdict["default_spike_width"] = default_spike_width 794 | wdict["spline_order"] = spline_order 795 | wdict["progress_bar"] = progress_bar 796 | wdict["show_diff_button"] = show_diff 797 | wdict["close_button"] = close 798 | wdict["next_button"] = next 799 | wdict["previous_button"] = previous 800 | wdict["remove_button"] = remove 801 | 802 | def on_show_diff_clicked(b): 803 | obj._show_derivative_histogram_fired() 804 | show_diff.on_click(on_show_diff_clicked) 805 | 806 | def on_next_clicked(b): 807 | obj.find() 808 | next.on_click(on_next_clicked) 809 | 810 | def on_previous_clicked(b): 811 | obj.find(back=True) 812 | previous.on_click(on_previous_clicked) 813 | 814 | def on_remove_clicked(b): 815 | obj.apply() 816 | remove.on_click(on_remove_clicked) 817 | labeled_spline_order = labelme("Spline order", spline_order) 818 | 819 | link((obj, "threshold"), (threshold, "value")) 820 | link((obj, "add_noise"), (add_noise, "value")) 821 | link((obj, "default_spike_width"), 822 | (default_spike_width, "value")) 823 | link((obj, "spline_order"), (spline_order, "value")) 824 | link((obj, "index"), (progress_bar, "value")) 825 | # Trigger the function that controls the visibility as 826 | # setting the default value doesn't trigger it. 827 | 828 | advanced = ipywidgets.Accordion(( 829 | ipywidgets.VBox([ 830 | labelme("Add noise", add_noise), 831 | labelme("Default spike width", default_spike_width), 832 | labelme("Spline order", spline_order), ]),)) 833 | 834 | set_title_container(advanced, ["Advanced settings"]) 835 | 836 | box = ipywidgets.VBox([ 837 | ipywidgets.VBox([ 838 | show_diff, 839 | labelme("Threshold", threshold), 840 | labelme("Progress", progress_bar), ]), 841 | advanced, 842 | help, 843 | ipywidgets.HBox([previous, next, remove, close]) 844 | ]) 845 | 846 | def on_close_clicked(b): 847 | obj.span_selector_switch(False) 848 | box.close() 849 | close.on_click(on_close_clicked) 850 | return { 851 | "widget": box, 852 | "wdict": wdict, 853 | } 854 | 855 | 856 | @add_display_arg 857 | def find_peaks2D_ipy(obj, **kwargs): 858 | wdict = {} 859 | # Define widgets 860 | # For "local max" method 861 | local_max_distance = ipywidgets.IntSlider(min=1, max=20, value=3) 862 | local_max_threshold = ipywidgets.FloatSlider(min=0, max=20, value=10) 863 | # For "max" method 864 | max_alpha = ipywidgets.FloatSlider(min=0, max=6, value=3) 865 | max_distance = ipywidgets.IntSlider(min=1, max=20, value=10) 866 | # For "minmax" method 867 | minmax_distance = ipywidgets.FloatSlider(min=0, max=6, value=3) 868 | minmax_threshold = ipywidgets.FloatSlider(min=0, max=20, value=10) 869 | # For "Zaefferer" method 870 | zaefferer_grad_threshold = ipywidgets.FloatSlider(min=0, max=0.2, 871 | value=0.1, step=0.2*1E-1) 872 | zaefferer_window_size = ipywidgets.IntSlider(min=2, max=80, value=40) 873 | zaefferer_distance_cutoff = ipywidgets.FloatSlider(min=0, max=100, value=50) 874 | # For "stat" method 875 | stat_alpha = ipywidgets.FloatSlider(min=0, max=2, value=1) 876 | stat_window_radius = ipywidgets.IntSlider(min=5, max=20, value=10) 877 | stat_convergence_ratio = ipywidgets.FloatSlider(min=0, max=0.1, value=0.05) 878 | # For "Laplacian of Gaussians" method 879 | log_min_sigma = ipywidgets.FloatSlider(min=0, max=2, value=1) 880 | log_max_sigma = ipywidgets.FloatSlider(min=0, max=100, value=50) 881 | log_num_sigma = ipywidgets.FloatSlider(min=0, max=20, value=10) 882 | log_threshold = ipywidgets.FloatSlider(min=0, max=0.4, value=0.2) 883 | log_overlap = ipywidgets.FloatSlider(min=0, max=1, value=0.5) 884 | log_log_scale = ipywidgets.Checkbox() 885 | # For "Difference of Gaussians" method 886 | dog_min_sigma = ipywidgets.FloatSlider(min=0, max=2, value=1) 887 | dog_max_sigma = ipywidgets.FloatSlider(min=0, max=100, value=50) 888 | dog_sigma_ratio = ipywidgets.FloatSlider(min=0, max=3.2, value=1.6) 889 | dog_threshold = ipywidgets.FloatSlider(min=0, max=0.4, value=0.2) 890 | dog_overlap = ipywidgets.FloatSlider(min=0, max=1, value=0.5) 891 | # For "Cross correlation" method 892 | xc_distance = ipywidgets.FloatSlider(min=0, max=10., value=5.) 893 | xc_threshold = ipywidgets.FloatSlider(min=0, max=2., value=0.5) 894 | 895 | wdict["local_max_distance"] = local_max_distance 896 | wdict["local_max_threshold"] = local_max_threshold 897 | wdict["max_alpha"] = max_alpha 898 | wdict["max_distance"] = max_distance 899 | wdict["minmax_distance"] = minmax_distance 900 | wdict["minmax_threshold"] = minmax_threshold 901 | wdict["zaefferer_grad_threshold"] = zaefferer_grad_threshold 902 | wdict["zaefferer_window_size"] = zaefferer_window_size 903 | wdict["zaefferer_distance_cutoff"] = zaefferer_distance_cutoff 904 | wdict["stat_alpha"] = stat_alpha 905 | wdict["stat_window_radius"] = stat_window_radius 906 | wdict["stat_convergence_ratio"] = stat_convergence_ratio 907 | wdict["log_min_sigma"] = log_min_sigma 908 | wdict["log_max_sigma"] = log_max_sigma 909 | wdict["log_num_sigma"] = log_num_sigma 910 | wdict["log_threshold"] = log_threshold 911 | wdict["log_overlap"] = log_overlap 912 | wdict["log_log_scale"] = log_log_scale 913 | wdict["dog_min_sigma"] = dog_min_sigma 914 | wdict["dog_max_sigma"] = dog_max_sigma 915 | wdict["dog_sigma_ratio"] = dog_sigma_ratio 916 | wdict["dog_threshold"] = dog_threshold 917 | wdict["dog_overlap"] = dog_overlap 918 | wdict["xc_distance"] = xc_distance 919 | wdict["xc_threshold"] = xc_threshold 920 | 921 | # Connect 922 | link((obj, "local_max_distance"), (local_max_distance, "value")) 923 | link((obj, "local_max_threshold"), (local_max_threshold, "value")) 924 | link((obj, "max_alpha"), (max_alpha, "value")) 925 | link((obj, "max_distance"), (max_distance, "value")) 926 | link((obj, "minmax_distance"), (minmax_distance, "value")) 927 | link((obj, "minmax_threshold"), (minmax_threshold, "value")) 928 | link((obj, "zaefferer_grad_threshold"), (zaefferer_grad_threshold, "value")) 929 | link((obj, "zaefferer_window_size"), (zaefferer_window_size, "value")) 930 | link((obj, "zaefferer_distance_cutoff"), (zaefferer_distance_cutoff, "value")) 931 | link((obj, "stat_alpha"), (stat_alpha, "value")) 932 | link((obj, "stat_window_radius"), (stat_window_radius, "value")) 933 | link((obj, "stat_convergence_ratio"), (stat_convergence_ratio, "value")) 934 | link((obj, "log_min_sigma"), (log_min_sigma, "value")) 935 | link((obj, "log_max_sigma"), (log_max_sigma, "value")) 936 | link((obj, "log_num_sigma"), (log_num_sigma, "value")) 937 | link((obj, "log_threshold"), (log_threshold, "value")) 938 | link((obj, "log_overlap"), (log_overlap, "value")) 939 | link((obj, "log_log_scale"), (log_log_scale, "value")) 940 | link((obj, "dog_min_sigma"), (dog_min_sigma, "value")) 941 | link((obj, "dog_max_sigma"), (dog_max_sigma, "value")) 942 | link((obj, "dog_sigma_ratio"), (dog_sigma_ratio, "value")) 943 | link((obj, "dog_threshold"), (dog_threshold, "value")) 944 | link((obj, "dog_overlap"), (dog_overlap, "value")) 945 | link((obj, "xc_distance"), (xc_distance, "value")) 946 | link((obj, "xc_threshold"), (xc_threshold, "value")) 947 | 948 | close = ipywidgets.Button( 949 | description="Close", 950 | tooltip="Close widget and close figure.") 951 | compute = ipywidgets.Button( 952 | description="Compute over navigation axes.", 953 | tooltip="Find the peaks by iterating over the navigation axes.") 954 | 955 | box_local_max = ipywidgets.VBox([ 956 | labelme("Distance", local_max_distance), 957 | labelme("Threshold", local_max_threshold), 958 | ]) 959 | box_max = ipywidgets.VBox([#max_alpha, max_distance 960 | labelme("Alpha", max_alpha), 961 | labelme("Distance", max_distance), 962 | ]) 963 | box_minmax = ipywidgets.VBox([ 964 | labelme("Distance", minmax_distance), 965 | labelme("Threshold", minmax_threshold), 966 | ]) 967 | box_zaefferer = ipywidgets.VBox([ 968 | labelme("Gradient threshold", zaefferer_grad_threshold), 969 | labelme("Window size", zaefferer_window_size), 970 | labelme("Distance cutoff", zaefferer_distance_cutoff), 971 | ]) 972 | box_stat = ipywidgets.VBox([ 973 | labelme("Alpha", stat_alpha), 974 | labelme("Radius", stat_window_radius), 975 | labelme("Convergence ratio", stat_convergence_ratio), 976 | ]) 977 | box_log = ipywidgets.VBox([ 978 | labelme("Min sigma", log_min_sigma), 979 | labelme("Max sigma", log_max_sigma), 980 | labelme("Num sigma", log_num_sigma), 981 | labelme("Threshold", log_threshold), 982 | labelme("Overlap", log_overlap), 983 | labelme("Log scale", log_log_scale), 984 | ]) 985 | box_dog = ipywidgets.VBox([ 986 | labelme("Min sigma", dog_min_sigma), 987 | labelme("Max sigma", dog_max_sigma), 988 | labelme("Sigma ratio", dog_sigma_ratio), 989 | labelme("Threshold", dog_threshold), 990 | labelme("Overlap", dog_overlap), 991 | ]) 992 | box_xc = ipywidgets.VBox([ 993 | labelme("Distance", xc_distance), 994 | labelme("Threshold", xc_threshold), 995 | ]) 996 | 997 | box_dict = {"Local Max": box_local_max, 998 | "Max": box_max, 999 | "Minmax": box_minmax, 1000 | "Zaefferer": box_zaefferer, 1001 | "Stat": box_stat, 1002 | "Laplacian of Gaussians": box_log, 1003 | "Difference of Gaussians": box_dog, 1004 | "Template matching": box_xc} 1005 | 1006 | method = enum2dropdown(obj.traits()["method"]) 1007 | def update_method_parameters(change): 1008 | # Remove all parameters vbox widgets 1009 | for item, value in box_dict.items(): 1010 | value.layout.display = "none" 1011 | if change.new == "Local max": 1012 | box_local_max.layout.display = "" 1013 | elif change.new == "Max": 1014 | box_max.layout.display = "" 1015 | elif change.new == "Minmax": 1016 | box_minmax.layout.display = "" 1017 | elif change.new == "Zaefferer": 1018 | box_zaefferer.layout.display = "" 1019 | elif change.new == "Stat": 1020 | box_stat.layout.display = "" 1021 | elif change.new == "Laplacian of Gaussians": 1022 | box_log.layout.display = "" 1023 | elif change.new == "Difference of Gaussians": 1024 | box_dog.layout.display = "" 1025 | elif change.new == "Template matching": 1026 | box_xc.layout.display = "" 1027 | 1028 | method.observe(update_method_parameters, "value") 1029 | link((obj, "method"), (method, "value")) 1030 | 1031 | # Trigger the function that controls the visibility as 1032 | # setting the default value doesn't trigger it. 1033 | class Dummy: 1034 | new = method.value 1035 | update_method_parameters(change=Dummy()) 1036 | 1037 | widgets_list = [] 1038 | 1039 | if obj.show_navigation_sliders: 1040 | nav_widget = get_ipy_navigation_sliders( 1041 | obj.signal.axes_manager.navigation_axes, 1042 | in_accordion=True, 1043 | random_position_button=True) 1044 | widgets_list.append(nav_widget['widget']) 1045 | wdict.update(nav_widget['wdict']) 1046 | 1047 | l = [labelme("Method", method)] 1048 | l.extend([value for item, value in box_dict.items()]) 1049 | method_parameters = ipywidgets.Accordion((ipywidgets.VBox(l), )) 1050 | set_title_container(method_parameters, ["Method parameters"]) 1051 | 1052 | widgets_list.extend([method_parameters, 1053 | ipywidgets.HBox([compute, close])]) 1054 | box = ipywidgets.VBox(widgets_list) 1055 | 1056 | def on_compute_clicked(b): 1057 | obj.compute_navigation() 1058 | obj.signal._plot.close() 1059 | obj.close() 1060 | box.close() 1061 | compute.on_click(on_compute_clicked) 1062 | 1063 | def on_close_clicked(b): 1064 | obj.signal._plot.close() 1065 | obj.close() 1066 | box.close() 1067 | close.on_click(on_close_clicked) 1068 | return { 1069 | "widget": box, 1070 | "wdict": wdict, 1071 | } 1072 | 1073 | 1074 | @add_display_arg 1075 | def remove_baseline_ipy(obj, **kwargs): 1076 | wdict = {} 1077 | w_kwargs = dict(style={'description_width': '150px'}, layout={'width': '500px'}) 1078 | algorithm = enum2dropdown(obj.traits()["algorithm"], description="Method", **w_kwargs) 1079 | # `readout_format` not implemented for `FloatText` 1080 | _time_per_pixel = ipywidgets.FloatText( 1081 | disabled=True, description="Time per pixel (ms)", **w_kwargs 1082 | ) 1083 | 1084 | # Whittaker parameters 1085 | lam = ipywidgets.FloatLogSlider(min=0, max=15, description="lam", **w_kwargs) 1086 | diff_order = ipywidgets.IntSlider(min=1, max=3, description="diff_order", **w_kwargs) 1087 | p = ipywidgets.FloatSlider(min=0.0, max=1.0, description="p", **w_kwargs) 1088 | lam_1 = ipywidgets.FloatLogSlider(min=-10, max=0, description="lam_1", **w_kwargs) 1089 | eta = ipywidgets.FloatSlider(min=0.0, max=1.0, description="eta", **w_kwargs) 1090 | penalized_spline = ipywidgets.Checkbox(description="penalized_spline", **w_kwargs) 1091 | # Polynomial 1092 | poly_order = ipywidgets.IntSlider(min=1, max=10, description="poly_order", **w_kwargs) 1093 | peak_ratio = ipywidgets.FloatSlider(min=0.0, max=1.0, description="peak_ratio", **w_kwargs) 1094 | # Splines 1095 | num_knots = ipywidgets.IntSlider(min=10, max=10000, description="num_knots", **w_kwargs) 1096 | spline_degree = ipywidgets.IntSlider(min=1, max=5, description="spline_degree", **w_kwargs) 1097 | symmetric = ipywidgets.Checkbox(description="symmetric", **w_kwargs) 1098 | quantile = ipywidgets.FloatSlider(min=0.001, max=0.5, description="quantile", **w_kwargs) 1099 | # Classification 1100 | smooth_half_window = ipywidgets.IntSlider(min=1, max=100, description="smooth_half_window", **w_kwargs) 1101 | num_std = ipywidgets.IntSlider(min=1, max=100, description="num_std", **w_kwargs) 1102 | interp_half_window = ipywidgets.IntSlider(min=1, max=100, description="interp_half_window", **w_kwargs) 1103 | half_window = ipywidgets.IntSlider(min=1, max=100, description="half_window", **w_kwargs) 1104 | section = ipywidgets.IntSlider(min=1, max=100, description="section", **w_kwargs) 1105 | segments = ipywidgets.IntSlider(min=1, max=100, description="segments", **w_kwargs) 1106 | 1107 | # connect 1108 | link((obj, "lam"), (lam, "value")) 1109 | link((obj, "_time_per_pixel"), (_time_per_pixel, "value")) 1110 | link((obj, "algorithm"), (algorithm, "value")) 1111 | link((obj, "_time_per_pixel"), (_time_per_pixel, "value")) 1112 | link((obj, "diff_order"), (diff_order, "value")) 1113 | link((obj, "p"), (p, "value")) 1114 | link((obj, "lam_1"), (lam_1, "value")) 1115 | link((obj, "eta"), (eta, "value")) 1116 | link((obj, "penalized_spline"), (penalized_spline, "value")) 1117 | link((obj, "poly_order"), (poly_order, "value")) 1118 | link((obj, "peak_ratio"), (peak_ratio, "value")) 1119 | link((obj, "num_knots"), (num_knots, "value")) 1120 | link((obj, "spline_degree"), (spline_degree, "value")) 1121 | link((obj, "symmetric"), (symmetric, "value")) 1122 | link((obj, "quantile"), (quantile, "value")) 1123 | link((obj, "smooth_half_window"), (smooth_half_window, "value")) 1124 | link((obj, "num_std"), (num_std, "value")) 1125 | link((obj, "interp_half_window"), (interp_half_window, "value")) 1126 | link((obj, "half_window"), (half_window, "value")) 1127 | link((obj, "section"), (section, "value")) 1128 | link((obj, "segments"), (segments, "value")) 1129 | 1130 | parameters_widget_dict = { 1131 | # Whittaker parameters 1132 | "lam": lam, 1133 | "diff_order": diff_order, 1134 | "p": p, 1135 | "lam_1": lam_1, 1136 | "eta": eta, 1137 | "penalized_spline": penalized_spline, 1138 | # Polynomial parameters 1139 | "poly_order": poly_order, 1140 | "peak_ratio": peak_ratio, 1141 | # Splines parameters 1142 | "num_knots": num_knots, 1143 | "spline_degree": spline_degree, 1144 | "symmetric": symmetric, 1145 | "quantile": quantile, 1146 | # Classification 1147 | "smooth_half_window": smooth_half_window, 1148 | "num_std": num_std, 1149 | "interp_half_window": interp_half_window, 1150 | "half_window": half_window, 1151 | "section": section, 1152 | "segments": segments, 1153 | } 1154 | 1155 | def update_algorithm_parameters(change): 1156 | # Remove all parameters vbox widgets 1157 | for parameter_name, parameter_widget in parameters_widget_dict.items(): 1158 | if getattr(obj, f"_enable_{parameter_name}"): 1159 | # if enabled, display the widget 1160 | parameter_widget.layout.display = "" 1161 | else: 1162 | parameter_widget.layout.display = "none" 1163 | 1164 | algorithm.observe(update_algorithm_parameters, "value") 1165 | 1166 | # For initialisation, trigger the function that controls the visibility 1167 | # as setting the default value doesn't trigger it. 1168 | class Dummy: 1169 | new = algorithm.value 1170 | update_algorithm_parameters(change=Dummy()) 1171 | 1172 | close = ipywidgets.Button( 1173 | description="Close", 1174 | tooltip="Close widget and remove baseline from the signal figure.") 1175 | apply = ipywidgets.Button( 1176 | description="Apply", 1177 | tooltip="Remove the baseline in the whole dataset.") 1178 | 1179 | method_parameters = ipywidgets.Accordion( 1180 | (ipywidgets.VBox([value for value in parameters_widget_dict.values()]), ), 1181 | selected_index=0, 1182 | ) 1183 | set_title_container(method_parameters, ["Method parameters"]) 1184 | 1185 | wdict["algorithm"] = algorithm 1186 | wdict["_time_per_pixel"] = _time_per_pixel 1187 | wdict["lam"] = lam 1188 | wdict["diff_order"] = diff_order 1189 | wdict["p"] = p 1190 | wdict["lam_1"] = lam_1 1191 | wdict["eta"] = eta 1192 | wdict["penalized_spline"] = penalized_spline 1193 | wdict["poly_order"] = poly_order 1194 | wdict["peak_ratio"] = peak_ratio 1195 | wdict["num_knots"] = num_knots 1196 | wdict["spline_degree"] = spline_degree 1197 | wdict["symmetric"] = symmetric 1198 | wdict["quantile"] = quantile 1199 | wdict["smooth_half_window"] = smooth_half_window 1200 | wdict["num_std"] = num_std 1201 | wdict["interp_half_window"] = interp_half_window 1202 | wdict["half_window"] = half_window 1203 | wdict["section"] = section 1204 | wdict["segments"] = segments 1205 | wdict["apply"] = apply 1206 | 1207 | box = ipywidgets.VBox( 1208 | [algorithm, _time_per_pixel, method_parameters, ipywidgets.HBox((apply, close))] 1209 | ) 1210 | 1211 | def on_apply_clicked(b): 1212 | obj.apply() 1213 | box.close() 1214 | apply.on_click(on_apply_clicked) 1215 | 1216 | def on_close_clicked(b): 1217 | obj.close() 1218 | box.close() 1219 | close.on_click(on_close_clicked) 1220 | 1221 | return { 1222 | "widget": box, 1223 | "wdict": wdict, 1224 | } 1225 | -------------------------------------------------------------------------------- /hyperspy_gui_ipywidgets/utils.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | import ipywidgets 4 | from traits.api import Undefined 5 | import IPython.display 6 | 7 | 8 | 9 | 10 | FORM_ITEM_LAYOUT = ipywidgets.Layout( 11 | display='flex', 12 | flex_flow='row', 13 | justify_content='space-between', 14 | ) 15 | 16 | 17 | def labelme(label, widget): 18 | if label is Undefined: 19 | label = "" 20 | if not isinstance(label, ipywidgets.Label): 21 | label = ipywidgets.Label(label, 22 | layout=ipywidgets.Layout(width="auto")) 23 | return ipywidgets.HBox( 24 | [label, widget], 25 | layout=FORM_ITEM_LAYOUT, 26 | ) 27 | 28 | 29 | def labelme_sandwich(label1, widget, label2): 30 | if label1 is Undefined: 31 | label1 = "" 32 | if label2 is Undefined: 33 | label2 = "" 34 | if not isinstance(label1, ipywidgets.Label): 35 | label1 = ipywidgets.Label(label1) 36 | if not isinstance(label2, ipywidgets.Label): 37 | label2 = ipywidgets.Label(label2) 38 | return ipywidgets.HBox( 39 | [label1, widget, label2], 40 | layout=FORM_ITEM_LAYOUT) 41 | 42 | 43 | def get_label(trait, label): 44 | label = (label.replace("_", " ").capitalize() 45 | if not trait.label else trait.label) 46 | return label 47 | 48 | 49 | def enum2dropdown(trait, description=None, **kwargs): 50 | widget = ipywidgets.Dropdown( 51 | options=trait.trait_type.values, **kwargs) 52 | if description is not None: 53 | description_tooltip = trait.desc if trait.desc else "" 54 | widget.description = description 55 | widget.description_tooltip = description_tooltip 56 | return widget 57 | 58 | 59 | def float2floattext(trait, label): 60 | description_tooltip = trait.desc if trait.desc else "" 61 | widget = ipywidgets.FloatText() 62 | widget.description_tooltip = description_tooltip 63 | return labelme(widget=widget, label=label) 64 | 65 | 66 | def str2text(trait, label): 67 | description = trait.desc if trait.desc else "" 68 | widget = ipywidgets.Text() 69 | widget.description_tooltip = description 70 | return labelme(widget=widget, label=label) 71 | 72 | 73 | def add_display_arg(f): 74 | @functools.wraps(f) 75 | def wrapper(*args, **kwargs): 76 | display = kwargs.pop("display", True) 77 | wdict = f(*args, **kwargs) 78 | if display: 79 | IPython.display.display(wdict["widget"]) 80 | else: 81 | return wdict 82 | return wrapper 83 | 84 | 85 | def set_title_container(container, titles): 86 | """Convenience function to set the title of a container widget, following 87 | API changes in ipywidget 8.0. 88 | 89 | Parameters 90 | ---------- 91 | container : ipywidgets accordion, tab or nested tab 92 | The container onto which the title is added 93 | titles : list of string 94 | The list of string to add to the container. 95 | 96 | Returns 97 | ------- 98 | None. 99 | 100 | """ 101 | try: 102 | for index, title in enumerate(titles): 103 | container.set_title(index, title) 104 | except AttributeError: 105 | container.titles = tuple(titles) -------------------------------------------------------------------------------- /images/preferences_gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperspy/hyperspy_gui_ipywidgets/073f47ba36940f8d0fbd51e38f2b9d628c10aa04/images/preferences_gui.png -------------------------------------------------------------------------------- /prepare_release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import re 4 | import subprocess 5 | 6 | 7 | def run_towncrier(tag): 8 | cmd = ("towncrier", "build", "--version", tag.strip("v")) 9 | 10 | return subprocess.call(cmd) 11 | 12 | 13 | def update_fallback_version_in_pyproject(tag, fname="pyproject.toml"): 14 | version = tag.strip("v").split(".") 15 | if len(version) < 3: 16 | version += ["0"] 17 | 18 | # Default to +1 on patch version 19 | major, minor, patch = version[0], version[1], int(version[2]) + 1 20 | 21 | with open(fname, "r") as file: 22 | lines = file.readlines() 23 | 24 | pattern = "fallback_version" 25 | new_version = f"{major}.{minor}.{patch}.dev0" 26 | # Iterate through the lines and find the pattern 27 | for i, line in enumerate(lines): 28 | if re.search(pattern, line): 29 | lines[i] = f'{pattern} = "{new_version}"\n' 30 | break 31 | 32 | # Write the updated content back to the file 33 | with open(fname, "w") as file: 34 | file.writelines(lines) 35 | 36 | print( 37 | f"\nNew (fallback) dev version ({new_version}) written to `pyproject.toml`.\n" 38 | ) 39 | 40 | 41 | if __name__ == "__main__": 42 | # Get tag argument 43 | parser = argparse.ArgumentParser() 44 | parser.add_argument("tag") 45 | args = parser.parse_args() 46 | tag = args.tag 47 | 48 | # Update release notes 49 | # towncrier is not used in this repository 50 | # run_towncrier(tag) 51 | 52 | # Update fallback version for setuptools_scm 53 | update_fallback_version_in_pyproject(tag) 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64", "setuptools_scm>=8", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "hyperspy_gui_ipywidgets" 7 | description = "ipywidgets GUI elements for the HyperSpy framework." 8 | requires-python = ">=3.9" 9 | readme = "README.md" 10 | keywords=[ 11 | "data analysis", 12 | "microscopy", 13 | "ipywidgets", 14 | "hyperspy", 15 | "multi-dimensional", 16 | ] 17 | classifiers = [ 18 | "Development Status :: 4 - Beta", 19 | "Intended Audience :: Developers", 20 | "Intended Audience :: Science/Research", 21 | "Topic :: Software Development :: Libraries", 22 | "Topic :: Scientific/Engineering", 23 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 24 | "Programming Language :: Python :: 3.9", 25 | "Programming Language :: Python :: 3.10", 26 | "Programming Language :: Python :: 3.11", 27 | "Programming Language :: Python :: 3.12", 28 | "Programming Language :: Python :: 3.13", 29 | "Operating System :: Microsoft :: Windows", 30 | "Operating System :: POSIX", 31 | "Operating System :: Unix", 32 | "Operating System :: MacOS", 33 | ] 34 | dependencies = [ 35 | "hyperspy>=2.3.0", 36 | "ipywidgets>=8.0", 37 | "link_traits", 38 | "ipympl", # for convenience 39 | ] 40 | dynamic = ["version"] 41 | 42 | [project.entry-points."hyperspy.extensions"] 43 | hyperspy_gui_ipywidgets = "hyperspy_gui_ipywidgets" 44 | 45 | [project.license] 46 | file = "LICENSE" 47 | 48 | [project.optional-dependencies] 49 | tests = [ 50 | "pytest", 51 | "pytest-cov", 52 | "pytest-rerunfailures", 53 | "setuptools-scm", 54 | ] 55 | dev = [ 56 | "black", 57 | "hyperspy-gui-ipywidgets[doc]", 58 | "hyperspy-gui-ipywidgets[tests]" 59 | ] 60 | 61 | [project.urls] 62 | "Homepage" = "https://github.com/hyperspy/hyperspy_gui_ipywidgets" 63 | "Bug Reports" = "https://github.com/hyperspy/hyperspy_gui_ipywidgets/issues" 64 | "Source" = "https://github.com/hyperspy/hyperspy_gui_ipywidgets" 65 | "Conda-Forge" = "https://anaconda.org/conda-forge/hyperspy-gui-ipywidgets" 66 | "Support" = "https://gitter.im/hyperspy/hyperspy" 67 | 68 | [tool.coverage.run] 69 | branch = true 70 | source = ["hyperspy_gui_ipywidgets"] 71 | omit = [ 72 | "hyperspy_gui_ipywidgets/tests/*", 73 | "hyperspy_gui_ipywidgets/conftest.py", 74 | "prepare_release.py", 75 | ] 76 | 77 | [tool.coverage.report] 78 | precision = 2 79 | 80 | [tool.pytest.ini_options] 81 | # "-ra", # Display summary: "all except passes" 82 | addopts = "-ra" 83 | minversion = "6.0" 84 | testpaths = [ 85 | "hyperspy_gui_ipywidgets/tests", 86 | ] 87 | 88 | [tool.setuptools.packages.find] 89 | include = ["hyperspy_gui_ipywidgets*"] 90 | 91 | [tool.setuptools.package-data] 92 | "*" = ["*.yaml"] 93 | 94 | [tool.setuptools_scm] 95 | # Presence enables setuptools_scm, the version will be determine at build time from git 96 | # The version will be updated by the `prepare_release.py` script 97 | fallback_version = "2.1.1.dev0" 98 | -------------------------------------------------------------------------------- /releasing_guide.md: -------------------------------------------------------------------------------- 1 | 2 | Cut a Release 3 | ============= 4 | 5 | Create a PR to the `main` branch and go through the following steps: 6 | 7 | **Preparation** 8 | - Prepare the release by running the `prepare_release.py` python script (e.g. `python prepare_release.py 2.0.1`) , which will do the following: 9 | - update the `setuptools_scm` fallback version in `pyproject.toml`. 10 | - Check release notes 11 | - (optional) check conda-forge and wheels build. Pushing a tag to a fork will run the release workflow without uploading to pypi 12 | - Let that PR collect comments for a day to ensure that other maintainers are comfortable with releasing 13 | 14 | **Tag and release** 15 | :warning: this is a point of no return point :warning: 16 | - push tag (`vx.y.z`) to the upstream repository and the following will be triggered: 17 | - build of the wheels and their upload to pypi 18 | - creation of a Github Release 19 | 20 | **Post-release action** 21 | - Merge the PR 22 | 23 | Follow-up 24 | ========= 25 | 26 | - Tidy up and close corresponding milestone 27 | - A PR to the conda-forge feedstock will be created by the conda-forge bot 28 | 29 | Additional information 30 | ====================== 31 | 32 | The version is defined by ``setuptools_scm`` at build time when packaging HyperSpy. In an editable installation (when using ``pip install -e .``), the version is defined from the 33 | git repository. 34 | --------------------------------------------------------------------------------