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,432 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import numpy as np
|
|
5
|
+
from iohub.ngff import Position, open_ome_zarr
|
|
6
|
+
|
|
7
|
+
from waveorder import focus
|
|
8
|
+
from waveorder.cli.parsing import (
|
|
9
|
+
config_filepath,
|
|
10
|
+
input_position_dirpaths,
|
|
11
|
+
output_dirpath,
|
|
12
|
+
)
|
|
13
|
+
from waveorder.cli.printing import echo_headline, echo_settings
|
|
14
|
+
from waveorder.cli.settings import ReconstructionSettings
|
|
15
|
+
from waveorder.io import utils
|
|
16
|
+
from waveorder.models import (
|
|
17
|
+
inplane_oriented_thick_pol3d,
|
|
18
|
+
inplane_oriented_thick_pol3d_vector,
|
|
19
|
+
isotropic_fluorescent_thick_3d,
|
|
20
|
+
isotropic_fluorescent_thin_3d,
|
|
21
|
+
isotropic_thin_3d,
|
|
22
|
+
phase_thick_3d,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _position_list_from_shape_scale_offset(
|
|
27
|
+
shape: int, scale: float, offset: float
|
|
28
|
+
) -> list:
|
|
29
|
+
"""
|
|
30
|
+
Generates a list of positions based on the given array shape, pixel size (scale), and offset.
|
|
31
|
+
|
|
32
|
+
Examples
|
|
33
|
+
--------
|
|
34
|
+
>>> _position_list_from_shape_scale_offset(5, 1.0, 0.0)
|
|
35
|
+
[2.0, 1.0, 0.0, -1.0, -2.0]
|
|
36
|
+
>>> _position_list_from_shape_scale_offset(4, 0.5, 1.0)
|
|
37
|
+
[1.5, 1.0, 0.5, 0.0]
|
|
38
|
+
"""
|
|
39
|
+
return list((-np.arange(shape) + (shape // 2) + offset) * scale)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def generate_and_save_vector_birefringence_transfer_function(
|
|
43
|
+
settings: ReconstructionSettings, dataset: Position, zyx_shape: tuple
|
|
44
|
+
):
|
|
45
|
+
"""Generates and saves the vector birefringence transfer function
|
|
46
|
+
to the dataset, based on the settings.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
settings : ReconstructionSettings
|
|
51
|
+
dataset : NGFF Node
|
|
52
|
+
The dataset that will be updated.
|
|
53
|
+
zyx_shape : tuple
|
|
54
|
+
A tuple of integers specifying the input data's shape in (Z, Y, X) order
|
|
55
|
+
"""
|
|
56
|
+
echo_headline(
|
|
57
|
+
"Generating vector birefringence transfer function with settings:"
|
|
58
|
+
)
|
|
59
|
+
echo_settings(settings.birefringence.transfer_function)
|
|
60
|
+
echo_settings(settings.phase.transfer_function)
|
|
61
|
+
|
|
62
|
+
num_elements = np.array(zyx_shape).prod()
|
|
63
|
+
max_tf_elements = 1e7 # empirical, based on memory usage
|
|
64
|
+
transverse_downsample_factor = np.ceil(
|
|
65
|
+
np.sqrt(num_elements / max_tf_elements)
|
|
66
|
+
)
|
|
67
|
+
echo_headline(
|
|
68
|
+
f"Downsampling transfer function in X and Y by {transverse_downsample_factor}x"
|
|
69
|
+
)
|
|
70
|
+
phase_settings_dict = settings.phase.transfer_function.model_dump()
|
|
71
|
+
phase_settings_dict.pop("z_focus_offset") # not used in 3D
|
|
72
|
+
|
|
73
|
+
sfZYX_transfer_function, _, singular_system = (
|
|
74
|
+
inplane_oriented_thick_pol3d_vector.calculate_transfer_function(
|
|
75
|
+
zyx_shape=zyx_shape,
|
|
76
|
+
scheme=str(len(settings.input_channel_names)) + "-State",
|
|
77
|
+
**settings.birefringence.transfer_function.model_dump(),
|
|
78
|
+
**phase_settings_dict,
|
|
79
|
+
fourier_oversample_factor=int(transverse_downsample_factor),
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
U, S, Vh = singular_system
|
|
84
|
+
chunks = (1, 1, 1, zyx_shape[1], zyx_shape[2])
|
|
85
|
+
|
|
86
|
+
# Add dummy channels
|
|
87
|
+
for i in range(3):
|
|
88
|
+
dataset.append_channel(f"ch{i}")
|
|
89
|
+
|
|
90
|
+
dataset.create_image(
|
|
91
|
+
"vector_transfer_function",
|
|
92
|
+
sfZYX_transfer_function.cpu().numpy(),
|
|
93
|
+
chunks=chunks,
|
|
94
|
+
)
|
|
95
|
+
dataset.create_image(
|
|
96
|
+
"vector_singular_system_U",
|
|
97
|
+
U.cpu().numpy(),
|
|
98
|
+
chunks=chunks,
|
|
99
|
+
)
|
|
100
|
+
dataset.create_image(
|
|
101
|
+
"vector_singular_system_S",
|
|
102
|
+
S[None].cpu().numpy(),
|
|
103
|
+
chunks=chunks,
|
|
104
|
+
)
|
|
105
|
+
dataset.create_image(
|
|
106
|
+
"vector_singular_system_Vh",
|
|
107
|
+
Vh.cpu().numpy(),
|
|
108
|
+
chunks=chunks,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def generate_and_save_birefringence_transfer_function(
|
|
113
|
+
settings: ReconstructionSettings, dataset
|
|
114
|
+
):
|
|
115
|
+
"""Generates and saves the birefringence transfer function to the dataset, based on the settings.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
settings: ReconstructionSettings
|
|
120
|
+
dataset: NGFF Node
|
|
121
|
+
The dataset that will be updated.
|
|
122
|
+
"""
|
|
123
|
+
echo_headline("Generating birefringence transfer function with settings:")
|
|
124
|
+
echo_settings(settings.birefringence.transfer_function)
|
|
125
|
+
|
|
126
|
+
# Calculate transfer functions
|
|
127
|
+
intensity_to_stokes_matrix = (
|
|
128
|
+
inplane_oriented_thick_pol3d.calculate_transfer_function(
|
|
129
|
+
scheme=str(len(settings.input_channel_names)) + "-State",
|
|
130
|
+
**settings.birefringence.transfer_function.model_dump(),
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
# Save
|
|
134
|
+
dataset["intensity_to_stokes_matrix"] = (
|
|
135
|
+
intensity_to_stokes_matrix.cpu().numpy()[None, None, None, ...]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def generate_and_save_phase_transfer_function(
|
|
140
|
+
settings: ReconstructionSettings,
|
|
141
|
+
dataset: Position,
|
|
142
|
+
zyx_shape: tuple,
|
|
143
|
+
):
|
|
144
|
+
"""Generates and saves the phase transfer function to the dataset, based on the settings.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
settings: ReconstructionSettings
|
|
149
|
+
dataset: Position
|
|
150
|
+
The dataset that will be updated.
|
|
151
|
+
zyx_shape : tuple
|
|
152
|
+
A tuple of integers specifying the input data's shape in (Z, Y, X) order
|
|
153
|
+
"""
|
|
154
|
+
echo_headline("Generating phase transfer function with settings:")
|
|
155
|
+
echo_settings(settings.phase.transfer_function)
|
|
156
|
+
|
|
157
|
+
settings_dict = settings.phase.transfer_function.model_dump()
|
|
158
|
+
if settings.reconstruction_dimension == 2:
|
|
159
|
+
# Convert zyx_shape and z_pixel_size into yx_shape and z_position_list
|
|
160
|
+
settings_dict["yx_shape"] = [zyx_shape[1], zyx_shape[2]]
|
|
161
|
+
settings_dict["z_position_list"] = (
|
|
162
|
+
_position_list_from_shape_scale_offset(
|
|
163
|
+
shape=zyx_shape[0],
|
|
164
|
+
scale=settings_dict["z_pixel_size"],
|
|
165
|
+
offset=settings_dict["z_focus_offset"],
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Remove unused parameters
|
|
170
|
+
settings_dict.pop("z_pixel_size")
|
|
171
|
+
settings_dict.pop("z_padding")
|
|
172
|
+
settings_dict.pop("z_focus_offset")
|
|
173
|
+
|
|
174
|
+
# Calculate transfer functions
|
|
175
|
+
(
|
|
176
|
+
absorption_transfer_function,
|
|
177
|
+
phase_transfer_function,
|
|
178
|
+
) = isotropic_thin_3d.calculate_transfer_function(
|
|
179
|
+
**settings_dict,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Calculate singular system
|
|
183
|
+
U, S, Vh = isotropic_thin_3d.calculate_singular_system(
|
|
184
|
+
absorption_transfer_function,
|
|
185
|
+
phase_transfer_function,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Save
|
|
189
|
+
dataset.create_image(
|
|
190
|
+
"singular_system_U",
|
|
191
|
+
U.cpu().numpy()[None],
|
|
192
|
+
)
|
|
193
|
+
dataset.create_image(
|
|
194
|
+
"singular_system_S",
|
|
195
|
+
S.cpu().numpy()[None, None],
|
|
196
|
+
)
|
|
197
|
+
dataset.create_image(
|
|
198
|
+
"singular_system_Vh",
|
|
199
|
+
Vh.cpu().numpy()[None],
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
elif settings.reconstruction_dimension == 3:
|
|
203
|
+
settings_dict.pop("z_focus_offset") # not used in 3D
|
|
204
|
+
|
|
205
|
+
# Calculate transfer functions
|
|
206
|
+
(
|
|
207
|
+
real_potential_transfer_function,
|
|
208
|
+
imaginary_potential_transfer_function,
|
|
209
|
+
) = phase_thick_3d.calculate_transfer_function(
|
|
210
|
+
zyx_shape=zyx_shape,
|
|
211
|
+
**settings_dict,
|
|
212
|
+
)
|
|
213
|
+
# Save
|
|
214
|
+
dataset.create_image(
|
|
215
|
+
"real_potential_transfer_function",
|
|
216
|
+
real_potential_transfer_function.cpu().numpy()[None, None, ...],
|
|
217
|
+
chunks=(1, 1, 1, zyx_shape[1], zyx_shape[2]),
|
|
218
|
+
)
|
|
219
|
+
dataset.create_image(
|
|
220
|
+
"imaginary_potential_transfer_function",
|
|
221
|
+
imaginary_potential_transfer_function.cpu().numpy()[
|
|
222
|
+
None, None, ...
|
|
223
|
+
],
|
|
224
|
+
chunks=(1, 1, 1, zyx_shape[1], zyx_shape[2]),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def generate_and_save_fluorescence_transfer_function(
|
|
229
|
+
settings: ReconstructionSettings,
|
|
230
|
+
dataset: Position,
|
|
231
|
+
zyx_shape: tuple,
|
|
232
|
+
):
|
|
233
|
+
"""Generates and saves the fluorescence transfer function to the dataset, based on the settings.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
settings: ReconstructionSettings
|
|
238
|
+
dataset: Position
|
|
239
|
+
The dataset that will be updated.
|
|
240
|
+
zyx_shape : tuple
|
|
241
|
+
A tuple of integers specifying the input data's shape in (Z, Y, X) order
|
|
242
|
+
"""
|
|
243
|
+
echo_headline("Generating fluorescence transfer function with settings:")
|
|
244
|
+
echo_settings(settings.fluorescence.transfer_function)
|
|
245
|
+
settings_dict = settings.fluorescence.transfer_function.model_dump()
|
|
246
|
+
|
|
247
|
+
if settings.reconstruction_dimension == 2:
|
|
248
|
+
# Convert zyx_shape and z_pixel_size into yx_shape and z_position_list
|
|
249
|
+
settings_dict["yx_shape"] = [zyx_shape[1], zyx_shape[2]]
|
|
250
|
+
settings_dict["z_position_list"] = (
|
|
251
|
+
_position_list_from_shape_scale_offset(
|
|
252
|
+
shape=zyx_shape[0],
|
|
253
|
+
scale=settings_dict["z_pixel_size"],
|
|
254
|
+
offset=settings_dict["z_focus_offset"],
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Remove unused parameters
|
|
259
|
+
settings_dict.pop("z_pixel_size")
|
|
260
|
+
settings_dict.pop("z_padding")
|
|
261
|
+
settings_dict.pop("z_focus_offset")
|
|
262
|
+
|
|
263
|
+
# Calculate 2D fluorescence transfer functions
|
|
264
|
+
fluorescent_2d_to_3d_transfer_function = (
|
|
265
|
+
isotropic_fluorescent_thin_3d.calculate_transfer_function(
|
|
266
|
+
**settings_dict,
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Calculate singular system for 2D reconstruction
|
|
271
|
+
U, S, Vh = isotropic_fluorescent_thin_3d.calculate_singular_system(
|
|
272
|
+
fluorescent_2d_to_3d_transfer_function
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Get yx_shape for chunk sizes
|
|
276
|
+
yx_shape = zyx_shape[1:]
|
|
277
|
+
|
|
278
|
+
# Save singular system components
|
|
279
|
+
dataset.create_image(
|
|
280
|
+
"singular_system_U",
|
|
281
|
+
U.cpu().numpy()[None, ...],
|
|
282
|
+
chunks=(1, 1, 1, yx_shape[0], yx_shape[1]),
|
|
283
|
+
)
|
|
284
|
+
dataset.create_image(
|
|
285
|
+
"singular_system_S",
|
|
286
|
+
S.cpu().numpy()[None, None, ...],
|
|
287
|
+
chunks=(1, 1, 1, yx_shape[0], yx_shape[1]),
|
|
288
|
+
)
|
|
289
|
+
dataset.create_image(
|
|
290
|
+
"singular_system_Vh",
|
|
291
|
+
Vh.cpu().numpy()[None, ...],
|
|
292
|
+
chunks=(1, 1, zyx_shape[0], yx_shape[0], yx_shape[1]),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
elif settings.reconstruction_dimension == 3:
|
|
296
|
+
# Remove unused parameters for 3D
|
|
297
|
+
settings_dict.pop("z_focus_offset")
|
|
298
|
+
|
|
299
|
+
# Calculate transfer functions
|
|
300
|
+
optical_transfer_function = (
|
|
301
|
+
isotropic_fluorescent_thick_3d.calculate_transfer_function(
|
|
302
|
+
zyx_shape=zyx_shape,
|
|
303
|
+
**settings_dict,
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
# Save
|
|
307
|
+
dataset.create_image(
|
|
308
|
+
"optical_transfer_function",
|
|
309
|
+
optical_transfer_function.cpu().numpy()[None, None, ...],
|
|
310
|
+
chunks=(1, 1, 1, zyx_shape[1], zyx_shape[2]),
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def compute_transfer_function_cli(
|
|
315
|
+
input_position_dirpath: Path,
|
|
316
|
+
config_filepath: Path,
|
|
317
|
+
output_dirpath: Path,
|
|
318
|
+
) -> None:
|
|
319
|
+
"""CLI command to compute the transfer function given a configuration file path
|
|
320
|
+
and a desired output path.
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
# Load config file
|
|
324
|
+
settings = utils.yaml_to_model(config_filepath, ReconstructionSettings)
|
|
325
|
+
|
|
326
|
+
echo_headline(
|
|
327
|
+
f"Generating transfer functions and storing in {output_dirpath}\n"
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Read shape from input dataset
|
|
331
|
+
input_dataset = open_ome_zarr(
|
|
332
|
+
input_position_dirpath, layout="fov", mode="r"
|
|
333
|
+
)
|
|
334
|
+
zyx_shape = input_dataset.data.shape[
|
|
335
|
+
2:
|
|
336
|
+
] # only loads a single position "0"
|
|
337
|
+
|
|
338
|
+
# Check input channel names
|
|
339
|
+
if not set(settings.input_channel_names).issubset(
|
|
340
|
+
input_dataset.channel_names
|
|
341
|
+
):
|
|
342
|
+
raise ValueError(
|
|
343
|
+
f"Each of the input_channel_names = {settings.input_channel_names} in {config_filepath} must appear in the dataset {input_position_dirpaths[0]} which currently contains channel_names = {input_dataset.channel_names}."
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Find in-focus slices for 2D reconstruction in "auto" mode
|
|
347
|
+
if (
|
|
348
|
+
settings.phase is not None
|
|
349
|
+
and settings.reconstruction_dimension == 2
|
|
350
|
+
and settings.phase.transfer_function.z_focus_offset == "auto"
|
|
351
|
+
):
|
|
352
|
+
|
|
353
|
+
c_idx = input_dataset.get_channel_index(
|
|
354
|
+
settings.input_channel_names[0]
|
|
355
|
+
)
|
|
356
|
+
zyx_array = input_dataset["0"][0, c_idx]
|
|
357
|
+
|
|
358
|
+
in_focus_index = focus.focus_from_transverse_band(
|
|
359
|
+
zyx_array,
|
|
360
|
+
NA_det=settings.phase.transfer_function.numerical_aperture_detection,
|
|
361
|
+
lambda_ill=settings.phase.transfer_function.wavelength_illumination,
|
|
362
|
+
pixel_size=settings.phase.transfer_function.yx_pixel_size,
|
|
363
|
+
mode="min",
|
|
364
|
+
polynomial_fit_order=4,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
z_focus_offset = in_focus_index - (zyx_shape[0] // 2)
|
|
368
|
+
settings.phase.transfer_function.z_focus_offset = z_focus_offset
|
|
369
|
+
print("Found z_focus_offset:", z_focus_offset)
|
|
370
|
+
|
|
371
|
+
# Prepare output dataset
|
|
372
|
+
num_channels = (
|
|
373
|
+
2 if settings.reconstruction_dimension == 2 else 1
|
|
374
|
+
) # space for SVD
|
|
375
|
+
output_dataset = open_ome_zarr(
|
|
376
|
+
output_dirpath,
|
|
377
|
+
layout="fov",
|
|
378
|
+
mode="w",
|
|
379
|
+
channel_names=num_channels * ["None"],
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Pass settings to appropriate calculate_transfer_function and save
|
|
383
|
+
if settings.birefringence is not None:
|
|
384
|
+
generate_and_save_birefringence_transfer_function(
|
|
385
|
+
settings, output_dataset
|
|
386
|
+
)
|
|
387
|
+
if settings.phase is not None:
|
|
388
|
+
generate_and_save_phase_transfer_function(
|
|
389
|
+
settings, output_dataset, zyx_shape
|
|
390
|
+
)
|
|
391
|
+
if settings.fluorescence is not None:
|
|
392
|
+
generate_and_save_fluorescence_transfer_function(
|
|
393
|
+
settings, output_dataset, zyx_shape
|
|
394
|
+
)
|
|
395
|
+
if settings.birefringence is not None and settings.phase is not None:
|
|
396
|
+
generate_and_save_vector_birefringence_transfer_function(
|
|
397
|
+
settings, output_dataset, zyx_shape
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Write settings to metadata
|
|
401
|
+
output_dataset.zattrs["settings"] = settings.model_dump()
|
|
402
|
+
|
|
403
|
+
echo_headline(f"Closing {output_dirpath}\n")
|
|
404
|
+
output_dataset.close()
|
|
405
|
+
|
|
406
|
+
echo_headline(
|
|
407
|
+
f"Recreate this transfer function with:\n$ waveorder compute-tf {input_position_dirpaths} -c {config_filepath} -o {output_dirpath}"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@click.command("compute-tf")
|
|
412
|
+
@input_position_dirpaths()
|
|
413
|
+
@config_filepath()
|
|
414
|
+
@output_dirpath()
|
|
415
|
+
def _compute_transfer_function_cli(
|
|
416
|
+
input_position_dirpaths: list[Path],
|
|
417
|
+
config_filepath: Path,
|
|
418
|
+
output_dirpath: Path,
|
|
419
|
+
) -> None:
|
|
420
|
+
"""
|
|
421
|
+
Compute a transfer function using a dataset and configuration file.
|
|
422
|
+
|
|
423
|
+
Calculates the transfer function based on the shape of the first position
|
|
424
|
+
in the list `input-position-dirpaths`.
|
|
425
|
+
|
|
426
|
+
See /examples for example configuration files.
|
|
427
|
+
|
|
428
|
+
>> waveorder compute-tf -i ./input.zarr/0/0/0 -c ./examples/birefringence.yml -o ./transfer_function.zarr
|
|
429
|
+
"""
|
|
430
|
+
compute_transfer_function_cli(
|
|
431
|
+
input_position_dirpaths[0], config_filepath, output_dirpath
|
|
432
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from waveorder.plugin import tab_recon
|
|
7
|
+
except:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from qtpy.QtWidgets import QApplication, QStyle, QVBoxLayout, QWidget
|
|
12
|
+
except:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import qdarktheme # pip install pyqtdarktheme==2.1.0 --ignore-requires-python
|
|
17
|
+
except:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
PLUGIN_NAME = "waveorder: Computational Toolkit for Label-Free Imaging"
|
|
21
|
+
PLUGIN_ICON = "🔬"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.command()
|
|
25
|
+
def gui():
|
|
26
|
+
"""GUI for waveorder: Computational Toolkit for Label-Free Imaging"""
|
|
27
|
+
|
|
28
|
+
app = QApplication(sys.argv)
|
|
29
|
+
app.setStyle(
|
|
30
|
+
"Fusion"
|
|
31
|
+
) # Other options: "Fusion", "Windows", "macOS", "WindowsVista"
|
|
32
|
+
try:
|
|
33
|
+
qdarktheme.setup_theme("dark")
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(e.args)
|
|
36
|
+
pass
|
|
37
|
+
window = MainWindow()
|
|
38
|
+
window.setWindowTitle(PLUGIN_ICON + " " + PLUGIN_NAME + " " + PLUGIN_ICON)
|
|
39
|
+
|
|
40
|
+
pixmapi = getattr(QStyle.StandardPixmap, "SP_TitleBarMenuButton")
|
|
41
|
+
icon = app.style().standardIcon(pixmapi)
|
|
42
|
+
window.setWindowIcon(icon)
|
|
43
|
+
|
|
44
|
+
window.show()
|
|
45
|
+
sys.exit(app.exec())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MainWindow(QWidget):
|
|
49
|
+
def __init__(self):
|
|
50
|
+
super().__init__()
|
|
51
|
+
recon_tab = tab_recon.Ui_ReconTab_Form(stand_alone=True)
|
|
52
|
+
layout = QVBoxLayout()
|
|
53
|
+
self.setLayout(layout)
|
|
54
|
+
layout.addWidget(recon_tab.recon_tab_mainScrollArea)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
gui()
|
waveorder/cli/main.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from waveorder.cli.apply_inverse_transfer_function import (
|
|
4
|
+
_apply_inverse_transfer_function_cli,
|
|
5
|
+
)
|
|
6
|
+
from waveorder.cli.compute_transfer_function import (
|
|
7
|
+
_compute_transfer_function_cli,
|
|
8
|
+
)
|
|
9
|
+
from waveorder.cli.reconstruct import _reconstruct_cli
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from waveorder.cli.gui_widget import gui
|
|
13
|
+
except:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
CONTEXT = {"help_option_names": ["-h", "--help"]}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# `waveorder -h` will show subcommands in the order they are added
|
|
20
|
+
class NaturalOrderGroup(click.Group):
|
|
21
|
+
def list_commands(self, ctx):
|
|
22
|
+
return self.commands.keys()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.group(context_settings=CONTEXT, cls=NaturalOrderGroup)
|
|
26
|
+
def cli():
|
|
27
|
+
"""\033[92mwaveorder: Computational Toolkit for Label-Free Imaging\033[0m\n"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
cli.add_command(_reconstruct_cli)
|
|
31
|
+
cli.add_command(_compute_transfer_function_cli)
|
|
32
|
+
cli.add_command(_apply_inverse_transfer_function_cli)
|
|
33
|
+
try:
|
|
34
|
+
cli.add_command(gui)
|
|
35
|
+
except:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
cli()
|
waveorder/cli/monitor.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import submitit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _move_cursor_up(n_lines, do_print=True):
|
|
11
|
+
if do_print:
|
|
12
|
+
sys.stdout.write("\033[F" * n_lines)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _print_status(
|
|
16
|
+
jobs, position_dirpaths, elapsed_list, print_indices=None, do_print=True
|
|
17
|
+
):
|
|
18
|
+
|
|
19
|
+
columns = [15, 30, 40, 50]
|
|
20
|
+
|
|
21
|
+
# header
|
|
22
|
+
if do_print:
|
|
23
|
+
sys.stdout.write(
|
|
24
|
+
"\033[K" # clear line
|
|
25
|
+
"\033[96mID" # cyan
|
|
26
|
+
f"\033[{columns[0]}G WELL "
|
|
27
|
+
f"\033[{columns[1]}G STATUS "
|
|
28
|
+
f"\033[{columns[2]}G NODE "
|
|
29
|
+
f"\033[{columns[2]}G ELAPSED\n"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if print_indices is None:
|
|
33
|
+
print_indices = range(len(jobs))
|
|
34
|
+
|
|
35
|
+
complete_count = 0
|
|
36
|
+
|
|
37
|
+
for i, (job, position_dirpath) in enumerate(zip(jobs, position_dirpaths)):
|
|
38
|
+
try:
|
|
39
|
+
node_name = job.get_info()["NodeList"] # slowest, so do this first
|
|
40
|
+
except:
|
|
41
|
+
node_name = "SUBMITTED"
|
|
42
|
+
|
|
43
|
+
if job.state == "COMPLETED":
|
|
44
|
+
color = "\033[32m" # green
|
|
45
|
+
complete_count += 1
|
|
46
|
+
elif job.state == "RUNNING":
|
|
47
|
+
color = "\033[93m" # yellow
|
|
48
|
+
elapsed_list[i] += 1 # inexact timing
|
|
49
|
+
else:
|
|
50
|
+
color = "\033[91m" # red
|
|
51
|
+
|
|
52
|
+
if i in print_indices:
|
|
53
|
+
if do_print:
|
|
54
|
+
sys.stdout.write(
|
|
55
|
+
f"\033[K" # clear line
|
|
56
|
+
f"{color}{job.job_id}"
|
|
57
|
+
f"\033[{columns[0]}G {'/'.join(position_dirpath.parts[-3:])}"
|
|
58
|
+
f"\033[{columns[1]}G {job.state}"
|
|
59
|
+
f"\033[{columns[2]}G {node_name}"
|
|
60
|
+
f"\033[{columns[3]}G {elapsed_list[i]} s\n"
|
|
61
|
+
)
|
|
62
|
+
sys.stdout.flush()
|
|
63
|
+
if do_print:
|
|
64
|
+
print(
|
|
65
|
+
f"\033[32m{complete_count}/{len(jobs)} jobs complete. "
|
|
66
|
+
"<ctrl+z> to move monitor to background. "
|
|
67
|
+
"<ctrl+c> twice to cancel jobs."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return elapsed_list
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _get_jobs_to_print(jobs, num_to_print):
|
|
74
|
+
job_indices_to_print = []
|
|
75
|
+
|
|
76
|
+
# if number of jobs is smaller than termanal size, print all
|
|
77
|
+
if len(jobs) <= num_to_print:
|
|
78
|
+
return list(range(len(jobs)))
|
|
79
|
+
|
|
80
|
+
# prioritize incomplete jobs
|
|
81
|
+
for i, job in enumerate(jobs):
|
|
82
|
+
if not job.done():
|
|
83
|
+
job_indices_to_print.append(i)
|
|
84
|
+
if len(job_indices_to_print) == num_to_print:
|
|
85
|
+
return job_indices_to_print
|
|
86
|
+
|
|
87
|
+
# fill in the rest with complete jobs
|
|
88
|
+
for i, job in enumerate(jobs):
|
|
89
|
+
job_indices_to_print.append(i)
|
|
90
|
+
if len(job_indices_to_print) == num_to_print:
|
|
91
|
+
return job_indices_to_print
|
|
92
|
+
|
|
93
|
+
# shouldn't reach here
|
|
94
|
+
return job_indices_to_print
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def monitor_jobs(
|
|
98
|
+
jobs: list[submitit.Job], position_dirpaths: list[Path], do_print=True
|
|
99
|
+
):
|
|
100
|
+
"""Displays the status of a list of submitit jobs with corresponding paths.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
jobs : list[submitit.Job]
|
|
105
|
+
List of submitit jobs
|
|
106
|
+
position_dirpaths : list[Path]
|
|
107
|
+
List of corresponding position paths
|
|
108
|
+
"""
|
|
109
|
+
NON_JOB_LINES = 3
|
|
110
|
+
|
|
111
|
+
if not len(jobs) == len(position_dirpaths):
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"The number of jobs and position_dirpaths should be the same."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
elapsed_list = [0] * len(jobs) # timer for each job
|
|
117
|
+
|
|
118
|
+
# print all jobs once if terminal is too small
|
|
119
|
+
if shutil.get_terminal_size().lines - NON_JOB_LINES < len(jobs):
|
|
120
|
+
_print_status(jobs, position_dirpaths, elapsed_list, do_print=do_print)
|
|
121
|
+
|
|
122
|
+
# main monitor loop
|
|
123
|
+
try:
|
|
124
|
+
while not all(job.done() for job in jobs):
|
|
125
|
+
terminal_lines = shutil.get_terminal_size().lines
|
|
126
|
+
num_jobs_to_print = np.min(
|
|
127
|
+
[terminal_lines - NON_JOB_LINES, len(jobs)]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
job_indices_to_print = _get_jobs_to_print(jobs, num_jobs_to_print)
|
|
131
|
+
|
|
132
|
+
elapsed_list = _print_status(
|
|
133
|
+
jobs,
|
|
134
|
+
position_dirpaths,
|
|
135
|
+
elapsed_list,
|
|
136
|
+
job_indices_to_print,
|
|
137
|
+
do_print,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
time.sleep(1)
|
|
141
|
+
_move_cursor_up(num_jobs_to_print + 2, do_print)
|
|
142
|
+
|
|
143
|
+
# Print final status
|
|
144
|
+
time.sleep(1)
|
|
145
|
+
_print_status(jobs, position_dirpaths, elapsed_list, do_print=do_print)
|
|
146
|
+
|
|
147
|
+
# cancel jobs if ctrl+c
|
|
148
|
+
except KeyboardInterrupt:
|
|
149
|
+
for job in jobs:
|
|
150
|
+
job.cancel()
|
|
151
|
+
print("All jobs cancelled.\033[97m")
|
|
152
|
+
|
|
153
|
+
# Print STDOUT and STDERR for first incomplete job
|
|
154
|
+
incomplete_count = 0
|
|
155
|
+
for job in jobs:
|
|
156
|
+
if not job.done():
|
|
157
|
+
if incomplete_count == 0:
|
|
158
|
+
print("\033[32mSTDOUT")
|
|
159
|
+
print(job.stdout())
|
|
160
|
+
print("\033[91mSTDERR")
|
|
161
|
+
print(job.stderr())
|
|
162
|
+
|
|
163
|
+
print("\033[97m") # print white
|