phaserEM 0.1__tar.gz → 0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phaserem-0.1 → phaserem-0.2}/PKG-INFO +83 -16
- phaserem-0.2/README.md +130 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/common/noise_models.py +3 -4
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/common/output.py +69 -5
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/common/regularizers.py +106 -4
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/common/simulation.py +41 -5
- phaserem-0.2/phaser/engines/conventional/run.py +123 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/conventional/solvers.py +39 -11
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/gradient/run.py +122 -135
- {phaserem-0.1 → phaserem-0.2}/phaser/execute.py +190 -95
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/__init__.py +60 -5
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/io/empad.py +15 -5
- phaserem-0.2/phaser/hooks/io/manual.py +168 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/preprocessing.py +18 -10
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/regularization.py +25 -2
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/scan.py +2 -1
- phaserem-0.2/phaser/hooks/tilt.py +55 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/io/empad.py +29 -10
- {phaserem-0.1 → phaserem-0.2}/phaser/main.py +1 -1
- phaserem-0.2/phaser/observer.py +332 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/plan.py +23 -4
- {phaserem-0.1 → phaserem-0.2}/phaser/state.py +38 -3
- {phaserem-0.1 → phaserem-0.2}/phaser/types.py +6 -3
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/_jax_kernels.py +5 -1
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/analysis.py +36 -7
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/image.py +43 -13
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/io.py +8 -4
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/num.py +24 -1
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/object.py +11 -10
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/plotting.py +13 -6
- phaserem-0.2/phaser/web/dist/bundle-dashboard.js +3577 -0
- phaserem-0.2/phaser/web/dist/bundle-manager.js +3455 -0
- phaserem-0.2/phaser/web/dist/bundle-vendors-node_modules_wasm-array_wasm_array_js.js +106 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/web/routes.py +21 -4
- {phaserem-0.1 → phaserem-0.2}/phaser/web/server.py +81 -16
- {phaserem-0.1 → phaserem-0.2}/phaser/web/slurm.py +3 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/web/templates/base.html +1 -1
- {phaserem-0.1 → phaserem-0.2}/phaser/web/templates/dashboard.html +0 -4
- phaserem-0.2/phaser/web/templates/manager.html +10 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/web/types.py +13 -9
- {phaserem-0.1 → phaserem-0.2}/phaser/web/worker.py +14 -9
- {phaserem-0.1 → phaserem-0.2}/phaserEM.egg-info/PKG-INFO +83 -16
- {phaserem-0.1 → phaserem-0.2}/phaserEM.egg-info/SOURCES.txt +4 -2
- {phaserem-0.1 → phaserem-0.2}/phaserEM.egg-info/requires.txt +12 -8
- {phaserem-0.1 → phaserem-0.2}/pyproject.toml +17 -11
- {phaserem-0.1 → phaserem-0.2}/tests/test_initialization.py +57 -11
- phaserem-0.2/tests/test_load.py +150 -0
- {phaserem-0.1 → phaserem-0.2}/tests/test_num.py +16 -3
- phaserem-0.1/README.md +0 -69
- phaserem-0.1/phaser/engines/conventional/run.py +0 -142
- phaserem-0.1/phaser/web/dist/03510a839ccb97b0da9f.module.wasm +0 -0
- phaserem-0.1/phaser/web/dist/bundle-dashboard.js +0 -712
- phaserem-0.1/phaser/web/dist/bundle-manager.js +0 -210
- phaserem-0.1/phaser/web/dist/bundle-vendors-node_modules_wasm-array_wasm_array_js.js +0 -106
- phaserem-0.1/phaser/web/dist/style.css +0 -152
- phaserem-0.1/phaser/web/templates/manager.html +0 -19
- {phaserem-0.1 → phaserem-0.2}/LICENSE.txt +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/__init__.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/__main__.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/common/__init__.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/common/position_correction.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/conventional/__init__.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/engines/gradient/solvers.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/hook.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/object.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/probe.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/schedule.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/hooks/solver.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/io/__init__.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/py.typed +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/__init__.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/_cuda_kernels.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/misc.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/optics.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/physics.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/utils/scan.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/web/__init__.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/web/dist/9573273f862f4f5d9644.module.wasm +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/web/notebook.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaser/web/util.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaserEM.egg-info/dependency_links.txt +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaserEM.egg-info/entry_points.txt +0 -0
- {phaserem-0.1 → phaserem-0.2}/phaserEM.egg-info/top_level.txt +0 -0
- {phaserem-0.1 → phaserem-0.2}/setup.cfg +0 -0
- {phaserem-0.1 → phaserem-0.2}/setup.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/tests/test_empad.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/tests/test_image.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/tests/test_misc.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/tests/test_object.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/tests/test_optics.py +0 -0
- {phaserem-0.1 → phaserem-0.2}/tests/test_physics.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phaserEM
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2
|
|
4
4
|
Summary: Weapon of choice for electron ptychographic reconstructions
|
|
5
5
|
Author-email: Colin Gilgenbach <hexane@mit.edu>
|
|
6
6
|
License-Expression: MPL-2.0
|
|
7
7
|
Project-URL: Repository, https://github.com/hexane360/phaser.git
|
|
8
8
|
Project-URL: Documentation, https://hexane360.github.io/phaser/dev
|
|
9
|
+
Keywords: science,atoms,materials,chemistry
|
|
9
10
|
Classifier: Development Status :: 4 - Beta
|
|
10
11
|
Classifier: Programming Language :: Python :: 3
|
|
11
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -13,32 +14,37 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
16
19
|
Classifier: Typing :: Typed
|
|
17
20
|
Requires-Python: >=3.10
|
|
18
21
|
Description-Content-Type: text/markdown
|
|
19
22
|
License-File: LICENSE.txt
|
|
20
|
-
Requires-Dist: numpy<2.
|
|
21
|
-
Requires-Dist: scipy<1.
|
|
23
|
+
Requires-Dist: numpy<2.6,>=1.22
|
|
24
|
+
Requires-Dist: scipy<1.19,>=1.7.0
|
|
22
25
|
Requires-Dist: matplotlib~=3.8
|
|
23
26
|
Requires-Dist: h5py~=3.8
|
|
24
27
|
Requires-Dist: pyyaml>=5.3.1
|
|
25
28
|
Requires-Dist: click~=8.1.0
|
|
26
|
-
Requires-Dist: rich
|
|
29
|
+
Requires-Dist: rich<15,>=12.0.0
|
|
27
30
|
Requires-Dist: tifffile>=2023.8.25
|
|
28
|
-
Requires-Dist: py-pane==0.11.
|
|
31
|
+
Requires-Dist: py-pane==0.11.3
|
|
29
32
|
Requires-Dist: typing_extensions~=4.7
|
|
30
33
|
Provides-Extra: dev
|
|
31
34
|
Requires-Dist: pytest>=6.2.4; extra == "dev"
|
|
32
35
|
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
|
|
33
36
|
Requires-Dist: coveragepy-lcov>=0.1.1; extra == "dev"
|
|
34
|
-
Provides-Extra:
|
|
35
|
-
Requires-Dist: cupy-
|
|
36
|
-
Requires-Dist: pynvml>=11.0.0; extra == "
|
|
37
|
+
Provides-Extra: cupy11
|
|
38
|
+
Requires-Dist: cupy-cuda11x>=12.0.0; extra == "cupy11"
|
|
39
|
+
Requires-Dist: pynvml>=11.0.0; extra == "cupy11"
|
|
40
|
+
Provides-Extra: cupy12
|
|
41
|
+
Requires-Dist: cupy-cuda12x>=12.0.0; extra == "cupy12"
|
|
42
|
+
Requires-Dist: pynvml>=11.0.0; extra == "cupy12"
|
|
37
43
|
Provides-Extra: jax
|
|
38
|
-
Requires-Dist: jax<0.
|
|
44
|
+
Requires-Dist: jax<0.8,>=0.4.25; extra == "jax"
|
|
39
45
|
Requires-Dist: optax>=0.2.2; extra == "jax"
|
|
40
46
|
Provides-Extra: web
|
|
41
|
-
Requires-Dist: Quart>=0.
|
|
47
|
+
Requires-Dist: Quart>=0.20.0; extra == "web"
|
|
42
48
|
Requires-Dist: backoff==2.2.1; extra == "web"
|
|
43
49
|
Requires-Dist: requests>=2.31.0; extra == "web"
|
|
44
50
|
Provides-Extra: docs
|
|
@@ -52,9 +58,11 @@ Dynamic: license-file
|
|
|
52
58
|
|
|
53
59
|
phaser: The weapon of choice for ptychographic reconstructions
|
|
54
60
|
---
|
|
55
|
-
[![][ci-badge]][ci-url] [![][commit-badge]][commit-url] [![][docs-dev-badge]][docs-dev-url]
|
|
61
|
+
[![][ci-badge]][ci-url] [![][commit-badge]][commit-url] [![][docs-dev-badge]][docs-dev-url] [![][binder-badge]][binder-url]
|
|
62
|
+
|
|
56
63
|
|
|
57
64
|
`phaser` is a fully-featured package for multislice electron ptychography.
|
|
65
|
+
See our [arXiv paper](https://arxiv.org/abs/2505.14372) for more details.
|
|
58
66
|
|
|
59
67
|
## Features
|
|
60
68
|
|
|
@@ -64,11 +72,18 @@ phaser: The weapon of choice for ptychographic reconstructions
|
|
|
64
72
|
- Multiple incoherent probe modes
|
|
65
73
|
- Probe position correction
|
|
66
74
|
- Upsampled (sPIE) and segmented ptychography (work in progress)
|
|
67
|
-
- Adaptive propagator correction (
|
|
75
|
+
- Adaptive propagator correction (contributed by M Zhu)
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
Documentation on `phaser` can be found here: https://hexane360.github.io/phaser/dev/
|
|
80
|
+
|
|
81
|
+
Documentation is still very much a work in progress, so please feel free to open an issue or email me if you have any questions!
|
|
68
82
|
|
|
69
83
|
## Installation
|
|
70
84
|
|
|
71
|
-
To install, first clone the repository from github
|
|
85
|
+
To install, first clone the repository from github.
|
|
86
|
+
This can be done from GitHub Desktop, or from the git command line:
|
|
72
87
|
|
|
73
88
|
```sh
|
|
74
89
|
$ git clone https://github.com/hexane360/phaser
|
|
@@ -76,9 +91,29 @@ $ git clone https://github.com/hexane360/phaser
|
|
|
76
91
|
$ cd phaser
|
|
77
92
|
```
|
|
78
93
|
|
|
79
|
-
|
|
94
|
+
We recommend using a conda environment or Python virtual environment to keep things clean, although this is not mandatory.
|
|
95
|
+
|
|
96
|
+
`phaser` supports multiple computational backends. The simplest (and slowest) is `numpy`. `cupy` can be used for CUDA-accelerated. `jax` supports CPU and GPU acceleration, and is the only backend which supports the gradient descent engine.
|
|
97
|
+
If you're unsure what engines to use, we recommend installing the `jax` engine.
|
|
98
|
+
|
|
99
|
+
If you're using [`cupy`][cupy] or [`jax`][jax] with a GPU, start by following the installation instructions for those packages.
|
|
100
|
+
Jax can be installed with or without CUDA support, if you're using CUDA make sure you install the correct version. Currently, Jax does not support CUDA on Windows.
|
|
80
101
|
|
|
81
|
-
|
|
102
|
+
Before moving on to installing `phaser`, make sure those packages you've installed work:
|
|
103
|
+
```sh
|
|
104
|
+
$ python
|
|
105
|
+
>>> import jax
|
|
106
|
+
>>> jax.default_backend()
|
|
107
|
+
'gpu' # should be 'gpu' on cuda, 'cpu' otherwise
|
|
108
|
+
>>> jax.numpy.array([1, 2, 3, 4]) + 1 # test a basic operation
|
|
109
|
+
Array([2, 3, 4, 5], dtype=int32)
|
|
110
|
+
|
|
111
|
+
>>> import cupy
|
|
112
|
+
>>> cupy.array([1, 2, 3, 4]) + 1
|
|
113
|
+
array([2, 3, 4, 5])
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Then, install `phaser` using `pip`:
|
|
82
117
|
|
|
83
118
|
```sh
|
|
84
119
|
$ python -m pip install -e .
|
|
@@ -87,7 +122,22 @@ $ python -m pip install -e .
|
|
|
87
122
|
For the jax or cupy backend, or for the optional webserver, install with the corresponding options:
|
|
88
123
|
|
|
89
124
|
```sh
|
|
90
|
-
$ python -m pip install -e
|
|
125
|
+
$ python -m pip install -e ".[jax,cupy12,web]" # for the 'jax', 'cupy12', and 'web' options
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Depending on your command line, you may need to put double quotes around the options (as shown).
|
|
129
|
+
|
|
130
|
+
Here are the supported installation options:
|
|
131
|
+
|
|
132
|
+
- `jax`: For the [`jax`][jax] backend (required for the gradient descent engine)
|
|
133
|
+
- `cupy11`: `cupy` for CUDA toolkit 11.x
|
|
134
|
+
- `cupy12`: `cupy` for CUDA toolkit 12.x
|
|
135
|
+
- `web`: For the web interface
|
|
136
|
+
|
|
137
|
+
For [Optuna](https://optuna.org/) hyperoptimization, install it as well:
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
$ pip install optuna
|
|
91
141
|
```
|
|
92
142
|
|
|
93
143
|
## Running
|
|
@@ -100,6 +150,20 @@ To run the webserver, call `phaser serve`. By default, the server serves on http
|
|
|
100
150
|
|
|
101
151
|
To run a worker, call `phaser worker <url>`, where `url` is the URL of a running job server.
|
|
102
152
|
|
|
153
|
+
## Sample data & Examples
|
|
154
|
+
|
|
155
|
+
Sample data can be downloaded from the following dropbox link: https://www.dropbox.com/scl/fo/txm3k88ubrzvt541v23ir/AL-l_m6VnGlFxzHWZSSc0TA?rlkey=8qxtwnc8cwhpff6jpr5s40y6i&st=x9pbwke0&dl=0
|
|
156
|
+
|
|
157
|
+
Copy the `sample_data` directory into the root code folder.
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
$ curl --output sample_data.zip -L 'https://www.dropbox.com/scl/fo/txm3k88ubrzvt541v23ir/AL-l_m6VnGlFxzHWZSSc0TA?rlkey=8qxtwnc8cwhpff6jpr5s40y6i&st=x9pbwke0&dl=1'
|
|
161
|
+
$ unzip sample_data.zip -x / -d sample_data
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Sample data includes simulated and experimental MoS2 data, simulated and experimental Si data, and experimental PrScO3 data.
|
|
165
|
+
After the data is downloaded, any of the example reconstructions can be run as `phaser run examples/mos2_grad.yaml` (for example).
|
|
166
|
+
|
|
103
167
|
## Alternatives
|
|
104
168
|
|
|
105
169
|
Other notable ptychography packages:
|
|
@@ -108,6 +172,7 @@ Other notable ptychography packages:
|
|
|
108
172
|
- [`py4DSTEM`](https://github.com/py4dstem/py4DSTEM)
|
|
109
173
|
- [`PtyLab.m/py/jl`](https://opg.optica.org/oe/fulltext.cfm?uri=oe-31-9-13763&id=529026)
|
|
110
174
|
- [PtyPy](https://ptycho.github.io/ptypy/)
|
|
175
|
+
- [PtyRAD](https://github.com/chiahao3/ptyrad)
|
|
111
176
|
|
|
112
177
|
[numpy]: https://numpy.org/
|
|
113
178
|
[cupy]: https://cupy.dev/
|
|
@@ -119,3 +184,5 @@ Other notable ptychography packages:
|
|
|
119
184
|
[docs-dev-url]: https://hexane360.github.io/phaser/dev/
|
|
120
185
|
[commit-badge]: https://img.shields.io/github/last-commit/hexane360/phaser
|
|
121
186
|
[commit-url]: https://github.com/hexane360/phaser/commits
|
|
187
|
+
[binder-badge]: https://mybinder.org/badge_logo.svg
|
|
188
|
+
[binder-url]: https://mybinder.org/v2/gh/hexane360/phaser/HEAD
|
phaserem-0.2/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
phaser: The weapon of choice for ptychographic reconstructions
|
|
2
|
+
---
|
|
3
|
+
[![][ci-badge]][ci-url] [![][commit-badge]][commit-url] [![][docs-dev-badge]][docs-dev-url] [![][binder-badge]][binder-url]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
`phaser` is a fully-featured package for multislice electron ptychography.
|
|
7
|
+
See our [arXiv paper](https://arxiv.org/abs/2505.14372) for more details.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- ePIE, LSQ-MLs, and gradient descent algorithms
|
|
12
|
+
- [`numpy`][numpy], [`cupy`][cupy], and [`jax`][jax] backends.
|
|
13
|
+
- Single and multislice ptychography
|
|
14
|
+
- Multiple incoherent probe modes
|
|
15
|
+
- Probe position correction
|
|
16
|
+
- Upsampled (sPIE) and segmented ptychography (work in progress)
|
|
17
|
+
- Adaptive propagator correction (contributed by M Zhu)
|
|
18
|
+
|
|
19
|
+
## Documentation
|
|
20
|
+
|
|
21
|
+
Documentation on `phaser` can be found here: https://hexane360.github.io/phaser/dev/
|
|
22
|
+
|
|
23
|
+
Documentation is still very much a work in progress, so please feel free to open an issue or email me if you have any questions!
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
To install, first clone the repository from github.
|
|
28
|
+
This can be done from GitHub Desktop, or from the git command line:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
$ git clone https://github.com/hexane360/phaser
|
|
32
|
+
# enter phaser directory
|
|
33
|
+
$ cd phaser
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
We recommend using a conda environment or Python virtual environment to keep things clean, although this is not mandatory.
|
|
37
|
+
|
|
38
|
+
`phaser` supports multiple computational backends. The simplest (and slowest) is `numpy`. `cupy` can be used for CUDA-accelerated. `jax` supports CPU and GPU acceleration, and is the only backend which supports the gradient descent engine.
|
|
39
|
+
If you're unsure what engines to use, we recommend installing the `jax` engine.
|
|
40
|
+
|
|
41
|
+
If you're using [`cupy`][cupy] or [`jax`][jax] with a GPU, start by following the installation instructions for those packages.
|
|
42
|
+
Jax can be installed with or without CUDA support, if you're using CUDA make sure you install the correct version. Currently, Jax does not support CUDA on Windows.
|
|
43
|
+
|
|
44
|
+
Before moving on to installing `phaser`, make sure those packages you've installed work:
|
|
45
|
+
```sh
|
|
46
|
+
$ python
|
|
47
|
+
>>> import jax
|
|
48
|
+
>>> jax.default_backend()
|
|
49
|
+
'gpu' # should be 'gpu' on cuda, 'cpu' otherwise
|
|
50
|
+
>>> jax.numpy.array([1, 2, 3, 4]) + 1 # test a basic operation
|
|
51
|
+
Array([2, 3, 4, 5], dtype=int32)
|
|
52
|
+
|
|
53
|
+
>>> import cupy
|
|
54
|
+
>>> cupy.array([1, 2, 3, 4]) + 1
|
|
55
|
+
array([2, 3, 4, 5])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then, install `phaser` using `pip`:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
$ python -m pip install -e .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
For the jax or cupy backend, or for the optional webserver, install with the corresponding options:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
$ python -m pip install -e ".[jax,cupy12,web]" # for the 'jax', 'cupy12', and 'web' options
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Depending on your command line, you may need to put double quotes around the options (as shown).
|
|
71
|
+
|
|
72
|
+
Here are the supported installation options:
|
|
73
|
+
|
|
74
|
+
- `jax`: For the [`jax`][jax] backend (required for the gradient descent engine)
|
|
75
|
+
- `cupy11`: `cupy` for CUDA toolkit 11.x
|
|
76
|
+
- `cupy12`: `cupy` for CUDA toolkit 12.x
|
|
77
|
+
- `web`: For the web interface
|
|
78
|
+
|
|
79
|
+
For [Optuna](https://optuna.org/) hyperoptimization, install it as well:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
$ pip install optuna
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Running
|
|
86
|
+
|
|
87
|
+
After installation, the `phaser` command should be available. Phaser can be run from the command line, or through a job server.
|
|
88
|
+
|
|
89
|
+
To run a single reconstruction on the command line, call `phaser run <file>`, where `file` is the path to a reconstruction plan file.
|
|
90
|
+
|
|
91
|
+
To run the webserver, call `phaser serve`. By default, the server serves on https://localhost:5050/, so navigate there in a web browser. The server interface can be used to start workers and schedule reconstruction jobs.
|
|
92
|
+
|
|
93
|
+
To run a worker, call `phaser worker <url>`, where `url` is the URL of a running job server.
|
|
94
|
+
|
|
95
|
+
## Sample data & Examples
|
|
96
|
+
|
|
97
|
+
Sample data can be downloaded from the following dropbox link: https://www.dropbox.com/scl/fo/txm3k88ubrzvt541v23ir/AL-l_m6VnGlFxzHWZSSc0TA?rlkey=8qxtwnc8cwhpff6jpr5s40y6i&st=x9pbwke0&dl=0
|
|
98
|
+
|
|
99
|
+
Copy the `sample_data` directory into the root code folder.
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
$ curl --output sample_data.zip -L 'https://www.dropbox.com/scl/fo/txm3k88ubrzvt541v23ir/AL-l_m6VnGlFxzHWZSSc0TA?rlkey=8qxtwnc8cwhpff6jpr5s40y6i&st=x9pbwke0&dl=1'
|
|
103
|
+
$ unzip sample_data.zip -x / -d sample_data
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Sample data includes simulated and experimental MoS2 data, simulated and experimental Si data, and experimental PrScO3 data.
|
|
107
|
+
After the data is downloaded, any of the example reconstructions can be run as `phaser run examples/mos2_grad.yaml` (for example).
|
|
108
|
+
|
|
109
|
+
## Alternatives
|
|
110
|
+
|
|
111
|
+
Other notable ptychography packages:
|
|
112
|
+
|
|
113
|
+
- [`fold_slice`](https://github.com/yijiang1/fold_slice) branch of [PtychoShelves](https://www.psi.ch/en/sls/csaxs/software#coming-soon-ptychoshelves-a-versatile-high-level-framework-for-high-performance-analysis-of)
|
|
114
|
+
- [`py4DSTEM`](https://github.com/py4dstem/py4DSTEM)
|
|
115
|
+
- [`PtyLab.m/py/jl`](https://opg.optica.org/oe/fulltext.cfm?uri=oe-31-9-13763&id=529026)
|
|
116
|
+
- [PtyPy](https://ptycho.github.io/ptypy/)
|
|
117
|
+
- [PtyRAD](https://github.com/chiahao3/ptyrad)
|
|
118
|
+
|
|
119
|
+
[numpy]: https://numpy.org/
|
|
120
|
+
[cupy]: https://cupy.dev/
|
|
121
|
+
[jax]: https://docs.jax.dev/en/latest/
|
|
122
|
+
|
|
123
|
+
[ci-badge]: https://github.com/hexane360/phaser/workflows/Tests/badge.svg
|
|
124
|
+
[ci-url]: https://github.com/hexane360/phaser/actions/workflows/ci.yaml
|
|
125
|
+
[docs-dev-badge]: https://img.shields.io/badge/docs-dev-blue
|
|
126
|
+
[docs-dev-url]: https://hexane360.github.io/phaser/dev/
|
|
127
|
+
[commit-badge]: https://img.shields.io/github/last-commit/hexane360/phaser
|
|
128
|
+
[commit-url]: https://github.com/hexane360/phaser/commits
|
|
129
|
+
[binder-badge]: https://mybinder.org/badge_logo.svg
|
|
130
|
+
[binder-url]: https://mybinder.org/v2/gh/hexane360/phaser/HEAD
|
|
@@ -93,12 +93,11 @@ class PoissonNoiseModel(NoiseModel[None]):
|
|
|
93
93
|
) -> t.Tuple[Float, None]:
|
|
94
94
|
xp = get_array_module(model_wave, model_intensity, exp_patterns, mask)
|
|
95
95
|
patterns = xp.maximum(exp_patterns, 0.0)
|
|
96
|
-
#intensity - patterns * xp.log(intensity + self.offset)
|
|
97
96
|
|
|
98
97
|
loss = xp.sum(mask * (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
model_intensity + self.eps + patterns * (
|
|
99
|
+
xp.log(patterns + self.eps) - xp.log(model_intensity + self.eps) - 1.0
|
|
100
|
+
)
|
|
102
101
|
)).astype(exp_patterns.dtype)
|
|
103
102
|
return (loss, state)
|
|
104
103
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from functools import partial
|
|
2
|
+
import logging
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
import typing as t
|
|
4
5
|
|
|
5
6
|
import numpy
|
|
6
|
-
from numpy.typing import NDArray
|
|
7
7
|
import tifffile
|
|
8
8
|
|
|
9
9
|
from phaser.utils.num import to_numpy, abs2, fft2, get_array_module
|
|
@@ -18,9 +18,10 @@ def output_images(state: ReconsState, out_dir: Path, options: SaveOptions):
|
|
|
18
18
|
if ty not in _SAVE_FUNCS:
|
|
19
19
|
raise ValueError(f"Unknown image type '{ty}'")
|
|
20
20
|
|
|
21
|
+
ext = options.plot_ext if ty in _PLOT_FUNCS else 'tiff'
|
|
21
22
|
try:
|
|
22
23
|
out_name = options.img_fmt.format(
|
|
23
|
-
type=ty, iter=state.iter,
|
|
24
|
+
type=ty, iter=state.iter, ext=ext
|
|
24
25
|
)
|
|
25
26
|
out_path = out_dir / out_name
|
|
26
27
|
except KeyError as e:
|
|
@@ -122,7 +123,7 @@ def _save_object_phase(state: ReconsState, out_path: Path, options: SaveOptions,
|
|
|
122
123
|
)
|
|
123
124
|
|
|
124
125
|
if crop:
|
|
125
|
-
obj_phase = obj_phase[
|
|
126
|
+
obj_phase = obj_phase[(Ellipsis, *state.object.sampling.get_region_crop())]
|
|
126
127
|
mask = xp.ones(obj_phase.shape[-2:], dtype=numpy.bool_)
|
|
127
128
|
else:
|
|
128
129
|
# include whole image, but only scale based on ROI
|
|
@@ -158,7 +159,7 @@ def _save_object_mag(state: ReconsState, out_path: Path, options: SaveOptions, s
|
|
|
158
159
|
xp = get_array_module(state.object.data)
|
|
159
160
|
obj_mag = abs2(state.object.data)
|
|
160
161
|
if crop:
|
|
161
|
-
obj_mag = obj_mag[
|
|
162
|
+
obj_mag = obj_mag[(Ellipsis, *state.object.sampling.get_region_crop())]
|
|
162
163
|
mask = numpy.ones(obj_mag.shape[-2:], dtype=numpy.bool_)
|
|
163
164
|
else:
|
|
164
165
|
# include whole image, but only scale based on ROI
|
|
@@ -176,6 +177,65 @@ def _save_object_mag(state: ReconsState, out_path: Path, options: SaveOptions, s
|
|
|
176
177
|
w.write(obj_mag, **write_opts)
|
|
177
178
|
|
|
178
179
|
|
|
180
|
+
def _plot_scan(state: ReconsState, out_path: Path, options: SaveOptions):
|
|
181
|
+
from matplotlib import pyplot
|
|
182
|
+
fig, ax = pyplot.subplots(figsize=(4, 4), dpi=options.plot_dpi, constrained_layout=True)
|
|
183
|
+
|
|
184
|
+
ax.set_aspect(1.)
|
|
185
|
+
[left, right, bottom, top] = state.object.sampling.mpl_extent()
|
|
186
|
+
ax.set_xlim(left, right)
|
|
187
|
+
ax.set_ylim(bottom, top)
|
|
188
|
+
|
|
189
|
+
scan = to_numpy(state.scan)
|
|
190
|
+
i = numpy.arange(scan[..., 0].size)
|
|
191
|
+
ax.scatter(scan[..., 1].ravel(), scan[..., 0].ravel(), c=i, cmap='plasma', s=0.5, edgecolors='none')
|
|
192
|
+
|
|
193
|
+
fig.savefig(out_path)
|
|
194
|
+
pyplot.close(fig)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _plot_tilt(state: ReconsState, out_path: Path, options: SaveOptions):
|
|
198
|
+
from matplotlib import pyplot
|
|
199
|
+
|
|
200
|
+
if state.tilt is None:
|
|
201
|
+
logger = logging.getLogger(__name__)
|
|
202
|
+
logger.warning("Tilt map (`state.tilt`) is missing, skipping `plot_tilt`")
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
fig, ax = pyplot.subplots(figsize=(4, 4), dpi=options.plot_dpi, constrained_layout=True)
|
|
206
|
+
|
|
207
|
+
ax.set_aspect(1.)
|
|
208
|
+
[left, right, bottom, top] = state.object.sampling.mpl_extent()
|
|
209
|
+
ax.set_xlim(left, right)
|
|
210
|
+
ax.set_ylim(bottom, top)
|
|
211
|
+
|
|
212
|
+
scan = to_numpy(state.scan)
|
|
213
|
+
tilt = to_numpy(state.tilt)
|
|
214
|
+
tilt = tilt[..., 1] + tilt[..., 0]*1.j
|
|
215
|
+
max_tilt = max(numpy.max(numpy.abs(tilt)), 1.0) # at least 1 mrad
|
|
216
|
+
c = colorize_complex(tilt.ravel() / max_tilt, amp=True, rescale=False)
|
|
217
|
+
ax.scatter(scan[..., 1].ravel(), scan[..., 0].ravel(), c=c, s=0.5, edgecolors='none')
|
|
218
|
+
|
|
219
|
+
fig.draw_without_rendering()
|
|
220
|
+
trans = ax.transAxes + fig.transFigure.inverted()
|
|
221
|
+
legend_ax_max = trans.transform([0.95, 0.02])
|
|
222
|
+
legend_ax_size = (0.1, 0.1)
|
|
223
|
+
legend_ax = fig.add_axes((legend_ax_max[0] - legend_ax_size[0], legend_ax_max[1], *legend_ax_size), projection='polar')
|
|
224
|
+
|
|
225
|
+
legend_ax.set_rmax(max_tilt) # type: ignore
|
|
226
|
+
legend_ax.set_theta_direction(-1) # type: ignore
|
|
227
|
+
legend_ax.set_axis_off()
|
|
228
|
+
|
|
229
|
+
thetas = numpy.linspace(0., 2*numpy.pi, 70)
|
|
230
|
+
rs = numpy.concatenate([[0.0], numpy.geomspace(0.1, 1.0, 30)])
|
|
231
|
+
rr, tt = numpy.meshgrid(rs, thetas, indexing='ij')
|
|
232
|
+
c2 = colorize_complex(rr * numpy.exp(1.j * tt), rescale=False)
|
|
233
|
+
legend_ax.pcolormesh(tt, rr * max_tilt, c2)
|
|
234
|
+
legend_ax.text(-numpy.pi/2., max_tilt * 1.05, f"{max_tilt:.1f} mrad", ha='center', va='bottom', size='small')
|
|
235
|
+
|
|
236
|
+
fig.savefig(out_path)
|
|
237
|
+
pyplot.close(fig)
|
|
238
|
+
|
|
179
239
|
|
|
180
240
|
_SAVE_FUNCS: t.Dict[str, t.Callable[[ReconsState, Path, SaveOptions], t.Any]] = {
|
|
181
241
|
'probe': _save_probe,
|
|
@@ -186,4 +246,8 @@ _SAVE_FUNCS: t.Dict[str, t.Callable[[ReconsState, Path, SaveOptions], t.Any]] =
|
|
|
186
246
|
'object_phase_sum': partial(_save_object_phase, stack=False),
|
|
187
247
|
'object_mag_stack': partial(_save_object_mag, stack=True),
|
|
188
248
|
'object_mag_sum': partial(_save_object_mag, stack=False),
|
|
189
|
-
|
|
249
|
+
'scan': _plot_scan,
|
|
250
|
+
'tilt': _plot_tilt,
|
|
251
|
+
}
|
|
252
|
+
# save functions with special handling of file extensions
|
|
253
|
+
_PLOT_FUNCS: t.Set[str] = {'scan', 'tilt'}
|
|
@@ -6,14 +6,14 @@ import numpy
|
|
|
6
6
|
from numpy.typing import NDArray
|
|
7
7
|
|
|
8
8
|
from phaser.utils.num import (
|
|
9
|
-
get_array_module, get_scipy_module, Float,
|
|
10
|
-
jit, fft2, ifft2, abs2, xp_is_jax, to_real_dtype
|
|
9
|
+
get_array_module, get_scipy_module, Float, unstack,
|
|
10
|
+
jit, fft2, ifft2, abs2, xp_is_jax, to_real_dtype, to_numpy
|
|
11
11
|
)
|
|
12
12
|
from phaser.state import ReconsState
|
|
13
13
|
from phaser.hooks.regularization import (
|
|
14
14
|
ClampObjectAmplitudeProps, LimitProbeSupportProps,
|
|
15
|
-
RegularizeLayersProps, ObjLowPassProps,
|
|
16
|
-
CostRegularizerProps, TVRegularizerProps
|
|
15
|
+
RegularizeLayersProps, ObjLowPassProps, GaussianProps,
|
|
16
|
+
CostRegularizerProps, TVRegularizerProps, UnstructuredGaussianProps
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
@@ -161,6 +161,37 @@ class ObjLowPass:
|
|
|
161
161
|
return (sim, state)
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
class ObjGaussian:
|
|
165
|
+
def __init__(self, args: None, props: GaussianProps):
|
|
166
|
+
self.sigma = props.sigma
|
|
167
|
+
self.weight = props.weight
|
|
168
|
+
|
|
169
|
+
def init_state(self, sim: ReconsState) -> NDArray[numpy.floating]:
|
|
170
|
+
samp = sim.object.sampling
|
|
171
|
+
xp = get_array_module(sim.object.data)
|
|
172
|
+
dtype = to_real_dtype(sim.object.data.dtype)
|
|
173
|
+
|
|
174
|
+
ky = xp.fft.fftfreq(samp.shape[0], samp.sampling[0]).astype(dtype)
|
|
175
|
+
kx = xp.fft.fftfreq(samp.shape[1], samp.sampling[1]).astype(dtype)
|
|
176
|
+
(ky, kx) = xp.meshgrid(ky, kx, indexing='ij')
|
|
177
|
+
k2 = ky**2 + kx**2
|
|
178
|
+
|
|
179
|
+
filt = xp.exp(-(2 * numpy.pi**2 * self.sigma**2) * k2).astype(dtype)
|
|
180
|
+
return (1. - self.weight * (1. - filt)).astype(dtype)
|
|
181
|
+
|
|
182
|
+
def apply_group(
|
|
183
|
+
self, group: NDArray[numpy.integer], sim: ReconsState, state: NDArray[numpy.floating]
|
|
184
|
+
) -> t.Tuple[ReconsState, NDArray[numpy.floating]]:
|
|
185
|
+
return self.apply_iter(sim, state)
|
|
186
|
+
|
|
187
|
+
def apply_iter(
|
|
188
|
+
self, sim: ReconsState, state: NDArray[numpy.floating]
|
|
189
|
+
) -> t.Tuple[ReconsState, NDArray[numpy.floating]]:
|
|
190
|
+
# TODO: should this be done in-place?
|
|
191
|
+
sim.object.data = ifft2(state * fft2(sim.object.data))
|
|
192
|
+
return (sim, state)
|
|
193
|
+
|
|
194
|
+
|
|
164
195
|
class ObjL1:
|
|
165
196
|
def __init__(self, args: None, props: CostRegularizerProps):
|
|
166
197
|
self.cost: float = props.cost
|
|
@@ -395,6 +426,77 @@ class ProbeRecipTotalVariation:
|
|
|
395
426
|
return (cost * cost_scale * self.cost, state)
|
|
396
427
|
|
|
397
428
|
|
|
429
|
+
class UnstructuredGaussian:
|
|
430
|
+
def __init__(self, args: None, props: UnstructuredGaussianProps):
|
|
431
|
+
self.weight = props.weight
|
|
432
|
+
self.sigma = props.sigma
|
|
433
|
+
self.attr_path = props.attr_path
|
|
434
|
+
|
|
435
|
+
def init_state(self, sim: ReconsState) -> NDArray[numpy.floating]:
|
|
436
|
+
xp = get_array_module(sim.scan)
|
|
437
|
+
try:
|
|
438
|
+
self.getattr_nested(sim, self.attr_path)
|
|
439
|
+
except AttributeError as e:
|
|
440
|
+
raise AttributeError(f"Can't get path '{self.attr_path}' in reconstruction state") from e
|
|
441
|
+
|
|
442
|
+
# precompute Gaussian filter
|
|
443
|
+
obj_samp = sim.object.sampling
|
|
444
|
+
ky = xp.fft.fftfreq(obj_samp.shape[0], obj_samp.sampling[0])
|
|
445
|
+
kx = xp.fft.fftfreq(obj_samp.shape[1], obj_samp.sampling[1])
|
|
446
|
+
ky, kx = xp.meshgrid(ky, kx, indexing='ij')
|
|
447
|
+
k2 = ky**2 + kx**2
|
|
448
|
+
return xp.exp(- (2 * numpy.pi**2 * self.sigma**2) * k2)
|
|
449
|
+
|
|
450
|
+
def getattr_nested(self, obj: t.Any, attr_path: str) -> t.Any:
|
|
451
|
+
for attr in attr_path.split('.'):
|
|
452
|
+
obj = getattr(obj, attr)
|
|
453
|
+
return obj
|
|
454
|
+
|
|
455
|
+
def setattr_nested(self, obj: t.Any, attr_path: str, value: t.Any):
|
|
456
|
+
*parents, last = attr_path.split('.')
|
|
457
|
+
for attr in parents:
|
|
458
|
+
obj = getattr(obj, attr)
|
|
459
|
+
setattr(obj, last, value)
|
|
460
|
+
|
|
461
|
+
def apply_iter(self, sim: ReconsState, state: NDArray[numpy.floating]) -> t.Tuple[ReconsState, NDArray[numpy.floating]]:
|
|
462
|
+
from scipy.spatial import KDTree
|
|
463
|
+
obj_samp = sim.object.sampling
|
|
464
|
+
scan_flat = sim.scan.reshape(-1, 2)
|
|
465
|
+
scan_ndim = sim.scan.ndim - 1
|
|
466
|
+
|
|
467
|
+
attr = self.getattr_nested(sim, self.attr_path)
|
|
468
|
+
vals = t.cast(NDArray[numpy.inexact], getattr(attr, 'data', attr)) # Extract raw array
|
|
469
|
+
xp = get_array_module(vals)
|
|
470
|
+
|
|
471
|
+
is_complex = xp.iscomplexobj(vals)
|
|
472
|
+
|
|
473
|
+
if is_complex:
|
|
474
|
+
vals = xp.stack([vals.real, vals.imag], axis=-1)
|
|
475
|
+
|
|
476
|
+
tree = KDTree(to_numpy(scan_flat))
|
|
477
|
+
obj_pts = numpy.stack(obj_samp.grid(xp=numpy), axis=-1)
|
|
478
|
+
# val_img should be on GPU
|
|
479
|
+
val_img = vals.reshape((-1, *vals.shape[scan_ndim:]))[xp.array(tree.query(obj_pts)[1])]
|
|
480
|
+
|
|
481
|
+
# blur in Fourier space
|
|
482
|
+
# HACK: the transposes are so we FFT over the first two axes
|
|
483
|
+
val_img_blur = ifft2(fft2(val_img.T) * state.T).real.T
|
|
484
|
+
|
|
485
|
+
# then sample back to the scan positions
|
|
486
|
+
idxs = xp.round((scan_flat - obj_samp.corner) / obj_samp.sampling).astype(numpy.int_)
|
|
487
|
+
blur_vals = val_img_blur[unstack(idxs, axis=-1)].reshape(vals.shape)
|
|
488
|
+
|
|
489
|
+
new_vals = self.weight * blur_vals + (1 - self.weight) * vals
|
|
490
|
+
|
|
491
|
+
if is_complex:
|
|
492
|
+
new_vals = new_vals[..., 0] + new_vals[..., 1] * 1.j
|
|
493
|
+
|
|
494
|
+
# TODO: this should be expressed as immutable
|
|
495
|
+
self.setattr_nested(sim, self.attr_path, new_vals)
|
|
496
|
+
|
|
497
|
+
return (sim, state)
|
|
498
|
+
|
|
499
|
+
|
|
398
500
|
def img_grad(img: numpy.ndarray) -> t.Tuple[numpy.ndarray, numpy.ndarray]:
|
|
399
501
|
xp = get_array_module(img)
|
|
400
502
|
return (
|
|
@@ -8,7 +8,7 @@ from typing_extensions import Self
|
|
|
8
8
|
|
|
9
9
|
from phaser.utils.num import (
|
|
10
10
|
get_array_module, to_real_dtype, to_complex_dtype,
|
|
11
|
-
fft2, ifft2, is_jax, to_numpy, block_until_ready,
|
|
11
|
+
fft2, ifft2, is_jax, to_numpy, block_until_ready, ufunc_outer
|
|
12
12
|
)
|
|
13
13
|
from phaser.utils.misc import FloatKey, jax_dataclass, create_compact_groupings, create_sparse_groupings, shuffled
|
|
14
14
|
from phaser.utils.optics import fresnel_propagator, fourier_shift_filter
|
|
@@ -190,6 +190,38 @@ def make_propagators(state: ReconsState, bwlim_frac: t.Optional[float] = 2/3) ->
|
|
|
190
190
|
)
|
|
191
191
|
|
|
192
192
|
|
|
193
|
+
def tilt_propagators(
|
|
194
|
+
ky: NDArray[numpy.floating], kx: NDArray[numpy.floating],
|
|
195
|
+
state: ReconsState,
|
|
196
|
+
props: t.Optional[NDArray[numpy.complexfloating]], # shape: (Nz-1, Ny, Nx)
|
|
197
|
+
tilts: t.Optional[NDArray[numpy.floating]] # shape: (..., 2), in mrad
|
|
198
|
+
) -> t.Optional[NDArray[numpy.complexfloating]]:
|
|
199
|
+
"""
|
|
200
|
+
Applies tilt and slice-dependent propagation phase shifts to props.
|
|
201
|
+
-------
|
|
202
|
+
NDArray[complex] or None
|
|
203
|
+
Tilted propagators of shape (n_layers-1, ..., Ny, Nx), or None if no slices.
|
|
204
|
+
"""
|
|
205
|
+
if props is None:
|
|
206
|
+
return None
|
|
207
|
+
if tilts is None:
|
|
208
|
+
return props[:, None, ...]
|
|
209
|
+
|
|
210
|
+
xp = get_array_module(state.probe.data)
|
|
211
|
+
dtype = to_real_dtype(state.probe.data.dtype)
|
|
212
|
+
complex_dtype = to_complex_dtype(dtype)
|
|
213
|
+
delta_zs = state.object.thicknesses[:-1]
|
|
214
|
+
|
|
215
|
+
tilt_ramps = xp.exp( # (n_layers-1, batch, Ny, Nx)
|
|
216
|
+
2.j * xp.pi * ufunc_outer(xp.multiply, delta_zs, (
|
|
217
|
+
ufunc_outer(xp.multiply, xp.tan(tilts[..., 0] * 1e-3), ky) +
|
|
218
|
+
ufunc_outer(xp.multiply, xp.tan(tilts[..., 1] * 1e-3), kx)
|
|
219
|
+
))
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return props[(slice(None), *(None,)*(tilts.ndim - 1), Ellipsis)] * tilt_ramps.astype(complex_dtype)
|
|
223
|
+
|
|
224
|
+
|
|
193
225
|
@t.overload
|
|
194
226
|
def cutout_group(
|
|
195
227
|
ky: NDArray[numpy.floating], kx: NDArray[numpy.floating],
|
|
@@ -236,16 +268,20 @@ def slice_forwards(
|
|
|
236
268
|
if props is None:
|
|
237
269
|
return f(0, None, state)
|
|
238
270
|
|
|
239
|
-
n_slices = len(props) + 1
|
|
271
|
+
n_slices = len(props) + 1 # props shape: (Nz-1, batch, Ny, Nx)
|
|
240
272
|
|
|
241
273
|
if is_jax(props):
|
|
242
274
|
import jax
|
|
243
|
-
|
|
275
|
+
def step_fn(carry, slice_i):
|
|
276
|
+
new_state = f(slice_i, props[slice_i], carry)
|
|
277
|
+
return new_state, None
|
|
278
|
+
|
|
279
|
+
state, _ = jax.lax.scan(step_fn, state, jax.numpy.arange(n_slices - 1))
|
|
244
280
|
return f(n_slices - 1, None, state)
|
|
245
281
|
|
|
282
|
+
# fallback numpy mode
|
|
246
283
|
for slice_i in range(n_slices - 1):
|
|
247
284
|
state = f(slice_i, props[slice_i], state)
|
|
248
|
-
|
|
249
285
|
return f(n_slices - 1, None, state)
|
|
250
286
|
|
|
251
287
|
|
|
@@ -267,4 +303,4 @@ def slice_backwards(
|
|
|
267
303
|
for slice_i in range(n_slices - 1, 0, -1):
|
|
268
304
|
state = f(slice_i, props[slice_i - 1], state)
|
|
269
305
|
|
|
270
|
-
return f(0, None, state)
|
|
306
|
+
return f(0, None, state)
|