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 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.
@@ -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
+ ![gpu_field_retr_phase_unwrapping.png](./examples/gpu_field_retr_phase_unwrapping.png)
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.
@@ -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
+ ![gpu_field_retr_phase_unwrapping.png](./examples/gpu_field_retr_phase_unwrapping.png)
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"
@@ -0,0 +1,9 @@
1
+ [flake8]
2
+ per-file-ignores =
3
+ __init__.py:F401
4
+ examples/*.py:E501
5
+
6
+ [egg_info]
7
+ tag_build =
8
+ tag_date = 0
9
+
@@ -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)