waveorder 2.1.0__py3-none-any.whl → 2.2.0__py3-none-any.whl
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.
- waveorder/_version.py +2 -2
- waveorder/focus.py +36 -18
- waveorder/models/inplane_oriented_thick_pol3d.py +12 -12
- waveorder/models/inplane_oriented_thick_pol3d_vector.py +351 -0
- waveorder/models/isotropic_fluorescent_thick_3d.py +86 -33
- waveorder/models/isotropic_thin_3d.py +94 -32
- waveorder/models/phase_thick_3d.py +107 -63
- waveorder/optics.py +242 -28
- waveorder/sampling.py +94 -0
- waveorder/util.py +54 -2
- waveorder/{visual.py → visuals/jupyter_visuals.py} +2 -6
- waveorder/visuals/matplotlib_visuals.py +335 -0
- waveorder/visuals/napari_visuals.py +77 -0
- waveorder/visuals/utils.py +31 -0
- waveorder/waveorder_reconstructor.py +8 -7
- waveorder-2.2.0.dist-info/METADATA +186 -0
- waveorder-2.2.0.dist-info/RECORD +25 -0
- {waveorder-2.1.0.dist-info → waveorder-2.2.0.dist-info}/WHEEL +1 -1
- waveorder-2.1.0.dist-info/METADATA +0 -124
- waveorder-2.1.0.dist-info/RECORD +0 -20
- {waveorder-2.1.0.dist-info → waveorder-2.2.0.dist-info}/LICENSE +0 -0
- {waveorder-2.1.0.dist-info → waveorder-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
from typing import Literal, Tuple
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
3
4
|
import torch
|
|
4
5
|
from torch import Tensor
|
|
5
6
|
|
|
6
|
-
from waveorder import optics, util
|
|
7
|
-
|
|
7
|
+
from waveorder import optics, sampling, util
|
|
8
8
|
|
|
9
9
|
def generate_test_phantom(
|
|
10
|
-
yx_shape,
|
|
11
|
-
yx_pixel_size,
|
|
12
|
-
wavelength_illumination,
|
|
13
|
-
index_of_refraction_media,
|
|
14
|
-
index_of_refraction_sample,
|
|
15
|
-
sphere_radius,
|
|
16
|
-
):
|
|
10
|
+
yx_shape: Tuple[int, int],
|
|
11
|
+
yx_pixel_size: float,
|
|
12
|
+
wavelength_illumination: float,
|
|
13
|
+
index_of_refraction_media: float,
|
|
14
|
+
index_of_refraction_sample: float,
|
|
15
|
+
sphere_radius: float,
|
|
16
|
+
) -> Tuple[Tensor, Tensor]:
|
|
17
17
|
sphere, _, _ = util.generate_sphere_target(
|
|
18
18
|
(3,) + yx_shape,
|
|
19
19
|
yx_pixel_size,
|
|
@@ -34,15 +34,73 @@ def generate_test_phantom(
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def calculate_transfer_function(
|
|
37
|
-
yx_shape,
|
|
38
|
-
yx_pixel_size,
|
|
39
|
-
z_position_list,
|
|
40
|
-
wavelength_illumination,
|
|
41
|
-
index_of_refraction_media,
|
|
42
|
-
numerical_aperture_illumination,
|
|
43
|
-
numerical_aperture_detection,
|
|
44
|
-
invert_phase_contrast=False,
|
|
45
|
-
):
|
|
37
|
+
yx_shape: Tuple[int, int],
|
|
38
|
+
yx_pixel_size: float,
|
|
39
|
+
z_position_list: list,
|
|
40
|
+
wavelength_illumination: float,
|
|
41
|
+
index_of_refraction_media: float,
|
|
42
|
+
numerical_aperture_illumination: float,
|
|
43
|
+
numerical_aperture_detection: float,
|
|
44
|
+
invert_phase_contrast: bool = False,
|
|
45
|
+
) -> Tuple[Tensor, Tensor]:
|
|
46
|
+
transverse_nyquist = sampling.transverse_nyquist(
|
|
47
|
+
wavelength_illumination,
|
|
48
|
+
numerical_aperture_illumination,
|
|
49
|
+
numerical_aperture_detection,
|
|
50
|
+
)
|
|
51
|
+
yx_factor = int(np.ceil(yx_pixel_size / transverse_nyquist))
|
|
52
|
+
|
|
53
|
+
absorption_2d_to_3d_transfer_function, phase_2d_to_3d_transfer_function = (
|
|
54
|
+
_calculate_wrap_unsafe_transfer_function(
|
|
55
|
+
(
|
|
56
|
+
yx_shape[0] * yx_factor,
|
|
57
|
+
yx_shape[1] * yx_factor,
|
|
58
|
+
),
|
|
59
|
+
yx_pixel_size / yx_factor,
|
|
60
|
+
z_position_list,
|
|
61
|
+
wavelength_illumination,
|
|
62
|
+
index_of_refraction_media,
|
|
63
|
+
numerical_aperture_illumination,
|
|
64
|
+
numerical_aperture_detection,
|
|
65
|
+
invert_phase_contrast=invert_phase_contrast,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
absorption_2d_to_3d_transfer_function_out = torch.zeros(
|
|
70
|
+
(len(z_position_list),) + tuple(yx_shape), dtype=torch.complex64
|
|
71
|
+
)
|
|
72
|
+
phase_2d_to_3d_transfer_function_out = torch.zeros(
|
|
73
|
+
(len(z_position_list),) + tuple(yx_shape), dtype=torch.complex64
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
for z in range(len(z_position_list)):
|
|
77
|
+
absorption_2d_to_3d_transfer_function_out[z] = (
|
|
78
|
+
sampling.nd_fourier_central_cuboid(
|
|
79
|
+
absorption_2d_to_3d_transfer_function[z], yx_shape
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
phase_2d_to_3d_transfer_function_out[z] = (
|
|
83
|
+
sampling.nd_fourier_central_cuboid(
|
|
84
|
+
phase_2d_to_3d_transfer_function[z], yx_shape
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
absorption_2d_to_3d_transfer_function_out,
|
|
90
|
+
phase_2d_to_3d_transfer_function_out,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _calculate_wrap_unsafe_transfer_function(
|
|
95
|
+
yx_shape: Tuple[int, int],
|
|
96
|
+
yx_pixel_size: float,
|
|
97
|
+
z_position_list: list,
|
|
98
|
+
wavelength_illumination: float,
|
|
99
|
+
index_of_refraction_media: float,
|
|
100
|
+
numerical_aperture_illumination: float,
|
|
101
|
+
numerical_aperture_detection: float,
|
|
102
|
+
invert_phase_contrast: bool = False,
|
|
103
|
+
) -> Tuple[Tensor, Tensor]:
|
|
46
104
|
if invert_phase_contrast:
|
|
47
105
|
z_position_list = torch.flip(torch.tensor(z_position_list), dims=(0,))
|
|
48
106
|
|
|
@@ -90,10 +148,14 @@ def calculate_transfer_function(
|
|
|
90
148
|
|
|
91
149
|
def visualize_transfer_function(
|
|
92
150
|
viewer,
|
|
93
|
-
absorption_2d_to_3d_transfer_function,
|
|
94
|
-
phase_2d_to_3d_transfer_function,
|
|
95
|
-
):
|
|
96
|
-
|
|
151
|
+
absorption_2d_to_3d_transfer_function: Tensor,
|
|
152
|
+
phase_2d_to_3d_transfer_function: Tensor,
|
|
153
|
+
) -> None:
|
|
154
|
+
"""Note: unlike other `visualize_transfer_function` calls, this transfer
|
|
155
|
+
function is a mixed 3D-to-2D transfer function, so it cannot reuse
|
|
156
|
+
util.add_transfer_function_to_viewer. If more 3D-to-2D transfer functions
|
|
157
|
+
are added, consider refactoring.
|
|
158
|
+
"""
|
|
97
159
|
arrays = [
|
|
98
160
|
(torch.imag(absorption_2d_to_3d_transfer_function), "Im(absorb TF)"),
|
|
99
161
|
(torch.real(absorption_2d_to_3d_transfer_function), "Re(absorb TF)"),
|
|
@@ -110,14 +172,14 @@ def visualize_transfer_function(
|
|
|
110
172
|
contrast_limits=(-lim, lim),
|
|
111
173
|
scale=(1, 1, 1),
|
|
112
174
|
)
|
|
113
|
-
viewer.dims.order = (0, 1
|
|
175
|
+
viewer.dims.order = (2, 0, 1)
|
|
114
176
|
|
|
115
177
|
|
|
116
178
|
def visualize_point_spread_function(
|
|
117
179
|
viewer,
|
|
118
|
-
absorption_2d_to_3d_transfer_function,
|
|
119
|
-
phase_2d_to_3d_transfer_function,
|
|
120
|
-
):
|
|
180
|
+
absorption_2d_to_3d_transfer_function: Tensor,
|
|
181
|
+
phase_2d_to_3d_transfer_function: Tensor,
|
|
182
|
+
) -> None:
|
|
121
183
|
arrays = [
|
|
122
184
|
(torch.fft.ifftn(absorption_2d_to_3d_transfer_function), "absorb PSF"),
|
|
123
185
|
(torch.fft.ifftn(phase_2d_to_3d_transfer_function), "phase PSF"),
|
|
@@ -136,11 +198,11 @@ def visualize_point_spread_function(
|
|
|
136
198
|
|
|
137
199
|
|
|
138
200
|
def apply_transfer_function(
|
|
139
|
-
yx_absorption,
|
|
140
|
-
yx_phase,
|
|
141
|
-
phase_2d_to_3d_transfer_function,
|
|
142
|
-
absorption_2d_to_3d_transfer_function,
|
|
143
|
-
):
|
|
201
|
+
yx_absorption: Tensor,
|
|
202
|
+
yx_phase: Tensor,
|
|
203
|
+
phase_2d_to_3d_transfer_function: Tensor,
|
|
204
|
+
absorption_2d_to_3d_transfer_function: Tensor,
|
|
205
|
+
) -> Tensor:
|
|
144
206
|
# Very simple simulation, consider adding noise and bkg knobs
|
|
145
207
|
|
|
146
208
|
# simulate absorbing object
|
|
@@ -177,7 +239,7 @@ def apply_inverse_transfer_function(
|
|
|
177
239
|
TV_rho_strength: float = 1e-3,
|
|
178
240
|
TV_iterations: int = 10,
|
|
179
241
|
bg_filter: bool = True,
|
|
180
|
-
) -> Tuple[Tensor]:
|
|
242
|
+
) -> Tuple[Tensor, Tensor]:
|
|
181
243
|
"""Reconstructs absorption and phase from zyx_data and a pair of
|
|
182
244
|
3D-to-2D transfer functions named absorption_2d_to_3d_transfer_function and
|
|
183
245
|
phase_2d_to_3d_transfer_function, providing options for reconstruction
|
|
@@ -4,19 +4,19 @@ import numpy as np
|
|
|
4
4
|
import torch
|
|
5
5
|
from torch import Tensor
|
|
6
6
|
|
|
7
|
-
from waveorder import optics, util
|
|
7
|
+
from waveorder import optics, sampling, util
|
|
8
8
|
from waveorder.models import isotropic_fluorescent_thick_3d
|
|
9
|
+
from waveorder.visuals.napari_visuals import add_transfer_function_to_viewer
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def generate_test_phantom(
|
|
12
|
-
zyx_shape,
|
|
13
|
-
yx_pixel_size,
|
|
14
|
-
z_pixel_size,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
):
|
|
13
|
+
zyx_shape: tuple[int, int, int],
|
|
14
|
+
yx_pixel_size: float,
|
|
15
|
+
z_pixel_size: float,
|
|
16
|
+
index_of_refraction_media: float,
|
|
17
|
+
index_of_refraction_sample: float,
|
|
18
|
+
sphere_radius: float,
|
|
19
|
+
) -> np.ndarray:
|
|
20
20
|
sphere, _, _ = util.generate_sphere_target(
|
|
21
21
|
zyx_shape,
|
|
22
22
|
yx_pixel_size,
|
|
@@ -24,27 +24,78 @@ def generate_test_phantom(
|
|
|
24
24
|
radius=sphere_radius,
|
|
25
25
|
blur_size=2 * yx_pixel_size,
|
|
26
26
|
)
|
|
27
|
-
zyx_phase = (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* z_pixel_size
|
|
31
|
-
/ wavelength_illumination
|
|
32
|
-
) # phase in radians
|
|
27
|
+
zyx_phase = sphere * (
|
|
28
|
+
index_of_refraction_sample - index_of_refraction_media
|
|
29
|
+
) # refractive index increment
|
|
33
30
|
|
|
34
31
|
return zyx_phase
|
|
35
32
|
|
|
36
33
|
|
|
37
34
|
def calculate_transfer_function(
|
|
38
|
-
zyx_shape,
|
|
39
|
-
yx_pixel_size,
|
|
40
|
-
z_pixel_size,
|
|
41
|
-
wavelength_illumination,
|
|
42
|
-
z_padding,
|
|
43
|
-
index_of_refraction_media,
|
|
44
|
-
numerical_aperture_illumination,
|
|
45
|
-
numerical_aperture_detection,
|
|
46
|
-
invert_phase_contrast=False,
|
|
47
|
-
):
|
|
35
|
+
zyx_shape: tuple[int, int, int],
|
|
36
|
+
yx_pixel_size: float,
|
|
37
|
+
z_pixel_size: float,
|
|
38
|
+
wavelength_illumination: float,
|
|
39
|
+
z_padding: int,
|
|
40
|
+
index_of_refraction_media: float,
|
|
41
|
+
numerical_aperture_illumination: float,
|
|
42
|
+
numerical_aperture_detection: float,
|
|
43
|
+
invert_phase_contrast: bool = False,
|
|
44
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
45
|
+
transverse_nyquist = sampling.transverse_nyquist(
|
|
46
|
+
wavelength_illumination,
|
|
47
|
+
numerical_aperture_illumination,
|
|
48
|
+
numerical_aperture_detection,
|
|
49
|
+
)
|
|
50
|
+
axial_nyquist = sampling.axial_nyquist(
|
|
51
|
+
wavelength_illumination,
|
|
52
|
+
numerical_aperture_detection,
|
|
53
|
+
index_of_refraction_media,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
yx_factor = int(np.ceil(yx_pixel_size / transverse_nyquist))
|
|
57
|
+
z_factor = int(np.ceil(z_pixel_size / axial_nyquist))
|
|
58
|
+
|
|
59
|
+
real_potential_transfer_function, imag_potential_transfer_function = (
|
|
60
|
+
_calculate_wrap_unsafe_transfer_function(
|
|
61
|
+
(
|
|
62
|
+
zyx_shape[0] * z_factor,
|
|
63
|
+
zyx_shape[1] * yx_factor,
|
|
64
|
+
zyx_shape[2] * yx_factor,
|
|
65
|
+
),
|
|
66
|
+
yx_pixel_size / yx_factor,
|
|
67
|
+
z_pixel_size / z_factor,
|
|
68
|
+
wavelength_illumination,
|
|
69
|
+
z_padding,
|
|
70
|
+
index_of_refraction_media,
|
|
71
|
+
numerical_aperture_illumination,
|
|
72
|
+
numerical_aperture_detection,
|
|
73
|
+
invert_phase_contrast=invert_phase_contrast,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
zyx_out_shape = (zyx_shape[0] + 2 * z_padding,) + zyx_shape[1:]
|
|
78
|
+
return (
|
|
79
|
+
sampling.nd_fourier_central_cuboid(
|
|
80
|
+
real_potential_transfer_function, zyx_out_shape
|
|
81
|
+
),
|
|
82
|
+
sampling.nd_fourier_central_cuboid(
|
|
83
|
+
imag_potential_transfer_function, zyx_out_shape
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _calculate_wrap_unsafe_transfer_function(
|
|
89
|
+
zyx_shape: tuple[int, int, int],
|
|
90
|
+
yx_pixel_size: float,
|
|
91
|
+
z_pixel_size: float,
|
|
92
|
+
wavelength_illumination: float,
|
|
93
|
+
z_padding: int,
|
|
94
|
+
index_of_refraction_media: float,
|
|
95
|
+
numerical_aperture_illumination: float,
|
|
96
|
+
numerical_aperture_detection: float,
|
|
97
|
+
invert_phase_contrast: bool = False,
|
|
98
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
48
99
|
radial_frequencies = util.generate_radial_frequencies(
|
|
49
100
|
zyx_shape[1:], yx_pixel_size
|
|
50
101
|
)
|
|
@@ -76,6 +127,7 @@ def calculate_transfer_function(
|
|
|
76
127
|
det_pupil,
|
|
77
128
|
wavelength_illumination / index_of_refraction_media,
|
|
78
129
|
z_position_list,
|
|
130
|
+
axially_even=False,
|
|
79
131
|
)
|
|
80
132
|
|
|
81
133
|
(
|
|
@@ -95,37 +147,39 @@ def calculate_transfer_function(
|
|
|
95
147
|
|
|
96
148
|
def visualize_transfer_function(
|
|
97
149
|
viewer,
|
|
98
|
-
real_potential_transfer_function,
|
|
99
|
-
imag_potential_transfer_function,
|
|
100
|
-
zyx_scale,
|
|
101
|
-
):
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
colormap="bwr",
|
|
116
|
-
contrast_limits=(-lim, lim),
|
|
117
|
-
scale=1 / zyx_scale,
|
|
118
|
-
)
|
|
119
|
-
viewer.dims.order = (0, 1, 2)
|
|
150
|
+
real_potential_transfer_function: np.ndarray,
|
|
151
|
+
imag_potential_transfer_function: np.ndarray,
|
|
152
|
+
zyx_scale: tuple[float, float, float],
|
|
153
|
+
) -> None:
|
|
154
|
+
add_transfer_function_to_viewer(
|
|
155
|
+
viewer,
|
|
156
|
+
imag_potential_transfer_function,
|
|
157
|
+
zyx_scale,
|
|
158
|
+
layer_name="Imag pot. TF",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
add_transfer_function_to_viewer(
|
|
162
|
+
viewer,
|
|
163
|
+
real_potential_transfer_function,
|
|
164
|
+
zyx_scale,
|
|
165
|
+
layer_name="Real pot. TF",
|
|
166
|
+
)
|
|
120
167
|
|
|
121
168
|
|
|
122
169
|
def apply_transfer_function(
|
|
123
|
-
zyx_object, real_potential_transfer_function, z_padding
|
|
124
|
-
):
|
|
170
|
+
zyx_object: np.ndarray, real_potential_transfer_function: np.ndarray, z_padding: int, brightness: float
|
|
171
|
+
) -> np.ndarray:
|
|
125
172
|
# This simplified forward model only handles phase, so it resuses the fluorescence forward model
|
|
126
173
|
# TODO: extend to absorption
|
|
127
|
-
return
|
|
128
|
-
|
|
174
|
+
return (
|
|
175
|
+
isotropic_fluorescent_thick_3d.apply_transfer_function(
|
|
176
|
+
zyx_object,
|
|
177
|
+
real_potential_transfer_function,
|
|
178
|
+
z_padding,
|
|
179
|
+
background=0,
|
|
180
|
+
)
|
|
181
|
+
* brightness
|
|
182
|
+
+ brightness
|
|
129
183
|
)
|
|
130
184
|
|
|
131
185
|
|
|
@@ -134,14 +188,12 @@ def apply_inverse_transfer_function(
|
|
|
134
188
|
real_potential_transfer_function: Tensor,
|
|
135
189
|
imaginary_potential_transfer_function: Tensor,
|
|
136
190
|
z_padding: int,
|
|
137
|
-
z_pixel_size: float, # TODO: MOVE THIS PARAM TO OTF? (leaky param)
|
|
138
|
-
wavelength_illumination: float, # TOOD: MOVE THIS PARAM TO OTF? (leaky param)
|
|
139
191
|
absorption_ratio: float = 0.0,
|
|
140
192
|
reconstruction_algorithm: Literal["Tikhonov", "TV"] = "Tikhonov",
|
|
141
193
|
regularization_strength: float = 1e-3,
|
|
142
194
|
TV_rho_strength: float = 1e-3,
|
|
143
195
|
TV_iterations: int = 10,
|
|
144
|
-
):
|
|
196
|
+
) -> Tensor:
|
|
145
197
|
"""Reconstructs 3D phase from labelfree defocus zyx_data and a pair of
|
|
146
198
|
complex 3D transfer functions real_potential_transfer_function and
|
|
147
199
|
imag_potential_transfer_function, providing options for reconstruction
|
|
@@ -158,14 +210,6 @@ def apply_inverse_transfer_function(
|
|
|
158
210
|
z_padding : int
|
|
159
211
|
Padding for axial dimension. Use zero for defocus stacks that
|
|
160
212
|
extend ~3 PSF widths beyond the sample. Pad by ~3 PSF widths otherwise.
|
|
161
|
-
z_pixel_size : float
|
|
162
|
-
spacing between axial samples in sample space
|
|
163
|
-
units must be consistent with wavelength_illumination
|
|
164
|
-
TODO: move this leaky parameter to calculate_transfer_function
|
|
165
|
-
wavelength_illumination : float,
|
|
166
|
-
illumination wavelength
|
|
167
|
-
units must be consistent with z_pixel_size
|
|
168
|
-
TODO: move this leaky parameter to calculate_transfer_function
|
|
169
213
|
absorption_ratio : float, optional,
|
|
170
214
|
Absorption-to-phase ratio in the sample.
|
|
171
215
|
Use default 0 for purely phase objects.
|
|
@@ -223,4 +267,4 @@ def apply_inverse_transfer_function(
|
|
|
223
267
|
if z_padding != 0:
|
|
224
268
|
f_real = f_real[z_padding:-z_padding]
|
|
225
269
|
|
|
226
|
-
return f_real
|
|
270
|
+
return f_real
|