waveorder 2.2.1__py3-none-any.whl → 3.0.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 +16 -3
- waveorder/acq/__init__.py +0 -0
- waveorder/acq/acq_functions.py +166 -0
- waveorder/assets/HSV_legend.png +0 -0
- waveorder/assets/JCh_legend.png +0 -0
- waveorder/assets/waveorder_plugin_logo.png +0 -0
- waveorder/calib/Calibration.py +1512 -0
- waveorder/calib/Optimization.py +470 -0
- waveorder/calib/__init__.py +0 -0
- waveorder/calib/calibration_workers.py +464 -0
- waveorder/cli/apply_inverse_models.py +328 -0
- waveorder/cli/apply_inverse_transfer_function.py +379 -0
- waveorder/cli/compute_transfer_function.py +432 -0
- waveorder/cli/gui_widget.py +58 -0
- waveorder/cli/main.py +39 -0
- waveorder/cli/monitor.py +163 -0
- waveorder/cli/option_eat_all.py +47 -0
- waveorder/cli/parsing.py +122 -0
- waveorder/cli/printing.py +16 -0
- waveorder/cli/reconstruct.py +67 -0
- waveorder/cli/settings.py +187 -0
- waveorder/cli/utils.py +175 -0
- waveorder/filter.py +1 -2
- waveorder/focus.py +136 -25
- waveorder/io/__init__.py +0 -0
- waveorder/io/_reader.py +61 -0
- waveorder/io/core_functions.py +272 -0
- waveorder/io/metadata_reader.py +195 -0
- waveorder/io/utils.py +175 -0
- waveorder/io/visualization.py +160 -0
- waveorder/models/inplane_oriented_thick_pol3d_vector.py +3 -3
- waveorder/models/isotropic_fluorescent_thick_3d.py +92 -0
- waveorder/models/isotropic_fluorescent_thin_3d.py +331 -0
- waveorder/models/isotropic_thin_3d.py +73 -72
- waveorder/models/phase_thick_3d.py +103 -4
- waveorder/napari.yaml +36 -0
- waveorder/plugin/__init__.py +9 -0
- waveorder/plugin/gui.py +1094 -0
- waveorder/plugin/gui.ui +1440 -0
- waveorder/plugin/job_manager.py +42 -0
- waveorder/plugin/main_widget.py +1605 -0
- waveorder/plugin/tab_recon.py +3294 -0
- waveorder/scripts/__init__.py +0 -0
- waveorder/scripts/launch_napari.py +13 -0
- waveorder/scripts/repeat-cal-acq-rec.py +147 -0
- waveorder/scripts/repeat-calibration.py +31 -0
- waveorder/scripts/samples.py +85 -0
- waveorder/scripts/simulate_zarr_acq.py +204 -0
- waveorder/util.py +1 -1
- waveorder/visuals/napari_visuals.py +1 -1
- waveorder-3.0.0.dist-info/METADATA +350 -0
- waveorder-3.0.0.dist-info/RECORD +69 -0
- {waveorder-2.2.1.dist-info → waveorder-3.0.0.dist-info}/WHEEL +1 -1
- waveorder-3.0.0.dist-info/entry_points.txt +5 -0
- {waveorder-2.2.1.dist-info → waveorder-3.0.0.dist-info}/licenses/LICENSE +13 -1
- waveorder-2.2.1.dist-info/METADATA +0 -188
- waveorder-2.2.1.dist-info/RECORD +0 -27
- {waveorder-2.2.1.dist-info → waveorder-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module converts GUI-level reconstruction calls into library calls
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from waveorder.cli.settings import FluorescenceSettings, PhaseSettings
|
|
9
|
+
from waveorder.models import (
|
|
10
|
+
inplane_oriented_thick_pol3d,
|
|
11
|
+
inplane_oriented_thick_pol3d_vector,
|
|
12
|
+
isotropic_fluorescent_thick_3d,
|
|
13
|
+
isotropic_fluorescent_thin_3d,
|
|
14
|
+
isotropic_thin_3d,
|
|
15
|
+
phase_thick_3d,
|
|
16
|
+
)
|
|
17
|
+
from waveorder.stokes import _s12_to_orientation, stokes_after_adr
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def radians_to_nanometers(retardance_rad, wavelength_illumination_um):
|
|
21
|
+
"""
|
|
22
|
+
waveorder returns retardance in radians, while waveorder displays and saves
|
|
23
|
+
retardance in nanometers. This function converts from radians to nanometers
|
|
24
|
+
using the illumination wavelength (which is internally handled in um
|
|
25
|
+
in waveorder).
|
|
26
|
+
"""
|
|
27
|
+
return retardance_rad * wavelength_illumination_um * 1e3 / (2 * np.pi)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def birefringence(
|
|
31
|
+
czyx_data,
|
|
32
|
+
cyx_no_sample_data,
|
|
33
|
+
wavelength_illumination,
|
|
34
|
+
recon_dim,
|
|
35
|
+
biref_inverse_dict,
|
|
36
|
+
transfer_function_dataset,
|
|
37
|
+
):
|
|
38
|
+
# Load transfer function
|
|
39
|
+
intensity_to_stokes_matrix = torch.tensor(
|
|
40
|
+
transfer_function_dataset["intensity_to_stokes_matrix"][0, 0, 0]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Apply reconstruction
|
|
44
|
+
# (retardance, orientation, transmittance, depolarization)
|
|
45
|
+
reconstructed_parameters = (
|
|
46
|
+
inplane_oriented_thick_pol3d.apply_inverse_transfer_function(
|
|
47
|
+
czyx_data,
|
|
48
|
+
intensity_to_stokes_matrix,
|
|
49
|
+
cyx_no_sample_data=cyx_no_sample_data,
|
|
50
|
+
project_stokes_to_2d=(recon_dim == 2),
|
|
51
|
+
**biref_inverse_dict,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Convert retardance
|
|
56
|
+
retardance = radians_to_nanometers(
|
|
57
|
+
reconstructed_parameters[0], wavelength_illumination
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return torch.stack((retardance,) + reconstructed_parameters[1:])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def phase(
|
|
64
|
+
czyx_data,
|
|
65
|
+
recon_dim,
|
|
66
|
+
settings_phase: PhaseSettings,
|
|
67
|
+
transfer_function_dataset,
|
|
68
|
+
):
|
|
69
|
+
# [phase only, 2]
|
|
70
|
+
if recon_dim == 2:
|
|
71
|
+
# Load transfer functions
|
|
72
|
+
U = torch.from_numpy(transfer_function_dataset["singular_system_U"][0])
|
|
73
|
+
S = torch.from_numpy(
|
|
74
|
+
transfer_function_dataset["singular_system_S"][0, 0]
|
|
75
|
+
)
|
|
76
|
+
Vh = torch.from_numpy(
|
|
77
|
+
transfer_function_dataset["singular_system_Vh"][0]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Apply
|
|
81
|
+
(
|
|
82
|
+
absorption_yx,
|
|
83
|
+
phase_yx,
|
|
84
|
+
) = isotropic_thin_3d.apply_inverse_transfer_function(
|
|
85
|
+
czyx_data[0],
|
|
86
|
+
(U, S, Vh),
|
|
87
|
+
**settings_phase.apply_inverse.model_dump(),
|
|
88
|
+
)
|
|
89
|
+
# Stack to C1YX
|
|
90
|
+
output = phase_yx[None, None]
|
|
91
|
+
# TODO: Write phase and absorption to CZYX
|
|
92
|
+
# torch.stack((phase_yx[None], absorption_yx[None]))
|
|
93
|
+
|
|
94
|
+
# [phase only, 3]
|
|
95
|
+
elif recon_dim == 3:
|
|
96
|
+
# Load transfer functions
|
|
97
|
+
real_potential_transfer_function = torch.tensor(
|
|
98
|
+
transfer_function_dataset["real_potential_transfer_function"][0, 0]
|
|
99
|
+
)
|
|
100
|
+
imaginary_potential_transfer_function = torch.tensor(
|
|
101
|
+
transfer_function_dataset["imaginary_potential_transfer_function"][
|
|
102
|
+
0, 0
|
|
103
|
+
]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Apply
|
|
107
|
+
output = phase_thick_3d.apply_inverse_transfer_function(
|
|
108
|
+
czyx_data[0],
|
|
109
|
+
real_potential_transfer_function,
|
|
110
|
+
imaginary_potential_transfer_function,
|
|
111
|
+
z_padding=settings_phase.transfer_function.z_padding,
|
|
112
|
+
**settings_phase.apply_inverse.model_dump(),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Pad to CZYX
|
|
116
|
+
while output.ndim != 4:
|
|
117
|
+
output = torch.unsqueeze(output, 0)
|
|
118
|
+
|
|
119
|
+
return output
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def birefringence_and_phase(
|
|
123
|
+
czyx_data,
|
|
124
|
+
cyx_no_sample_data,
|
|
125
|
+
wavelength_illumination,
|
|
126
|
+
recon_dim,
|
|
127
|
+
biref_inverse_dict,
|
|
128
|
+
settings_phase: PhaseSettings,
|
|
129
|
+
transfer_function_dataset,
|
|
130
|
+
):
|
|
131
|
+
# Load birefringence transfer function
|
|
132
|
+
intensity_to_stokes_matrix = torch.tensor(
|
|
133
|
+
transfer_function_dataset["intensity_to_stokes_matrix"][0, 0, 0]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# [biref and phase, 2]
|
|
137
|
+
if recon_dim == 2:
|
|
138
|
+
# Load transfer functions
|
|
139
|
+
U = torch.from_numpy(
|
|
140
|
+
transfer_function_dataset["vector_singular_system_U"][0]
|
|
141
|
+
)
|
|
142
|
+
S = torch.from_numpy(
|
|
143
|
+
transfer_function_dataset["vector_singular_system_S"][0, 0]
|
|
144
|
+
)
|
|
145
|
+
Vh = torch.from_numpy(
|
|
146
|
+
transfer_function_dataset["vector_singular_system_Vh"][0]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Apply
|
|
150
|
+
reconstructed_parameters_2d = (
|
|
151
|
+
inplane_oriented_thick_pol3d.apply_inverse_transfer_function(
|
|
152
|
+
czyx_data,
|
|
153
|
+
intensity_to_stokes_matrix,
|
|
154
|
+
cyx_no_sample_data=cyx_no_sample_data,
|
|
155
|
+
project_stokes_to_2d=True,
|
|
156
|
+
**biref_inverse_dict,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
reconstructed_parameters_3d = (
|
|
161
|
+
inplane_oriented_thick_pol3d.apply_inverse_transfer_function(
|
|
162
|
+
czyx_data,
|
|
163
|
+
intensity_to_stokes_matrix,
|
|
164
|
+
cyx_no_sample_data=cyx_no_sample_data,
|
|
165
|
+
project_stokes_to_2d=False,
|
|
166
|
+
**biref_inverse_dict,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
brightfield_3d = reconstructed_parameters_3d[2]
|
|
171
|
+
|
|
172
|
+
(
|
|
173
|
+
_,
|
|
174
|
+
yx_phase,
|
|
175
|
+
) = isotropic_thin_3d.apply_inverse_transfer_function(
|
|
176
|
+
brightfield_3d,
|
|
177
|
+
(U, S, Vh),
|
|
178
|
+
**settings_phase.apply_inverse.model_dump(),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Convert retardance
|
|
182
|
+
retardance = radians_to_nanometers(
|
|
183
|
+
reconstructed_parameters_2d[0], wavelength_illumination
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
output = torch.stack(
|
|
187
|
+
(retardance,)
|
|
188
|
+
+ reconstructed_parameters_2d[1:]
|
|
189
|
+
+ (torch.unsqueeze(yx_phase, 0),)
|
|
190
|
+
) # CZYX
|
|
191
|
+
|
|
192
|
+
# [biref and phase, 3]
|
|
193
|
+
elif recon_dim == 3:
|
|
194
|
+
# Load phase transfer functions
|
|
195
|
+
intensity_to_stokes_matrix = torch.tensor(
|
|
196
|
+
transfer_function_dataset["intensity_to_stokes_matrix"][0, 0, 0]
|
|
197
|
+
)
|
|
198
|
+
# Load transfer functions
|
|
199
|
+
real_potential_transfer_function = torch.tensor(
|
|
200
|
+
transfer_function_dataset["real_potential_transfer_function"][0, 0]
|
|
201
|
+
)
|
|
202
|
+
imaginary_potential_transfer_function = torch.tensor(
|
|
203
|
+
transfer_function_dataset["imaginary_potential_transfer_function"][
|
|
204
|
+
0, 0
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Apply
|
|
209
|
+
reconstructed_parameters_3d = (
|
|
210
|
+
inplane_oriented_thick_pol3d.apply_inverse_transfer_function(
|
|
211
|
+
czyx_data,
|
|
212
|
+
intensity_to_stokes_matrix,
|
|
213
|
+
cyx_no_sample_data=cyx_no_sample_data,
|
|
214
|
+
project_stokes_to_2d=False,
|
|
215
|
+
**biref_inverse_dict,
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
brightfield_3d = reconstructed_parameters_3d[2]
|
|
220
|
+
|
|
221
|
+
zyx_phase = phase_thick_3d.apply_inverse_transfer_function(
|
|
222
|
+
brightfield_3d,
|
|
223
|
+
real_potential_transfer_function,
|
|
224
|
+
imaginary_potential_transfer_function,
|
|
225
|
+
z_padding=settings_phase.transfer_function.z_padding,
|
|
226
|
+
**settings_phase.apply_inverse.model_dump(),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Convert retardance
|
|
230
|
+
retardance = radians_to_nanometers(
|
|
231
|
+
reconstructed_parameters_3d[0], wavelength_illumination
|
|
232
|
+
)
|
|
233
|
+
# Load singular system
|
|
234
|
+
U = torch.tensor(
|
|
235
|
+
np.array(transfer_function_dataset["vector_singular_system_U"])
|
|
236
|
+
)
|
|
237
|
+
S = torch.tensor(
|
|
238
|
+
np.array(transfer_function_dataset["vector_singular_system_S"][0])
|
|
239
|
+
)
|
|
240
|
+
Vh = torch.tensor(
|
|
241
|
+
np.array(transfer_function_dataset["vector_singular_system_Vh"])
|
|
242
|
+
)
|
|
243
|
+
singular_system = (U, S, Vh)
|
|
244
|
+
|
|
245
|
+
# Convert retardance and orientation to stokes
|
|
246
|
+
stokes = stokes_after_adr(*reconstructed_parameters_3d)
|
|
247
|
+
|
|
248
|
+
stokes = torch.nan_to_num_(
|
|
249
|
+
torch.stack(stokes), nan=0.0
|
|
250
|
+
) # very rare nans from previous like
|
|
251
|
+
|
|
252
|
+
# Apply reconstruction
|
|
253
|
+
joint_recon_params = inplane_oriented_thick_pol3d_vector.apply_inverse_transfer_function(
|
|
254
|
+
szyx_data=stokes,
|
|
255
|
+
singular_system=singular_system,
|
|
256
|
+
intensity_to_stokes_matrix=None,
|
|
257
|
+
**settings_phase.apply_inverse.model_dump(),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
new_ret = (
|
|
261
|
+
joint_recon_params[1] ** 2 + joint_recon_params[2] ** 2
|
|
262
|
+
) ** (0.5)
|
|
263
|
+
new_ori = _s12_to_orientation(
|
|
264
|
+
joint_recon_params[1], -joint_recon_params[2]
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Convert stokes to retardance and orientation
|
|
268
|
+
# new_ret, new_ori, _ = estimate_ar_from_stokes012(*joint_recon_params)
|
|
269
|
+
|
|
270
|
+
# Convert retardance
|
|
271
|
+
new_ret_nm = radians_to_nanometers(new_ret, wavelength_illumination)
|
|
272
|
+
|
|
273
|
+
# Save
|
|
274
|
+
output = torch.stack(
|
|
275
|
+
(retardance,)
|
|
276
|
+
+ reconstructed_parameters_3d[1:]
|
|
277
|
+
+ (zyx_phase,)
|
|
278
|
+
+ (new_ret_nm,)
|
|
279
|
+
+ (new_ori,)
|
|
280
|
+
+ (joint_recon_params[0],)
|
|
281
|
+
)
|
|
282
|
+
return output
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def fluorescence(
|
|
286
|
+
czyx_data,
|
|
287
|
+
recon_dim,
|
|
288
|
+
settings_fluorescence: FluorescenceSettings,
|
|
289
|
+
transfer_function_dataset,
|
|
290
|
+
):
|
|
291
|
+
# [fluo, 2]
|
|
292
|
+
if recon_dim == 2:
|
|
293
|
+
# Load transfer functions for 2D thin fluorescence reconstruction
|
|
294
|
+
U = torch.from_numpy(transfer_function_dataset["singular_system_U"][0])
|
|
295
|
+
S = torch.from_numpy(
|
|
296
|
+
transfer_function_dataset["singular_system_S"][0, 0]
|
|
297
|
+
)
|
|
298
|
+
Vh = torch.from_numpy(
|
|
299
|
+
transfer_function_dataset["singular_system_Vh"][0]
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Apply 2D fluorescence reconstruction
|
|
303
|
+
output = isotropic_fluorescent_thin_3d.apply_inverse_transfer_function(
|
|
304
|
+
czyx_data[0],
|
|
305
|
+
(U, S, Vh),
|
|
306
|
+
**settings_fluorescence.apply_inverse.model_dump(),
|
|
307
|
+
)
|
|
308
|
+
# [fluo, 3]
|
|
309
|
+
elif recon_dim == 3:
|
|
310
|
+
# Load transfer functions
|
|
311
|
+
optical_transfer_function = torch.tensor(
|
|
312
|
+
transfer_function_dataset["optical_transfer_function"][0, 0]
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Apply
|
|
316
|
+
output = (
|
|
317
|
+
isotropic_fluorescent_thick_3d.apply_inverse_transfer_function(
|
|
318
|
+
czyx_data[0],
|
|
319
|
+
optical_transfer_function,
|
|
320
|
+
settings_fluorescence.transfer_function.z_padding,
|
|
321
|
+
**settings_fluorescence.apply_inverse.model_dump(),
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
# Pad to CZYX
|
|
325
|
+
while output.ndim != 4:
|
|
326
|
+
output = torch.unsqueeze(output, 0)
|
|
327
|
+
|
|
328
|
+
return output
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import warnings
|
|
3
|
+
from functools import partial
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import numpy as np
|
|
8
|
+
import torch
|
|
9
|
+
import torch.multiprocessing as mp
|
|
10
|
+
from iohub import open_ome_zarr
|
|
11
|
+
|
|
12
|
+
from waveorder.cli import apply_inverse_models
|
|
13
|
+
from waveorder.cli.parsing import (
|
|
14
|
+
config_filepath,
|
|
15
|
+
input_position_dirpaths,
|
|
16
|
+
output_dirpath,
|
|
17
|
+
processes_option,
|
|
18
|
+
transfer_function_dirpath,
|
|
19
|
+
)
|
|
20
|
+
from waveorder.cli.printing import echo_headline, echo_settings
|
|
21
|
+
from waveorder.cli.settings import ReconstructionSettings
|
|
22
|
+
from waveorder.cli.utils import (
|
|
23
|
+
apply_inverse_to_zyx_and_save,
|
|
24
|
+
create_empty_hcs_zarr,
|
|
25
|
+
generate_valid_position_key,
|
|
26
|
+
is_single_position_store,
|
|
27
|
+
)
|
|
28
|
+
from waveorder.io import utils
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _check_background_consistency(
|
|
32
|
+
background_shape, data_shape, input_channel_names
|
|
33
|
+
):
|
|
34
|
+
data_cyx_shape = (len(input_channel_names),) + data_shape[3:]
|
|
35
|
+
if background_shape != data_cyx_shape:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"Background shape {background_shape} does not match data shape {data_cyx_shape}"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_reconstruction_output_metadata(position_path: Path, config_path: Path):
|
|
42
|
+
# Get non-OME-Zarr plate-level metadata if it's available
|
|
43
|
+
plate_metadata = {}
|
|
44
|
+
try:
|
|
45
|
+
input_plate = open_ome_zarr(
|
|
46
|
+
position_path.parent.parent.parent, mode="r"
|
|
47
|
+
)
|
|
48
|
+
plate_metadata = dict(input_plate.zattrs)
|
|
49
|
+
plate_metadata.pop("plate")
|
|
50
|
+
except (RuntimeError, FileNotFoundError):
|
|
51
|
+
warnings.warn(
|
|
52
|
+
"Position is not part of a plate...no plate metadata will be copied."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Load the first position to infer dataset information
|
|
56
|
+
input_dataset = open_ome_zarr(str(position_path), mode="r")
|
|
57
|
+
T, _, Z, Y, X = input_dataset.data.shape
|
|
58
|
+
|
|
59
|
+
settings = utils.yaml_to_model(config_path, ReconstructionSettings)
|
|
60
|
+
|
|
61
|
+
# Simplify important settings names
|
|
62
|
+
recon_biref = settings.birefringence is not None
|
|
63
|
+
recon_phase = settings.phase is not None
|
|
64
|
+
recon_fluo = settings.fluorescence is not None
|
|
65
|
+
recon_dim = settings.reconstruction_dimension
|
|
66
|
+
|
|
67
|
+
# Prepare output dataset
|
|
68
|
+
channel_names = []
|
|
69
|
+
if recon_biref:
|
|
70
|
+
channel_names.append("Retardance")
|
|
71
|
+
channel_names.append("Orientation")
|
|
72
|
+
channel_names.append("BF")
|
|
73
|
+
channel_names.append("Pol")
|
|
74
|
+
if recon_phase:
|
|
75
|
+
if recon_dim == 2:
|
|
76
|
+
channel_names.append("Phase2D")
|
|
77
|
+
# channel_names.append("Absorption2D")
|
|
78
|
+
elif recon_dim == 3:
|
|
79
|
+
channel_names.append("Phase3D")
|
|
80
|
+
if recon_biref and recon_phase:
|
|
81
|
+
channel_names.append("Retardance_Joint_Decon")
|
|
82
|
+
channel_names.append("Orientation_Joint_Decon")
|
|
83
|
+
channel_names.append("Phase_Joint_Decon")
|
|
84
|
+
if recon_fluo:
|
|
85
|
+
fluor_name = settings.input_channel_names[0]
|
|
86
|
+
if recon_dim == 2:
|
|
87
|
+
channel_names.append(fluor_name + "_Density2D")
|
|
88
|
+
elif recon_dim == 3:
|
|
89
|
+
channel_names.append(fluor_name + "_Density3D")
|
|
90
|
+
|
|
91
|
+
if recon_dim == 2:
|
|
92
|
+
output_z_shape = 1
|
|
93
|
+
elif recon_dim == 3:
|
|
94
|
+
output_z_shape = input_dataset.data.shape[2]
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"shape": (T, len(channel_names), output_z_shape, Y, X),
|
|
98
|
+
"chunks": (1, 1, 1, Y, X),
|
|
99
|
+
"scale": input_dataset.scale,
|
|
100
|
+
"channel_names": channel_names,
|
|
101
|
+
"dtype": np.float32,
|
|
102
|
+
"plate_metadata": plate_metadata,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def apply_inverse_transfer_function_single_position(
|
|
107
|
+
input_position_dirpath: Path,
|
|
108
|
+
transfer_function_dirpath: Path,
|
|
109
|
+
config_filepath: Path,
|
|
110
|
+
output_position_dirpath: Path,
|
|
111
|
+
num_processes,
|
|
112
|
+
output_channel_names: list[str],
|
|
113
|
+
) -> None:
|
|
114
|
+
|
|
115
|
+
echo_headline("\nStarting reconstruction...")
|
|
116
|
+
|
|
117
|
+
# Load datasets
|
|
118
|
+
transfer_function_dataset = open_ome_zarr(transfer_function_dirpath)
|
|
119
|
+
input_dataset = open_ome_zarr(input_position_dirpath)
|
|
120
|
+
output_dataset = open_ome_zarr(output_position_dirpath, mode="r+")
|
|
121
|
+
|
|
122
|
+
# Load config file
|
|
123
|
+
settings = utils.yaml_to_model(config_filepath, ReconstructionSettings)
|
|
124
|
+
|
|
125
|
+
# Check input channel names
|
|
126
|
+
if not set(settings.input_channel_names).issubset(
|
|
127
|
+
input_dataset.channel_names
|
|
128
|
+
):
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"Each of the input_channel_names = {settings.input_channel_names} in {config_filepath} must appear in the dataset {input_position_dirpath} which currently contains channel_names = {input_dataset.channel_names}."
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Find input channel indices
|
|
134
|
+
input_channel_indices = []
|
|
135
|
+
for input_channel_name in settings.input_channel_names:
|
|
136
|
+
input_channel_indices.append(
|
|
137
|
+
input_dataset.channel_names.index(input_channel_name)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Find output channel indices
|
|
141
|
+
output_channel_indices = []
|
|
142
|
+
for output_channel_name in output_channel_names:
|
|
143
|
+
output_channel_indices.append(
|
|
144
|
+
output_dataset.channel_names.index(output_channel_name)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Find time indices
|
|
148
|
+
if settings.time_indices == "all":
|
|
149
|
+
time_indices = range(input_dataset.data.shape[0])
|
|
150
|
+
elif isinstance(settings.time_indices, list):
|
|
151
|
+
time_indices = settings.time_indices
|
|
152
|
+
elif isinstance(settings.time_indices, int):
|
|
153
|
+
time_indices = [settings.time_indices]
|
|
154
|
+
|
|
155
|
+
# Check for invalid times
|
|
156
|
+
time_ubound = input_dataset.data.shape[0] - 1
|
|
157
|
+
if np.max(time_indices) > time_ubound:
|
|
158
|
+
raise ValueError(
|
|
159
|
+
f"time_indices = {time_indices} includes a time index beyond the maximum index of the dataset = {time_ubound}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Simplify important settings names
|
|
163
|
+
recon_biref = settings.birefringence is not None
|
|
164
|
+
recon_phase = settings.phase is not None
|
|
165
|
+
recon_fluo = settings.fluorescence is not None
|
|
166
|
+
recon_dim = settings.reconstruction_dimension
|
|
167
|
+
|
|
168
|
+
# Prepare birefringence parameters
|
|
169
|
+
if settings.birefringence is not None:
|
|
170
|
+
# settings.birefringence has more parameters than waveorder needs,
|
|
171
|
+
# so this section converts the settings to a dict and separates the
|
|
172
|
+
# waveorder parameters (biref_inverse_dict) from the waveorder
|
|
173
|
+
# parameters (cyx_no_sample_data, and wavelength_illumination)
|
|
174
|
+
biref_inverse_dict = settings.birefringence.apply_inverse.model_dump()
|
|
175
|
+
|
|
176
|
+
# Resolve background path into array
|
|
177
|
+
background_path = biref_inverse_dict.pop("background_path")
|
|
178
|
+
if background_path != "":
|
|
179
|
+
cyx_no_sample_data = utils.load_background(background_path)
|
|
180
|
+
_check_background_consistency(
|
|
181
|
+
cyx_no_sample_data.shape,
|
|
182
|
+
input_dataset.data.shape,
|
|
183
|
+
settings.input_channel_names,
|
|
184
|
+
)
|
|
185
|
+
else:
|
|
186
|
+
cyx_no_sample_data = None
|
|
187
|
+
|
|
188
|
+
# Get illumination wavelength for retardance radians -> nanometers conversion
|
|
189
|
+
biref_wavelength_illumination = biref_inverse_dict.pop(
|
|
190
|
+
"wavelength_illumination"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Prepare the apply_inverse_model_function and its arguments
|
|
194
|
+
|
|
195
|
+
# [biref only]
|
|
196
|
+
if recon_biref and (not recon_phase):
|
|
197
|
+
echo_headline("Reconstructing birefringence with settings:")
|
|
198
|
+
echo_settings(settings.birefringence)
|
|
199
|
+
|
|
200
|
+
# Setup parameters for apply_inverse_to_zyx_and_save
|
|
201
|
+
apply_inverse_model_function = apply_inverse_models.birefringence
|
|
202
|
+
apply_inverse_args = {
|
|
203
|
+
"cyx_no_sample_data": cyx_no_sample_data,
|
|
204
|
+
"wavelength_illumination": biref_wavelength_illumination,
|
|
205
|
+
"recon_dim": recon_dim,
|
|
206
|
+
"biref_inverse_dict": biref_inverse_dict,
|
|
207
|
+
"transfer_function_dataset": transfer_function_dataset,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# [phase only]
|
|
211
|
+
if recon_phase and (not recon_biref):
|
|
212
|
+
echo_headline("Reconstructing phase with settings:")
|
|
213
|
+
echo_settings(settings.phase.apply_inverse)
|
|
214
|
+
|
|
215
|
+
# Setup parameters for apply_inverse_to_zyx_and_save
|
|
216
|
+
apply_inverse_model_function = apply_inverse_models.phase
|
|
217
|
+
apply_inverse_args = {
|
|
218
|
+
"recon_dim": recon_dim,
|
|
219
|
+
"settings_phase": settings.phase,
|
|
220
|
+
"transfer_function_dataset": transfer_function_dataset,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# [biref and phase]
|
|
224
|
+
if recon_biref and recon_phase:
|
|
225
|
+
echo_headline("Reconstructing birefringence and phase with settings:")
|
|
226
|
+
echo_settings(settings.birefringence.apply_inverse)
|
|
227
|
+
echo_settings(settings.phase.apply_inverse)
|
|
228
|
+
|
|
229
|
+
# Setup parameters for apply_inverse_to_zyx_and_save
|
|
230
|
+
apply_inverse_model_function = (
|
|
231
|
+
apply_inverse_models.birefringence_and_phase
|
|
232
|
+
)
|
|
233
|
+
apply_inverse_args = {
|
|
234
|
+
"cyx_no_sample_data": cyx_no_sample_data,
|
|
235
|
+
"wavelength_illumination": biref_wavelength_illumination,
|
|
236
|
+
"recon_dim": recon_dim,
|
|
237
|
+
"biref_inverse_dict": biref_inverse_dict,
|
|
238
|
+
"settings_phase": settings.phase,
|
|
239
|
+
"transfer_function_dataset": transfer_function_dataset,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# [fluo]
|
|
243
|
+
if recon_fluo:
|
|
244
|
+
echo_headline("Reconstructing fluorescence with settings:")
|
|
245
|
+
echo_settings(settings.fluorescence.apply_inverse)
|
|
246
|
+
|
|
247
|
+
# Setup parameters for apply_inverse_to_zyx_and_save
|
|
248
|
+
apply_inverse_model_function = apply_inverse_models.fluorescence
|
|
249
|
+
apply_inverse_args = {
|
|
250
|
+
"recon_dim": recon_dim,
|
|
251
|
+
"settings_fluorescence": settings.fluorescence,
|
|
252
|
+
"transfer_function_dataset": transfer_function_dataset,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# Make the partial function for apply inverse
|
|
256
|
+
partial_apply_inverse_to_zyx_and_save = partial(
|
|
257
|
+
apply_inverse_to_zyx_and_save,
|
|
258
|
+
apply_inverse_model_function,
|
|
259
|
+
input_dataset,
|
|
260
|
+
output_position_dirpath,
|
|
261
|
+
input_channel_indices,
|
|
262
|
+
output_channel_indices,
|
|
263
|
+
**apply_inverse_args,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Multiprocessing logic
|
|
267
|
+
if num_processes > 1:
|
|
268
|
+
# Loop through T, processing and writing as we go
|
|
269
|
+
click.echo(
|
|
270
|
+
f"\nStarting multiprocess pool with {num_processes} processes"
|
|
271
|
+
)
|
|
272
|
+
with mp.Pool(num_processes) as p:
|
|
273
|
+
p.starmap(
|
|
274
|
+
partial_apply_inverse_to_zyx_and_save,
|
|
275
|
+
itertools.product(time_indices),
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
for t_idx in time_indices:
|
|
279
|
+
partial_apply_inverse_to_zyx_and_save(t_idx)
|
|
280
|
+
|
|
281
|
+
# Save metadata at position level
|
|
282
|
+
output_dataset.zattrs["settings"] = settings.model_dump()
|
|
283
|
+
|
|
284
|
+
echo_headline(f"Closing {output_position_dirpath}\n")
|
|
285
|
+
|
|
286
|
+
output_dataset.close()
|
|
287
|
+
transfer_function_dataset.close()
|
|
288
|
+
input_dataset.close()
|
|
289
|
+
|
|
290
|
+
echo_headline(
|
|
291
|
+
f"Recreate this reconstruction with:\n$ waveorder apply-inv-tf {input_position_dirpath} {transfer_function_dirpath} -c {config_filepath} -o {output_position_dirpath}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def apply_inverse_transfer_function_cli(
|
|
296
|
+
input_position_dirpaths: list[Path],
|
|
297
|
+
transfer_function_dirpath: Path,
|
|
298
|
+
config_filepath: Path,
|
|
299
|
+
output_dirpath: Path,
|
|
300
|
+
num_processes,
|
|
301
|
+
) -> None:
|
|
302
|
+
# Prepare output store
|
|
303
|
+
output_metadata = get_reconstruction_output_metadata(
|
|
304
|
+
input_position_dirpaths[0], config_filepath
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Generate position keys - use valid HCS keys for single-position stores
|
|
308
|
+
position_keys = []
|
|
309
|
+
for i, input_path in enumerate(input_position_dirpaths):
|
|
310
|
+
if is_single_position_store(input_path):
|
|
311
|
+
position_key = generate_valid_position_key(i)
|
|
312
|
+
else:
|
|
313
|
+
# Use original HCS plate structure
|
|
314
|
+
position_key = input_path.parts[-3:]
|
|
315
|
+
position_keys.append(position_key)
|
|
316
|
+
|
|
317
|
+
create_empty_hcs_zarr(
|
|
318
|
+
store_path=output_dirpath,
|
|
319
|
+
position_keys=position_keys,
|
|
320
|
+
**output_metadata,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Initialize torch threads
|
|
324
|
+
if num_processes > 1:
|
|
325
|
+
torch.set_num_threads(1)
|
|
326
|
+
torch.set_num_interop_threads(1)
|
|
327
|
+
|
|
328
|
+
# Loop through positions
|
|
329
|
+
for i, input_position_dirpath in enumerate(input_position_dirpaths):
|
|
330
|
+
# Use the same position key generation logic
|
|
331
|
+
if is_single_position_store(input_position_dirpath):
|
|
332
|
+
position_key = generate_valid_position_key(i)
|
|
333
|
+
else:
|
|
334
|
+
position_key = input_position_dirpath.parts[-3:]
|
|
335
|
+
|
|
336
|
+
output_position_path = output_dirpath / Path(*position_key)
|
|
337
|
+
|
|
338
|
+
apply_inverse_transfer_function_single_position(
|
|
339
|
+
input_position_dirpath,
|
|
340
|
+
transfer_function_dirpath,
|
|
341
|
+
config_filepath,
|
|
342
|
+
output_position_path,
|
|
343
|
+
num_processes,
|
|
344
|
+
output_metadata["channel_names"],
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@click.command("apply-inv-tf")
|
|
349
|
+
@input_position_dirpaths()
|
|
350
|
+
@transfer_function_dirpath()
|
|
351
|
+
@config_filepath()
|
|
352
|
+
@output_dirpath()
|
|
353
|
+
@processes_option(default=1)
|
|
354
|
+
def _apply_inverse_transfer_function_cli(
|
|
355
|
+
input_position_dirpaths: list[Path],
|
|
356
|
+
transfer_function_dirpath: Path,
|
|
357
|
+
config_filepath: Path,
|
|
358
|
+
output_dirpath: Path,
|
|
359
|
+
num_processes,
|
|
360
|
+
) -> None:
|
|
361
|
+
"""
|
|
362
|
+
Apply an inverse transfer function to a dataset using a configuration file.
|
|
363
|
+
|
|
364
|
+
Applies a transfer function to all positions in the list `input-position-dirpaths`,
|
|
365
|
+
so all positions must have the same TCZYX shape.
|
|
366
|
+
|
|
367
|
+
Appends channels to ./output.zarr, so multiple reconstructions can fill a single store.
|
|
368
|
+
|
|
369
|
+
See /examples for example configuration files.
|
|
370
|
+
|
|
371
|
+
>> waveorder apply-inv-tf -i ./input.zarr/*/*/* -t ./transfer-function.zarr -c /examples/birefringence.yml -o ./output.zarr
|
|
372
|
+
"""
|
|
373
|
+
apply_inverse_transfer_function_cli(
|
|
374
|
+
input_position_dirpaths,
|
|
375
|
+
transfer_function_dirpath,
|
|
376
|
+
config_filepath,
|
|
377
|
+
output_dirpath,
|
|
378
|
+
num_processes,
|
|
379
|
+
)
|