├── .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 | [](https://github.com/hyperspy/hyperspy_gui_ipywidgets/actions)
3 | [](https://pypi.org/project/hyperspy-gui-ipywidgets)
4 | [](https://pypi.org/project/hyperspy-gui-ipywidgets)
5 | [](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 | 
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 |
--------------------------------------------------------------------------------