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.
@@ -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
- # TODO: consider generalizing w/ phase_thick_3d.visualize_transfer_function
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, 2)
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
- wavelength_illumination,
16
- index_of_refraction_media,
17
- index_of_refraction_sample,
18
- sphere_radius,
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
- sphere
29
- * (index_of_refraction_sample - index_of_refraction_media)
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
- # TODO: consider generalizing w/ phase2Dto3D.visualize_TF
103
- arrays = [
104
- (torch.real(imag_potential_transfer_function), "Re(imag pot. TF)"),
105
- (torch.imag(imag_potential_transfer_function), "Im(imag pot. TF)"),
106
- (torch.real(real_potential_transfer_function), "Re(real pot. TF)"),
107
- (torch.imag(real_potential_transfer_function), "Im(real pot. TF)"),
108
- ]
109
-
110
- for array in arrays:
111
- lim = 0.5 * torch.max(torch.abs(array[0]))
112
- viewer.add_image(
113
- torch.fft.ifftshift(array[0]).cpu().numpy(),
114
- name=array[1],
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 isotropic_fluorescent_thick_3d.apply_transfer_function(
128
- zyx_object, real_potential_transfer_function, z_padding
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 * z_pixel_size / 4 / np.pi * wavelength_illumination
270
+ return f_real