xpunwrap 0.0.0__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.
- xpunwrap-0.0.0/LICENSE +19 -0
- xpunwrap-0.0.0/PKG-INFO +225 -0
- xpunwrap-0.0.0/README.md +205 -0
- xpunwrap-0.0.0/pyproject.toml +62 -0
- xpunwrap-0.0.0/setup.cfg +9 -0
- xpunwrap-0.0.0/tests/test_fft_engines.py +70 -0
- xpunwrap-0.0.0/tests/test_ls_poisson.py +21 -0
- xpunwrap-0.0.0/tests/test_ls_poisson_periodic_grad.py +31 -0
- xpunwrap-0.0.0/tests/test_ls_weighted.py +29 -0
- xpunwrap-0.0.0/xpunwrap/__init__.py +29 -0
- xpunwrap-0.0.0/xpunwrap/_dtype_utils.py +54 -0
- xpunwrap-0.0.0/xpunwrap/_ndarray_backend.py +96 -0
- xpunwrap-0.0.0/xpunwrap/algorithms/__init__.py +4 -0
- xpunwrap-0.0.0/xpunwrap/algorithms/_ls_common.py +92 -0
- xpunwrap-0.0.0/xpunwrap/algorithms/_plane_utils.py +65 -0
- xpunwrap-0.0.0/xpunwrap/algorithms/ls_poisson.py +68 -0
- xpunwrap-0.0.0/xpunwrap/algorithms/ls_poisson_pg.py +117 -0
- xpunwrap-0.0.0/xpunwrap/algorithms/ls_weighted.py +188 -0
- xpunwrap-0.0.0/xpunwrap/algorithms/skimage_unwrap.py +106 -0
- xpunwrap-0.0.0/xpunwrap/fourier/__init__.py +88 -0
- xpunwrap-0.0.0/xpunwrap/fourier/base.py +48 -0
- xpunwrap-0.0.0/xpunwrap/fourier/fft_cupy.py +21 -0
- xpunwrap-0.0.0/xpunwrap/fourier/fft_numpy.py +16 -0
- xpunwrap-0.0.0/xpunwrap/fourier/fft_pyfftw.py +106 -0
- xpunwrap-0.0.0/xpunwrap.egg-info/PKG-INFO +225 -0
- xpunwrap-0.0.0/xpunwrap.egg-info/SOURCES.txt +28 -0
- xpunwrap-0.0.0/xpunwrap.egg-info/dependency_links.txt +1 -0
- xpunwrap-0.0.0/xpunwrap.egg-info/requires.txt +13 -0
- xpunwrap-0.0.0/xpunwrap.egg-info/top_level.txt +1 -0
xpunwrap-0.0.0/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 Eoghan O'Connell
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
xpunwrap-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xpunwrap
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Phase unwrapping algorithms which are CPU and GPU agnostic
|
|
5
|
+
Author: Conrad Möckel, Eoghan O'Connell
|
|
6
|
+
Maintainer-email: Eoghan O'Connell <eoclives@hotmail.com>
|
|
7
|
+
Requires-Python: <4,>=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy>=2.2.6
|
|
11
|
+
Provides-Extra: cupy-cuda12x
|
|
12
|
+
Requires-Dist: cupy-cuda12x; extra == "cupy-cuda12x"
|
|
13
|
+
Provides-Extra: cupy-cuda13x
|
|
14
|
+
Requires-Dist: cupy-cuda13x; extra == "cupy-cuda13x"
|
|
15
|
+
Provides-Extra: scikit-image
|
|
16
|
+
Requires-Dist: scikit-image>=0.20.0; extra == "scikit-image"
|
|
17
|
+
Provides-Extra: fftw
|
|
18
|
+
Requires-Dist: pyfftw>=0.15.0; extra == "fftw"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
<!-- sphinx-logo-start -->
|
|
22
|
+
<picture>
|
|
23
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/logos/xpunwrap_animated_dark.gif">
|
|
24
|
+
<img src="docs/logos/xpunwrap_animated_light.gif" alt="XPUnwrap">
|
|
25
|
+
</picture>
|
|
26
|
+
<!-- sphinx-logo-end -->
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# xpUnwrap
|
|
30
|
+
**Phase Unwrapping on GPU (and CPU), with Python**
|
|
31
|
+
|
|
32
|
+
There are many phase unwrapping algorithms out there. Many are implemented in
|
|
33
|
+
CUDA, C++ etc. I haven't yet found any algorithm that interfaces
|
|
34
|
+
**easily** with Python via the wonderful GPU-based package CuPy.
|
|
35
|
+
Please inform me if you know of one that is open-source.
|
|
36
|
+
|
|
37
|
+
<!-- there is one via pytorch -->
|
|
38
|
+
|
|
39
|
+
This package aims to make GPU-based phase unwrapping in Python seamless.
|
|
40
|
+
If you don't have a GPU, don't worry, all the code works on the CPU
|
|
41
|
+
(albeit slower).
|
|
42
|
+
|
|
43
|
+
## What does "xp" stand for?
|
|
44
|
+
|
|
45
|
+
The ``xp`` in xpUnwrap references several things:
|
|
46
|
+
- CuPy's [agnostic code idea](https://docs.cupy.dev/en/stable/user_guide/basic.html#how-to-write-cpu-gpu-agnostic-code)
|
|
47
|
+
where ``cp`` is for cupy and ``np`` is for numpy.
|
|
48
|
+
- the "G" in GPU and "C" in CPU.
|
|
49
|
+
- the "Q" from QPI (Quantitative Phase Imaging) e.g., from the sister package [qpretrieve](https://github.com/RI-imaging/qpretrieve).
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
<!-- sphinx-after-installation-heading -->
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
|
|
56
|
+
# if you have the CUDA Toolkit version 12x use:
|
|
57
|
+
pip install xpunwrap[cupy-cuda12x]
|
|
58
|
+
|
|
59
|
+
# if you have the CUDA Toolkit version 13x use:
|
|
60
|
+
pip install xpunwrap[cupy-cuda13x]
|
|
61
|
+
|
|
62
|
+
# to install and just use on the CPU, just don't use any optional dependencies:
|
|
63
|
+
pip install xpunwrap
|
|
64
|
+
|
|
65
|
+
# to also use skimage's unwrap (CPU-only):
|
|
66
|
+
pip install xpunwrap[scikit-image]
|
|
67
|
+
|
|
68
|
+
# for faster CPU Fourier transforms via pyFFTW:
|
|
69
|
+
pip install xpunwrap[FFTW]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
On the CPU backend, `xpunwrap` automatically uses pyFFTW for the Fourier
|
|
73
|
+
transforms in the least-squares solvers when it is installed, falling back to
|
|
74
|
+
NumPy otherwise. On the GPU backend, CuPy's FFT is always used.
|
|
75
|
+
|
|
76
|
+
## Compatible Phase Retrieval and Numerical Refocusing GPU packages
|
|
77
|
+
|
|
78
|
+
In the same group on GitHub, we have two other packages that work seamlessy
|
|
79
|
+
with `xpunwrap`.
|
|
80
|
+
- Phase Retrieval that works on CPU and GPU:
|
|
81
|
+
[qpretrieve](https://github.com/RI-imaging/qpretrieve)
|
|
82
|
+
- Numerical Refocussing that works on CPU and GPU:
|
|
83
|
+
[nrefocus](https://github.com/RI-imaging/nrefocus)
|
|
84
|
+
- If you are looking for a file format that can also work with the GPU, try
|
|
85
|
+
out [zarr-python](https://zarr.readthedocs.io/en/stable/user-guide/gpu/)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Documentation and Citations
|
|
89
|
+
|
|
90
|
+
Check out the Documentation and Examples here: https://xpunwrap.readthedocs.io/
|
|
91
|
+
|
|
92
|
+
<!-- ## Citing this work -->
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Using `xpunwrap`
|
|
96
|
+
|
|
97
|
+
There are several phase unwrapping algorithms to choose from:
|
|
98
|
+
- `algo_ls_poisson`: Least-squares Poisson solver
|
|
99
|
+
- `algo_ls_poisson_pg`: Least-squares unwrapping with periodic gradient enforcement
|
|
100
|
+
- `algo_ls_weighted`: Weighted least-squares unwrapping with border masking
|
|
101
|
+
- `algo_skimage_unwrap`: Scikit-Image's Path Following algorithm
|
|
102
|
+
(Herraez et al.) is included for comparison. It only works on the CPU,
|
|
103
|
+
as it is not suitable for use with GPU.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
"""
|
|
107
|
+
Field retrieval (qpretrieve) and phase unwrapping (xpunwrap) on GPU.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
import matplotlib.pyplot as plt
|
|
111
|
+
import numpy as np
|
|
112
|
+
import qpretrieve
|
|
113
|
+
import xpunwrap
|
|
114
|
+
|
|
115
|
+
# Force GPU backend for both libraries.
|
|
116
|
+
qpretrieve.set_ndarray_backend("cupy")
|
|
117
|
+
xpunwrap.set_ndarray_backend("cupy")
|
|
118
|
+
xp = xpunwrap.get_ndarray_backend()
|
|
119
|
+
|
|
120
|
+
fft_interface = qpretrieve.fourier.FFTFilterCupy
|
|
121
|
+
edata = np.load("./data/hologram_cell.npz")
|
|
122
|
+
holo = qpretrieve.OffAxisHologram(edata["data"], fft_interface)
|
|
123
|
+
bg = qpretrieve.OffAxisHologram(edata["bg_data"], fft_interface)
|
|
124
|
+
|
|
125
|
+
holo.run_pipeline(filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
|
|
126
|
+
bg.process_like(holo)
|
|
127
|
+
phase_wrapped = xp.asarray(holo.phase - bg.phase).astype(xp.float32)
|
|
128
|
+
|
|
129
|
+
# Unwrap the phase with all available algorithms
|
|
130
|
+
outputs = {}
|
|
131
|
+
for algo_name, algo in xpunwrap.algos_available().items():
|
|
132
|
+
outputs[algo_name] = algo(phase_wrapped)
|
|
133
|
+
skimage_out = outputs.get("algo_skimage_unwrap", None)
|
|
134
|
+
if skimage_out is not None:
|
|
135
|
+
outputs_no_skimage = {k: v for k, v in outputs.items() if k != "algo_skimage_unwrap"}
|
|
136
|
+
else:
|
|
137
|
+
outputs_no_skimage = outputs
|
|
138
|
+
|
|
139
|
+
# plot the wrapped and unwrapped phases
|
|
140
|
+
plt.style.use("dark_background")
|
|
141
|
+
fig, axes = plt.subplots(2, 3, figsize=(8, 6))
|
|
142
|
+
fig.suptitle("Field Retrieval + Phase Unwrapping (GPU)", fontsize=18)
|
|
143
|
+
axes = axes.flatten(order="F")
|
|
144
|
+
|
|
145
|
+
axes[0].imshow(phase_wrapped.get()[0])
|
|
146
|
+
axes[0].set_title("Wrapped Phase")
|
|
147
|
+
|
|
148
|
+
# With column-major flattening, bottom-right is index 5.
|
|
149
|
+
skimage_ax = axes[5]
|
|
150
|
+
|
|
151
|
+
# Fill remaining non-skimage algorithms first (slots 1..4).
|
|
152
|
+
plot_slots = [2, 3, 4]
|
|
153
|
+
for slot, (algo_name, arr) in zip(plot_slots, outputs_no_skimage.items()):
|
|
154
|
+
ax = axes[slot]
|
|
155
|
+
ax.imshow(arr.get()[0])
|
|
156
|
+
ax.set_title(f"Unwrapped\n{algo_name}")
|
|
157
|
+
|
|
158
|
+
if skimage_out is not None:
|
|
159
|
+
skimage_ax.imshow(skimage_out.get()[0])
|
|
160
|
+
skimage_ax.set_title("Unwrapped (CPU-only)\nalgo_skimage_unwrap")
|
|
161
|
+
else:
|
|
162
|
+
skimage_ax.text(0.5, 0.5, "skimage missing", ha="center", va="center")
|
|
163
|
+
skimage_ax.set_title("Unwrapped\nalgo_skimage_unwrap")
|
|
164
|
+
|
|
165
|
+
for ax in axes:
|
|
166
|
+
ax.set_axis_off()
|
|
167
|
+
|
|
168
|
+
plt.tight_layout(w_pad=4.5)
|
|
169
|
+
# plt.savefig("gpu_field_retr_phase_unwrapping.png")
|
|
170
|
+
plt.show()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+

|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
## Developers
|
|
177
|
+
|
|
178
|
+
Install everything you need with (for example for cuda12x)
|
|
179
|
+
```bash
|
|
180
|
+
pip install -e .[cupy-cuda12x] --group dev
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
Run the unit tests with `pytest`
|
|
185
|
+
```bash
|
|
186
|
+
pip install --group tests
|
|
187
|
+
pytest tests
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Build docs with `sphinx`
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
pip install --group docs
|
|
194
|
+
cd docs
|
|
195
|
+
sphinx-build . _build
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Check the docs locally by opening `docs/_build/index.html` file in your browser.
|
|
199
|
+
|
|
200
|
+
### Package management with `uv`
|
|
201
|
+
|
|
202
|
+
If you wish to use `uv` to handle package management, then you need to first
|
|
203
|
+
install `uv` and then run:
|
|
204
|
+
|
|
205
|
+
For example, for the optional `cupy-cuda12x`
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
uv sync -active --all-groups --all-extras
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Which should install all dev dependencies.
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
## Other phase unwrapping packages
|
|
215
|
+
|
|
216
|
+
Here are several phase unwrapping packages that exist in Python:
|
|
217
|
+
- https://github.com/blakedewey/phase_unwrap (CPU)
|
|
218
|
+
- Scikit-image (CPU)
|
|
219
|
+
|
|
220
|
+
## Use of AI
|
|
221
|
+
|
|
222
|
+
This package used Codex (mostly 5.4-Mini) to translate the source citations
|
|
223
|
+
(see documentation website) into Python code, as well as the mathematical derivations.
|
|
224
|
+
All code and documents were then human-verified and verified with Claude Opus,
|
|
225
|
+
and tested against known input data.
|
xpunwrap-0.0.0/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<!-- sphinx-logo-start -->
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/logos/xpunwrap_animated_dark.gif">
|
|
4
|
+
<img src="docs/logos/xpunwrap_animated_light.gif" alt="XPUnwrap">
|
|
5
|
+
</picture>
|
|
6
|
+
<!-- sphinx-logo-end -->
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# xpUnwrap
|
|
10
|
+
**Phase Unwrapping on GPU (and CPU), with Python**
|
|
11
|
+
|
|
12
|
+
There are many phase unwrapping algorithms out there. Many are implemented in
|
|
13
|
+
CUDA, C++ etc. I haven't yet found any algorithm that interfaces
|
|
14
|
+
**easily** with Python via the wonderful GPU-based package CuPy.
|
|
15
|
+
Please inform me if you know of one that is open-source.
|
|
16
|
+
|
|
17
|
+
<!-- there is one via pytorch -->
|
|
18
|
+
|
|
19
|
+
This package aims to make GPU-based phase unwrapping in Python seamless.
|
|
20
|
+
If you don't have a GPU, don't worry, all the code works on the CPU
|
|
21
|
+
(albeit slower).
|
|
22
|
+
|
|
23
|
+
## What does "xp" stand for?
|
|
24
|
+
|
|
25
|
+
The ``xp`` in xpUnwrap references several things:
|
|
26
|
+
- CuPy's [agnostic code idea](https://docs.cupy.dev/en/stable/user_guide/basic.html#how-to-write-cpu-gpu-agnostic-code)
|
|
27
|
+
where ``cp`` is for cupy and ``np`` is for numpy.
|
|
28
|
+
- the "G" in GPU and "C" in CPU.
|
|
29
|
+
- the "Q" from QPI (Quantitative Phase Imaging) e.g., from the sister package [qpretrieve](https://github.com/RI-imaging/qpretrieve).
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
<!-- sphinx-after-installation-heading -->
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
|
|
36
|
+
# if you have the CUDA Toolkit version 12x use:
|
|
37
|
+
pip install xpunwrap[cupy-cuda12x]
|
|
38
|
+
|
|
39
|
+
# if you have the CUDA Toolkit version 13x use:
|
|
40
|
+
pip install xpunwrap[cupy-cuda13x]
|
|
41
|
+
|
|
42
|
+
# to install and just use on the CPU, just don't use any optional dependencies:
|
|
43
|
+
pip install xpunwrap
|
|
44
|
+
|
|
45
|
+
# to also use skimage's unwrap (CPU-only):
|
|
46
|
+
pip install xpunwrap[scikit-image]
|
|
47
|
+
|
|
48
|
+
# for faster CPU Fourier transforms via pyFFTW:
|
|
49
|
+
pip install xpunwrap[FFTW]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
On the CPU backend, `xpunwrap` automatically uses pyFFTW for the Fourier
|
|
53
|
+
transforms in the least-squares solvers when it is installed, falling back to
|
|
54
|
+
NumPy otherwise. On the GPU backend, CuPy's FFT is always used.
|
|
55
|
+
|
|
56
|
+
## Compatible Phase Retrieval and Numerical Refocusing GPU packages
|
|
57
|
+
|
|
58
|
+
In the same group on GitHub, we have two other packages that work seamlessy
|
|
59
|
+
with `xpunwrap`.
|
|
60
|
+
- Phase Retrieval that works on CPU and GPU:
|
|
61
|
+
[qpretrieve](https://github.com/RI-imaging/qpretrieve)
|
|
62
|
+
- Numerical Refocussing that works on CPU and GPU:
|
|
63
|
+
[nrefocus](https://github.com/RI-imaging/nrefocus)
|
|
64
|
+
- If you are looking for a file format that can also work with the GPU, try
|
|
65
|
+
out [zarr-python](https://zarr.readthedocs.io/en/stable/user-guide/gpu/)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## Documentation and Citations
|
|
69
|
+
|
|
70
|
+
Check out the Documentation and Examples here: https://xpunwrap.readthedocs.io/
|
|
71
|
+
|
|
72
|
+
<!-- ## Citing this work -->
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## Using `xpunwrap`
|
|
76
|
+
|
|
77
|
+
There are several phase unwrapping algorithms to choose from:
|
|
78
|
+
- `algo_ls_poisson`: Least-squares Poisson solver
|
|
79
|
+
- `algo_ls_poisson_pg`: Least-squares unwrapping with periodic gradient enforcement
|
|
80
|
+
- `algo_ls_weighted`: Weighted least-squares unwrapping with border masking
|
|
81
|
+
- `algo_skimage_unwrap`: Scikit-Image's Path Following algorithm
|
|
82
|
+
(Herraez et al.) is included for comparison. It only works on the CPU,
|
|
83
|
+
as it is not suitable for use with GPU.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
"""
|
|
87
|
+
Field retrieval (qpretrieve) and phase unwrapping (xpunwrap) on GPU.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
import matplotlib.pyplot as plt
|
|
91
|
+
import numpy as np
|
|
92
|
+
import qpretrieve
|
|
93
|
+
import xpunwrap
|
|
94
|
+
|
|
95
|
+
# Force GPU backend for both libraries.
|
|
96
|
+
qpretrieve.set_ndarray_backend("cupy")
|
|
97
|
+
xpunwrap.set_ndarray_backend("cupy")
|
|
98
|
+
xp = xpunwrap.get_ndarray_backend()
|
|
99
|
+
|
|
100
|
+
fft_interface = qpretrieve.fourier.FFTFilterCupy
|
|
101
|
+
edata = np.load("./data/hologram_cell.npz")
|
|
102
|
+
holo = qpretrieve.OffAxisHologram(edata["data"], fft_interface)
|
|
103
|
+
bg = qpretrieve.OffAxisHologram(edata["bg_data"], fft_interface)
|
|
104
|
+
|
|
105
|
+
holo.run_pipeline(filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
|
|
106
|
+
bg.process_like(holo)
|
|
107
|
+
phase_wrapped = xp.asarray(holo.phase - bg.phase).astype(xp.float32)
|
|
108
|
+
|
|
109
|
+
# Unwrap the phase with all available algorithms
|
|
110
|
+
outputs = {}
|
|
111
|
+
for algo_name, algo in xpunwrap.algos_available().items():
|
|
112
|
+
outputs[algo_name] = algo(phase_wrapped)
|
|
113
|
+
skimage_out = outputs.get("algo_skimage_unwrap", None)
|
|
114
|
+
if skimage_out is not None:
|
|
115
|
+
outputs_no_skimage = {k: v for k, v in outputs.items() if k != "algo_skimage_unwrap"}
|
|
116
|
+
else:
|
|
117
|
+
outputs_no_skimage = outputs
|
|
118
|
+
|
|
119
|
+
# plot the wrapped and unwrapped phases
|
|
120
|
+
plt.style.use("dark_background")
|
|
121
|
+
fig, axes = plt.subplots(2, 3, figsize=(8, 6))
|
|
122
|
+
fig.suptitle("Field Retrieval + Phase Unwrapping (GPU)", fontsize=18)
|
|
123
|
+
axes = axes.flatten(order="F")
|
|
124
|
+
|
|
125
|
+
axes[0].imshow(phase_wrapped.get()[0])
|
|
126
|
+
axes[0].set_title("Wrapped Phase")
|
|
127
|
+
|
|
128
|
+
# With column-major flattening, bottom-right is index 5.
|
|
129
|
+
skimage_ax = axes[5]
|
|
130
|
+
|
|
131
|
+
# Fill remaining non-skimage algorithms first (slots 1..4).
|
|
132
|
+
plot_slots = [2, 3, 4]
|
|
133
|
+
for slot, (algo_name, arr) in zip(plot_slots, outputs_no_skimage.items()):
|
|
134
|
+
ax = axes[slot]
|
|
135
|
+
ax.imshow(arr.get()[0])
|
|
136
|
+
ax.set_title(f"Unwrapped\n{algo_name}")
|
|
137
|
+
|
|
138
|
+
if skimage_out is not None:
|
|
139
|
+
skimage_ax.imshow(skimage_out.get()[0])
|
|
140
|
+
skimage_ax.set_title("Unwrapped (CPU-only)\nalgo_skimage_unwrap")
|
|
141
|
+
else:
|
|
142
|
+
skimage_ax.text(0.5, 0.5, "skimage missing", ha="center", va="center")
|
|
143
|
+
skimage_ax.set_title("Unwrapped\nalgo_skimage_unwrap")
|
|
144
|
+
|
|
145
|
+
for ax in axes:
|
|
146
|
+
ax.set_axis_off()
|
|
147
|
+
|
|
148
|
+
plt.tight_layout(w_pad=4.5)
|
|
149
|
+
# plt.savefig("gpu_field_retr_phase_unwrapping.png")
|
|
150
|
+
plt.show()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+

|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
## Developers
|
|
157
|
+
|
|
158
|
+
Install everything you need with (for example for cuda12x)
|
|
159
|
+
```bash
|
|
160
|
+
pip install -e .[cupy-cuda12x] --group dev
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
Run the unit tests with `pytest`
|
|
165
|
+
```bash
|
|
166
|
+
pip install --group tests
|
|
167
|
+
pytest tests
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Build docs with `sphinx`
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
pip install --group docs
|
|
174
|
+
cd docs
|
|
175
|
+
sphinx-build . _build
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Check the docs locally by opening `docs/_build/index.html` file in your browser.
|
|
179
|
+
|
|
180
|
+
### Package management with `uv`
|
|
181
|
+
|
|
182
|
+
If you wish to use `uv` to handle package management, then you need to first
|
|
183
|
+
install `uv` and then run:
|
|
184
|
+
|
|
185
|
+
For example, for the optional `cupy-cuda12x`
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
uv sync -active --all-groups --all-extras
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Which should install all dev dependencies.
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
## Other phase unwrapping packages
|
|
195
|
+
|
|
196
|
+
Here are several phase unwrapping packages that exist in Python:
|
|
197
|
+
- https://github.com/blakedewey/phase_unwrap (CPU)
|
|
198
|
+
- Scikit-image (CPU)
|
|
199
|
+
|
|
200
|
+
## Use of AI
|
|
201
|
+
|
|
202
|
+
This package used Codex (mostly 5.4-Mini) to translate the source citations
|
|
203
|
+
(see documentation website) into Python code, as well as the mathematical derivations.
|
|
204
|
+
All code and documents were then human-verified and verified with Claude Opus,
|
|
205
|
+
and tested against known input data.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "xpunwrap"
|
|
3
|
+
description = "Phase unwrapping algorithms which are CPU and GPU agnostic"
|
|
4
|
+
authors = [
|
|
5
|
+
# In alphabetical order.
|
|
6
|
+
{name = "Conrad Möckel"},
|
|
7
|
+
{name = "Eoghan O'Connell"},
|
|
8
|
+
]
|
|
9
|
+
maintainers = [
|
|
10
|
+
{name = "Eoghan O'Connell", email="eoclives@hotmail.com"},
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
requires-python = ">=3.11, <4"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"numpy>=2.2.6",
|
|
18
|
+
]
|
|
19
|
+
dynamic = ["version"]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
# cupy has several installation options, depending on your CUDA Toolkit version
|
|
23
|
+
cupy-cuda12x = ["cupy-cuda12x"]
|
|
24
|
+
cupy-cuda13x = ["cupy-cuda13x"]
|
|
25
|
+
scikit-image = ["scikit-image>=0.20.0"]
|
|
26
|
+
# faster CPU Fourier transforms (numpy>=2 requires pyfftw>=0.15)
|
|
27
|
+
FFTW = ["pyfftw>=0.15.0"]
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
tests = [
|
|
31
|
+
"pytest>=9.0.2",
|
|
32
|
+
"pytest-benchmark>=5.2.3",
|
|
33
|
+
"qpretrieve[FFTW]>=0.6.1",
|
|
34
|
+
]
|
|
35
|
+
docs = [
|
|
36
|
+
"sphinx>=7.2",
|
|
37
|
+
"sphinx-rtd-theme>=2.0",
|
|
38
|
+
"myst-parser>=2.0",
|
|
39
|
+
]
|
|
40
|
+
examples = [
|
|
41
|
+
"matplotlib>=3.10.8",
|
|
42
|
+
"qpretrieve>=0.6.0",
|
|
43
|
+
"zarr>=3.1.0",
|
|
44
|
+
"scikit-image>=0.24.0",
|
|
45
|
+
"cupy-cuda12x",
|
|
46
|
+
]
|
|
47
|
+
dev = [
|
|
48
|
+
{ include-group = "tests" },
|
|
49
|
+
{ include-group = "docs" },
|
|
50
|
+
{ include-group = "examples" },
|
|
51
|
+
"flake8>=7.3.0",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools]
|
|
55
|
+
include-package-data = true
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.packages.find]
|
|
58
|
+
include = ["xpunwrap*"]
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
testpaths = ["tests"]
|
|
62
|
+
addopts = "--ignore=benchmarking"
|
xpunwrap-0.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
import xpunwrap
|
|
5
|
+
from xpunwrap.fourier import (
|
|
6
|
+
FFTEngineCupy,
|
|
7
|
+
FFTEngineNumpy,
|
|
8
|
+
FFTEnginePyFFTW,
|
|
9
|
+
get_best_engine,
|
|
10
|
+
get_fft_engine,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
pyfftw_missing = FFTEnginePyFFTW is None
|
|
14
|
+
cupy_missing = FFTEngineCupy is None or not FFTEngineCupy.is_available
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_select_numpy_when_forced(numpy_backend, preferred_engine):
|
|
18
|
+
preferred_engine("FFTEngineNumpy")
|
|
19
|
+
assert get_best_engine() is FFTEngineNumpy
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.skipif(pyfftw_missing, reason="pyfftw not installed")
|
|
23
|
+
def test_select_pyfftw_by_default(numpy_backend, preferred_engine):
|
|
24
|
+
preferred_engine(None)
|
|
25
|
+
assert get_best_engine() is FFTEnginePyFFTW
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_get_fft_engine_caches_instance(numpy_backend):
|
|
29
|
+
assert get_fft_engine() is get_fft_engine()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.skipif(cupy_missing, reason="cupy not installed")
|
|
33
|
+
def test_select_cupy_under_cupy_backend():
|
|
34
|
+
xpunwrap.set_ndarray_backend("cupy")
|
|
35
|
+
try:
|
|
36
|
+
assert get_best_engine() is FFTEngineCupy
|
|
37
|
+
finally:
|
|
38
|
+
xpunwrap.set_ndarray_backend("numpy")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.skipif(pyfftw_missing, reason="pyfftw not installed")
|
|
42
|
+
def test_pyfftw_matches_numpy(numpy_backend, preferred_engine):
|
|
43
|
+
# float64 isolates the FFT convention (normalisation) from single-precision
|
|
44
|
+
# rounding, so the two engines must agree to near machine precision.
|
|
45
|
+
rng = np.random.default_rng(7)
|
|
46
|
+
wrapped = rng.uniform(-np.pi, np.pi, size=(3, 32, 24))
|
|
47
|
+
|
|
48
|
+
preferred_engine("FFTEngineNumpy")
|
|
49
|
+
out_np = xpunwrap.algo_ls_poisson(wrapped)
|
|
50
|
+
|
|
51
|
+
preferred_engine("FFTEnginePyFFTW")
|
|
52
|
+
out_fftw = xpunwrap.algo_ls_poisson(wrapped)
|
|
53
|
+
|
|
54
|
+
assert np.allclose(out_np, out_fftw, rtol=1e-9, atol=1e-9)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.skipif(cupy_missing, reason="cupy not installed")
|
|
58
|
+
def test_cupy_matches_numpy(numpy_backend, preferred_engine):
|
|
59
|
+
rng = np.random.default_rng(7)
|
|
60
|
+
wrapped = rng.uniform(-np.pi, np.pi, size=(3, 32, 24))
|
|
61
|
+
|
|
62
|
+
preferred_engine("FFTEngineNumpy")
|
|
63
|
+
out_np = xpunwrap.algo_ls_poisson(wrapped)
|
|
64
|
+
|
|
65
|
+
preferred_engine("FFTEngineCupy")
|
|
66
|
+
xpunwrap.set_ndarray_backend("cupy")
|
|
67
|
+
_cp = xpunwrap.get_ndarray_backend()
|
|
68
|
+
out_cp = xpunwrap.algo_ls_poisson(_cp.asarray(wrapped))
|
|
69
|
+
|
|
70
|
+
assert np.allclose(out_np, out_cp.get(), rtol=1e-9, atol=1e-9)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from xpunwrap.algorithms import algo_ls_poisson
|
|
2
|
+
|
|
3
|
+
from tests.helper_methods import assert_shape_dtype
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_ls_poisson_stack(fake_phase_data):
|
|
7
|
+
phase_stack = fake_phase_data["stack"]
|
|
8
|
+
out = algo_ls_poisson(phase_stack)
|
|
9
|
+
assert_shape_dtype(out, phase_stack.shape)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_ls_poisson_2d(fake_phase_data):
|
|
13
|
+
phase_single = fake_phase_data["single"]
|
|
14
|
+
out = algo_ls_poisson(phase_single)
|
|
15
|
+
assert_shape_dtype(out, phase_single.shape)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_ls_poisson_stack_real_data(cell_phase_data):
|
|
19
|
+
phase_stack = cell_phase_data
|
|
20
|
+
out = algo_ls_poisson(phase_stack)
|
|
21
|
+
assert_shape_dtype(out, phase_stack.shape)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import xpunwrap
|
|
2
|
+
from xpunwrap.algorithms import algo_ls_poisson_pg
|
|
3
|
+
|
|
4
|
+
from tests.helper_methods import assert_shape_dtype
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_ls_poisson_periodic_grad_2d(fake_phase_data):
|
|
8
|
+
phase_single = fake_phase_data["single"]
|
|
9
|
+
out = algo_ls_poisson_pg(phase_single)
|
|
10
|
+
assert_shape_dtype(out, phase_single.shape)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_ls_poisson_periodic_grad_3d(fake_phase_data):
|
|
14
|
+
phase_stack = fake_phase_data["stack"]
|
|
15
|
+
out = algo_ls_poisson_pg(phase_stack)
|
|
16
|
+
assert_shape_dtype(out, phase_stack.shape)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_ls_poisson_periodic_grad_different_shape():
|
|
20
|
+
xp = xpunwrap.get_ndarray_backend()
|
|
21
|
+
rng = xp.random.default_rng(11)
|
|
22
|
+
phase_stack = rng.uniform(
|
|
23
|
+
-xp.pi, xp.pi, size=(2, 24, 20)).astype(xp.float32)
|
|
24
|
+
out = algo_ls_poisson_pg(phase_stack)
|
|
25
|
+
assert_shape_dtype(out, phase_stack.shape)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_ls_poisson_stack_real_data(cell_phase_data):
|
|
29
|
+
phase_stack = cell_phase_data
|
|
30
|
+
out = algo_ls_poisson_pg(phase_stack)
|
|
31
|
+
assert_shape_dtype(out, phase_stack.shape)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from xpunwrap.algorithms import algo_ls_weighted
|
|
2
|
+
|
|
3
|
+
from tests.helper_methods import assert_shape_dtype
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_ls_weighted_stack(fake_phase_data):
|
|
7
|
+
phase_stack = fake_phase_data["stack"]
|
|
8
|
+
out = algo_ls_weighted(
|
|
9
|
+
phase_stack,
|
|
10
|
+
border_thresh=1.0,
|
|
11
|
+
n_iter=5,
|
|
12
|
+
)
|
|
13
|
+
assert_shape_dtype(out, phase_stack.shape)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_ls_weighted_2d(fake_phase_data):
|
|
17
|
+
phase_single = fake_phase_data["single"]
|
|
18
|
+
out = algo_ls_weighted(
|
|
19
|
+
phase_single,
|
|
20
|
+
border_thresh=2.0,
|
|
21
|
+
n_iter=3,
|
|
22
|
+
)
|
|
23
|
+
assert_shape_dtype(out, phase_single.shape)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_ls_poisson_stack_real_data(cell_phase_data):
|
|
27
|
+
phase_stack = cell_phase_data
|
|
28
|
+
out = algo_ls_weighted(phase_stack)
|
|
29
|
+
assert_shape_dtype(out, phase_stack.shape)
|