waveorder 3.0.0a2__py3-none-any.whl → 3.0.0a3__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/assets/waveorder_plugin_logo.png +0 -0
- waveorder/cli/apply_inverse_models.py +14 -10
- waveorder/cli/apply_inverse_transfer_function.py +3 -3
- waveorder/cli/compute_transfer_function.py +9 -7
- waveorder/cli/printing.py +6 -2
- waveorder/cli/settings.py +51 -51
- waveorder/cli/utils.py +1 -1
- waveorder/focus.py +73 -9
- waveorder/io/utils.py +5 -3
- waveorder/models/phase_thick_3d.py +103 -4
- waveorder/plugin/gui.py +198 -799
- waveorder/plugin/gui.ui +0 -795
- waveorder/plugin/main_widget.py +6 -572
- waveorder/plugin/tab_recon.py +196 -96
- {waveorder-3.0.0a2.dist-info → waveorder-3.0.0a3.dist-info}/METADATA +18 -3
- {waveorder-3.0.0a2.dist-info → waveorder-3.0.0a3.dist-info}/RECORD +21 -22
- {waveorder-3.0.0a2.dist-info → waveorder-3.0.0a3.dist-info}/WHEEL +1 -1
- {waveorder-3.0.0a2.dist-info → waveorder-3.0.0a3.dist-info}/licenses/LICENSE +12 -0
- waveorder/acq/acquisition_workers.py +0 -650
- {waveorder-3.0.0a2.dist-info → waveorder-3.0.0a3.dist-info}/entry_points.txt +0 -0
- {waveorder-3.0.0a2.dist-info → waveorder-3.0.0a3.dist-info}/top_level.txt +0 -0
|
@@ -1,650 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import shutil
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
# type hint/check
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
|
|
10
|
-
import numpy as np
|
|
11
|
-
from iohub import open_ome_zarr
|
|
12
|
-
from iohub.convert import TIFFConverter
|
|
13
|
-
from napari.qt.threading import WorkerBase, WorkerBaseSignals
|
|
14
|
-
from napari.utils.notifications import show_warning
|
|
15
|
-
from qtpy.QtCore import Signal
|
|
16
|
-
|
|
17
|
-
from waveorder.acq.acq_functions import (
|
|
18
|
-
acquire_from_settings,
|
|
19
|
-
generate_acq_settings,
|
|
20
|
-
)
|
|
21
|
-
from waveorder.cli import settings
|
|
22
|
-
from waveorder.cli.apply_inverse_transfer_function import (
|
|
23
|
-
_apply_inverse_transfer_function_cli,
|
|
24
|
-
)
|
|
25
|
-
from waveorder.cli.compute_transfer_function import (
|
|
26
|
-
_compute_transfer_function_cli,
|
|
27
|
-
)
|
|
28
|
-
from waveorder.io.utils import add_index_to_path, model_to_yaml, ram_message
|
|
29
|
-
|
|
30
|
-
# avoid runtime import error
|
|
31
|
-
if TYPE_CHECKING:
|
|
32
|
-
from waveorder.calib.Calibration import QLIPP_Calibration
|
|
33
|
-
from waveorder.plugin.main_widget import MainWidget
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _check_scale_mismatch(
|
|
37
|
-
recon_scale: np.array,
|
|
38
|
-
ngff_scale: tuple[float, float, float, float, float],
|
|
39
|
-
) -> None:
|
|
40
|
-
if not np.allclose(np.array(ngff_scale[2:]), recon_scale, rtol=1e-2):
|
|
41
|
-
show_warning(
|
|
42
|
-
f"Requested reconstruction scale = {recon_scale} "
|
|
43
|
-
f"and OME-Zarr metadata scale = {ngff_scale[2:]} are not equal. "
|
|
44
|
-
"waveorder's reconstruction uses the GUI's "
|
|
45
|
-
"Z-step, pixel size, and magnification, "
|
|
46
|
-
"while napari's viewer uses the input array's metadata."
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _generate_reconstruction_config_from_gui(
|
|
51
|
-
reconstruction_config_path,
|
|
52
|
-
mode,
|
|
53
|
-
calib_window,
|
|
54
|
-
input_channel_names,
|
|
55
|
-
):
|
|
56
|
-
if mode == "birefringence" or mode == "all":
|
|
57
|
-
if calib_window.bg_option == "None":
|
|
58
|
-
background_path = ""
|
|
59
|
-
remove_estimated_background = False
|
|
60
|
-
elif calib_window.bg_option == "Measured":
|
|
61
|
-
background_path = str(calib_window.acq_bg_directory)
|
|
62
|
-
remove_estimated_background = False
|
|
63
|
-
elif calib_window.bg_option == "Estimated":
|
|
64
|
-
background_path = ""
|
|
65
|
-
remove_estimated_background = True
|
|
66
|
-
elif calib_window.bg_option == "Measured + Estimated":
|
|
67
|
-
background_path = str(calib_window.acq_bg_directory)
|
|
68
|
-
remove_estimated_background = True
|
|
69
|
-
|
|
70
|
-
birefringence_transfer_function_settings = (
|
|
71
|
-
settings.BirefringenceTransferFunctionSettings(
|
|
72
|
-
swing=calib_window.swing,
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
birefringence_apply_inverse_settings = (
|
|
76
|
-
settings.BirefringenceApplyInverseSettings(
|
|
77
|
-
wavelength_illumination=calib_window.recon_wavelength
|
|
78
|
-
/ 1000, # convert from um to nm
|
|
79
|
-
background_path=background_path,
|
|
80
|
-
remove_estimated_background=remove_estimated_background,
|
|
81
|
-
flip_orientation=calib_window.flip_orientation,
|
|
82
|
-
rotate_orientation=calib_window.rotate_orientation,
|
|
83
|
-
)
|
|
84
|
-
)
|
|
85
|
-
birefringence_settings = settings.BirefringenceSettings(
|
|
86
|
-
transfer_function=birefringence_transfer_function_settings,
|
|
87
|
-
apply_inverse=birefringence_apply_inverse_settings,
|
|
88
|
-
)
|
|
89
|
-
else:
|
|
90
|
-
birefringence_settings = None
|
|
91
|
-
|
|
92
|
-
if mode == "phase" or mode == "all":
|
|
93
|
-
phase_transfer_function_settings = (
|
|
94
|
-
settings.PhaseTransferFunctionSettings(
|
|
95
|
-
wavelength_illumination=calib_window.recon_wavelength
|
|
96
|
-
/ 1000, # um
|
|
97
|
-
yx_pixel_size=calib_window.ps / calib_window.mag, # um
|
|
98
|
-
z_pixel_size=calib_window.z_step, # um
|
|
99
|
-
z_padding=calib_window.pad_z,
|
|
100
|
-
index_of_refraction_media=calib_window.n_media,
|
|
101
|
-
numerical_aperture_detection=calib_window.obj_na,
|
|
102
|
-
numerical_aperture_illumination=calib_window.cond_na,
|
|
103
|
-
invert_phase_contrast=calib_window.invert_phase_contrast,
|
|
104
|
-
)
|
|
105
|
-
)
|
|
106
|
-
phase_apply_inverse_settings = settings.FourierApplyInverseSettings(
|
|
107
|
-
reconstruction_algorithm=calib_window.phase_regularizer,
|
|
108
|
-
regularization_strength=calib_window.ui.le_phase_strength.text(),
|
|
109
|
-
TV_rho_strength=calib_window.ui.le_rho.text(),
|
|
110
|
-
TV_iterations=calib_window.ui.le_itr.text(),
|
|
111
|
-
)
|
|
112
|
-
phase_settings = settings.PhaseSettings(
|
|
113
|
-
transfer_function=phase_transfer_function_settings,
|
|
114
|
-
apply_inverse=phase_apply_inverse_settings,
|
|
115
|
-
)
|
|
116
|
-
else:
|
|
117
|
-
phase_settings = None
|
|
118
|
-
|
|
119
|
-
reconstruction_settings = settings.ReconstructionSettings(
|
|
120
|
-
input_channel_names=input_channel_names,
|
|
121
|
-
reconstruction_dimension=int(calib_window.acq_mode[0]),
|
|
122
|
-
birefringence=birefringence_settings,
|
|
123
|
-
phase=phase_settings,
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
model_to_yaml(reconstruction_settings, reconstruction_config_path)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class PolarizationAcquisitionSignals(WorkerBaseSignals):
|
|
130
|
-
"""
|
|
131
|
-
Custom Signals class that includes napari native signals
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
phase_image_emitter = Signal(tuple)
|
|
135
|
-
bire_image_emitter = Signal(tuple)
|
|
136
|
-
phase_reconstructor_emitter = Signal(object)
|
|
137
|
-
aborted = Signal()
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
class BFAcquisitionSignals(WorkerBaseSignals):
|
|
141
|
-
"""
|
|
142
|
-
Custom Signals class that includes napari native signals
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
phase_image_emitter = Signal(tuple)
|
|
146
|
-
phase_reconstructor_emitter = Signal(object)
|
|
147
|
-
aborted = Signal()
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
class BFAcquisitionWorker(WorkerBase):
|
|
151
|
-
"""
|
|
152
|
-
Class to execute a brightfield acquisition. First step is to snap the images follow by a second
|
|
153
|
-
step of reconstructing those images.
|
|
154
|
-
"""
|
|
155
|
-
|
|
156
|
-
def __init__(self, calib_window: MainWidget):
|
|
157
|
-
super().__init__(SignalsClass=BFAcquisitionSignals)
|
|
158
|
-
|
|
159
|
-
# Save current state of GUI window
|
|
160
|
-
self.calib_window = calib_window
|
|
161
|
-
|
|
162
|
-
# Init Properties
|
|
163
|
-
self.prefix = "snap"
|
|
164
|
-
self.dm = self.calib_window.mm.displays()
|
|
165
|
-
self.dim = (
|
|
166
|
-
"2D"
|
|
167
|
-
if self.calib_window.ui.cb_acq_mode.currentIndex() == 0
|
|
168
|
-
else "3D"
|
|
169
|
-
)
|
|
170
|
-
self.img_dim = None
|
|
171
|
-
|
|
172
|
-
save_dir = (
|
|
173
|
-
self.calib_window.save_directory
|
|
174
|
-
if self.calib_window.save_directory
|
|
175
|
-
else self.calib_window.directory
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
if save_dir is None:
|
|
179
|
-
raise ValueError(
|
|
180
|
-
"save directory is empty, please specify a directory in the plugin"
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
if self.calib_window.save_name is None:
|
|
184
|
-
self.snap_dir = Path(save_dir) / "snap"
|
|
185
|
-
else:
|
|
186
|
-
self.snap_dir = Path(save_dir) / (
|
|
187
|
-
self.calib_window.save_name + "_snap"
|
|
188
|
-
)
|
|
189
|
-
self.snap_dir = add_index_to_path(self.snap_dir)
|
|
190
|
-
self.snap_dir.mkdir()
|
|
191
|
-
|
|
192
|
-
def _check_abort(self):
|
|
193
|
-
if self.abort_requested:
|
|
194
|
-
self.aborted.emit()
|
|
195
|
-
raise TimeoutError("Stop Requested")
|
|
196
|
-
|
|
197
|
-
def _check_ram(self):
|
|
198
|
-
"""
|
|
199
|
-
Show a warning if RAM < 32 GB.
|
|
200
|
-
"""
|
|
201
|
-
is_warning, msg = ram_message()
|
|
202
|
-
if is_warning:
|
|
203
|
-
show_warning(msg)
|
|
204
|
-
else:
|
|
205
|
-
logging.info(msg)
|
|
206
|
-
|
|
207
|
-
def work(self):
|
|
208
|
-
"""
|
|
209
|
-
Function that runs the 2D or 3D acquisition and reconstructs the data
|
|
210
|
-
"""
|
|
211
|
-
self._check_ram()
|
|
212
|
-
logging.info("Running Acquisition...")
|
|
213
|
-
self._check_abort()
|
|
214
|
-
|
|
215
|
-
channel_idx = self.calib_window.ui.cb_acq_channel.currentIndex()
|
|
216
|
-
channel = self.calib_window.ui.cb_acq_channel.itemText(channel_idx)
|
|
217
|
-
channel_group = None
|
|
218
|
-
|
|
219
|
-
groups = self.calib_window.mmc.getAvailableConfigGroups()
|
|
220
|
-
group_list = []
|
|
221
|
-
for i in range(groups.size()):
|
|
222
|
-
group_list.append(groups.get(i))
|
|
223
|
-
|
|
224
|
-
for group in group_list:
|
|
225
|
-
config = self.calib_window.mmc.getAvailableConfigs(group)
|
|
226
|
-
for idx in range(config.size()):
|
|
227
|
-
if channel in config.get(idx):
|
|
228
|
-
channel_group = group
|
|
229
|
-
break
|
|
230
|
-
|
|
231
|
-
# Create and validate reconstruction settings
|
|
232
|
-
self.config_path = self.snap_dir / "reconstruction_settings.yml"
|
|
233
|
-
|
|
234
|
-
_generate_reconstruction_config_from_gui(
|
|
235
|
-
self.config_path,
|
|
236
|
-
"phase",
|
|
237
|
-
self.calib_window,
|
|
238
|
-
input_channel_names=["BF"],
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
# Acquire 3D stack
|
|
242
|
-
logging.debug("Acquiring 3D stack")
|
|
243
|
-
|
|
244
|
-
# Generate MDA Settings
|
|
245
|
-
settings = generate_acq_settings(
|
|
246
|
-
self.calib_window.mm,
|
|
247
|
-
channel_group=channel_group,
|
|
248
|
-
channels=[channel],
|
|
249
|
-
zstart=self.calib_window.z_start,
|
|
250
|
-
zend=self.calib_window.z_end,
|
|
251
|
-
zstep=self.calib_window.z_step,
|
|
252
|
-
save_dir=str(self.snap_dir),
|
|
253
|
-
prefix=self.prefix,
|
|
254
|
-
keep_shutter_open_slices=True,
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
self._check_abort()
|
|
258
|
-
|
|
259
|
-
# Acquire from MDA settings uses MM MDA GUI
|
|
260
|
-
# Returns (1, 4/5, Z, Y, X) array
|
|
261
|
-
stack = acquire_from_settings(
|
|
262
|
-
self.calib_window.mm,
|
|
263
|
-
settings,
|
|
264
|
-
grab_images=True,
|
|
265
|
-
restore_settings=True,
|
|
266
|
-
)
|
|
267
|
-
self._check_abort()
|
|
268
|
-
|
|
269
|
-
# Cleanup acquisition by closing window, converting to zarr, and deleting temp directory
|
|
270
|
-
self._cleanup_acq()
|
|
271
|
-
|
|
272
|
-
# Reconstruct snapped images
|
|
273
|
-
self.n_slices = stack.shape[2]
|
|
274
|
-
|
|
275
|
-
phase, scale = self._reconstruct()
|
|
276
|
-
self._check_abort()
|
|
277
|
-
|
|
278
|
-
# Warn the user about axial
|
|
279
|
-
if self.calib_window.invert_phase_contrast:
|
|
280
|
-
show_warning(
|
|
281
|
-
"Inverting the phase contrast. This affects the visualization and saved reconstruction."
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
# Warn user about mismatched scales
|
|
285
|
-
recon_scale = np.array(
|
|
286
|
-
(self.calib_window.z_step,)
|
|
287
|
-
+ 2 * (self.calib_window.ps / self.calib_window.mag,)
|
|
288
|
-
)
|
|
289
|
-
_check_scale_mismatch(recon_scale, scale)
|
|
290
|
-
|
|
291
|
-
logging.info("Finished Acquisition")
|
|
292
|
-
logging.debug("Finished Acquisition")
|
|
293
|
-
|
|
294
|
-
# Emit the images and let thread know function is finished
|
|
295
|
-
self.phase_image_emitter.emit((phase, scale))
|
|
296
|
-
|
|
297
|
-
def _reconstruct(self):
|
|
298
|
-
"""
|
|
299
|
-
Method to reconstruct
|
|
300
|
-
"""
|
|
301
|
-
self._check_abort()
|
|
302
|
-
|
|
303
|
-
# Create i/o paths
|
|
304
|
-
transfer_function_path = Path(self.snap_dir) / "transfer_function.zarr"
|
|
305
|
-
reconstruction_path = Path(self.snap_dir) / "reconstruction.zarr"
|
|
306
|
-
input_data_path = Path(self.latest_out_path) / "0" / "0" / "0"
|
|
307
|
-
|
|
308
|
-
# TODO: skip if config files match
|
|
309
|
-
_compute_transfer_function_cli(
|
|
310
|
-
input_position_dirpath=input_data_path,
|
|
311
|
-
config_filepath=self.config_path,
|
|
312
|
-
output_dirpath=transfer_function_path,
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
_apply_inverse_transfer_function_cli(
|
|
316
|
-
input_position_dirpaths=[input_data_path],
|
|
317
|
-
transfer_function_dirpath=transfer_function_path,
|
|
318
|
-
config_filepath=self.config_path,
|
|
319
|
-
output_dirpath=reconstruction_path,
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
# Read reconstruction to pass to emitters
|
|
323
|
-
with open_ome_zarr(reconstruction_path, mode="r") as dataset:
|
|
324
|
-
phase = dataset["0/0/0/0"][0]
|
|
325
|
-
scale = dataset["0/0/0"].scale
|
|
326
|
-
|
|
327
|
-
return phase, scale
|
|
328
|
-
|
|
329
|
-
def _cleanup_acq(self):
|
|
330
|
-
# Get display windows
|
|
331
|
-
disps = self.dm.getAllDataViewers()
|
|
332
|
-
|
|
333
|
-
# loop through display window and find one with matching prefix
|
|
334
|
-
for i in range(disps.size()):
|
|
335
|
-
disp = disps.get(i)
|
|
336
|
-
|
|
337
|
-
# close the datastore and grab the path to where the data is saved
|
|
338
|
-
if self.prefix in disp.getName():
|
|
339
|
-
dp = disp.getDataProvider()
|
|
340
|
-
dir_ = dp.getSummaryMetadata().getDirectory()
|
|
341
|
-
prefix = dp.getSummaryMetadata().getPrefix()
|
|
342
|
-
closed = False
|
|
343
|
-
disp.close()
|
|
344
|
-
while not closed:
|
|
345
|
-
closed = disp.isClosed()
|
|
346
|
-
dp.close()
|
|
347
|
-
|
|
348
|
-
# Try to delete the data, sometime it isn't cleaned up quickly enough and will
|
|
349
|
-
# return an error. In this case, catch the error and then try to close again (seems to work).
|
|
350
|
-
try:
|
|
351
|
-
self.latest_out_path = self.snap_dir / "raw_data.zarr"
|
|
352
|
-
converter = TIFFConverter(
|
|
353
|
-
str(Path(dir_) / prefix),
|
|
354
|
-
str(self.latest_out_path),
|
|
355
|
-
data_type="ometiff",
|
|
356
|
-
grid_layout=False,
|
|
357
|
-
)
|
|
358
|
-
converter.run()
|
|
359
|
-
shutil.rmtree(Path(dir_) / prefix)
|
|
360
|
-
except PermissionError as ex:
|
|
361
|
-
dp.close()
|
|
362
|
-
break
|
|
363
|
-
else:
|
|
364
|
-
continue
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
# TODO: Cache common OTF's on local computers and use those for reconstruction
|
|
368
|
-
class PolarizationAcquisitionWorker(WorkerBase):
|
|
369
|
-
"""
|
|
370
|
-
Class to execute a birefringence/phase acquisition. First step is to snap the images follow by a second
|
|
371
|
-
step of reconstructing those images.
|
|
372
|
-
"""
|
|
373
|
-
|
|
374
|
-
def __init__(
|
|
375
|
-
self, calib_window: MainWidget, calib: QLIPP_Calibration, mode: str
|
|
376
|
-
):
|
|
377
|
-
super().__init__(SignalsClass=PolarizationAcquisitionSignals)
|
|
378
|
-
|
|
379
|
-
# Save current state of GUI window
|
|
380
|
-
self.calib_window = calib_window
|
|
381
|
-
|
|
382
|
-
# Init properties
|
|
383
|
-
self.calib = calib
|
|
384
|
-
self.mode = mode
|
|
385
|
-
self.n_slices = None
|
|
386
|
-
self.prefix = "waveorderPluginSnap"
|
|
387
|
-
self.dm = self.calib_window.mm.displays()
|
|
388
|
-
self.channel_group = self.calib_window.config_group
|
|
389
|
-
|
|
390
|
-
# Determine whether 2D or 3D acquisition is needed
|
|
391
|
-
if self.mode == "birefringence" and self.calib_window.acq_mode == "2D":
|
|
392
|
-
self.dim = "2D"
|
|
393
|
-
else:
|
|
394
|
-
self.dim = "3D"
|
|
395
|
-
|
|
396
|
-
save_dir = (
|
|
397
|
-
self.calib_window.save_directory
|
|
398
|
-
if self.calib_window.save_directory
|
|
399
|
-
else self.calib_window.directory
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
if save_dir is None:
|
|
403
|
-
raise ValueError(
|
|
404
|
-
"save directory is empty, please specify a directory in the plugin"
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
if self.calib_window.save_name is None:
|
|
408
|
-
self.snap_dir = Path(save_dir) / "snap"
|
|
409
|
-
else:
|
|
410
|
-
self.snap_dir = Path(save_dir) / (
|
|
411
|
-
self.calib_window.save_name + "_snap"
|
|
412
|
-
)
|
|
413
|
-
self.snap_dir = add_index_to_path(self.snap_dir)
|
|
414
|
-
self.snap_dir.mkdir()
|
|
415
|
-
|
|
416
|
-
def _check_abort(self):
|
|
417
|
-
if self.abort_requested:
|
|
418
|
-
self.aborted.emit()
|
|
419
|
-
raise TimeoutError("Stop Requested")
|
|
420
|
-
|
|
421
|
-
def _check_ram(self):
|
|
422
|
-
"""
|
|
423
|
-
Show a warning if RAM < 32 GB.
|
|
424
|
-
"""
|
|
425
|
-
is_warning, msg = ram_message()
|
|
426
|
-
if is_warning:
|
|
427
|
-
show_warning(msg)
|
|
428
|
-
else:
|
|
429
|
-
logging.info(msg)
|
|
430
|
-
|
|
431
|
-
def work(self):
|
|
432
|
-
"""
|
|
433
|
-
Function that runs the 2D or 3D acquisition and reconstructs the data
|
|
434
|
-
"""
|
|
435
|
-
self._check_ram()
|
|
436
|
-
logging.info("Running Acquisition...")
|
|
437
|
-
|
|
438
|
-
# List the Channels to acquire, if 5-state then append 5th channel
|
|
439
|
-
channels = ["State0", "State1", "State2", "State3"]
|
|
440
|
-
if self.calib.calib_scheme == "5-State":
|
|
441
|
-
channels.append("State4")
|
|
442
|
-
|
|
443
|
-
self._check_abort()
|
|
444
|
-
|
|
445
|
-
# Create and validate reconstruction settings
|
|
446
|
-
self.config_path = self.snap_dir / "reconstruction_settings.yml"
|
|
447
|
-
_generate_reconstruction_config_from_gui(
|
|
448
|
-
self.config_path,
|
|
449
|
-
self.mode,
|
|
450
|
-
self.calib_window,
|
|
451
|
-
input_channel_names=channels,
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
# Acquire 2D stack
|
|
455
|
-
if self.dim == "2D":
|
|
456
|
-
logging.debug("Acquiring 2D stack")
|
|
457
|
-
|
|
458
|
-
# Generate MDA Settings
|
|
459
|
-
self.settings = generate_acq_settings(
|
|
460
|
-
self.calib_window.mm,
|
|
461
|
-
channel_group=self.channel_group,
|
|
462
|
-
channels=channels,
|
|
463
|
-
save_dir=str(self.snap_dir),
|
|
464
|
-
prefix=self.prefix,
|
|
465
|
-
keep_shutter_open_channels=True,
|
|
466
|
-
)
|
|
467
|
-
self._check_abort()
|
|
468
|
-
# acquire images
|
|
469
|
-
stack = self._acquire()
|
|
470
|
-
|
|
471
|
-
# Acquire 3D stack
|
|
472
|
-
else:
|
|
473
|
-
logging.debug("Acquiring 3D stack")
|
|
474
|
-
|
|
475
|
-
# Generate MDA Settings
|
|
476
|
-
self.settings = generate_acq_settings(
|
|
477
|
-
self.calib_window.mm,
|
|
478
|
-
channel_group=self.channel_group,
|
|
479
|
-
channels=channels,
|
|
480
|
-
zstart=self.calib_window.z_start,
|
|
481
|
-
zend=self.calib_window.z_end,
|
|
482
|
-
zstep=self.calib_window.z_step,
|
|
483
|
-
save_dir=str(self.snap_dir),
|
|
484
|
-
prefix=self.prefix,
|
|
485
|
-
keep_shutter_open_channels=True,
|
|
486
|
-
keep_shutter_open_slices=True,
|
|
487
|
-
)
|
|
488
|
-
|
|
489
|
-
self._check_abort()
|
|
490
|
-
|
|
491
|
-
# set acquisition order to channel-first
|
|
492
|
-
self.settings["slicesFirst"] = False
|
|
493
|
-
self.settings["acqOrderMode"] = 0 # TIME_POS_SLICE_CHANNEL
|
|
494
|
-
|
|
495
|
-
# acquire images
|
|
496
|
-
stack = self._acquire()
|
|
497
|
-
|
|
498
|
-
# Cleanup acquisition by closing window, converting to zarr, and deleting temp directory
|
|
499
|
-
self._cleanup_acq()
|
|
500
|
-
|
|
501
|
-
# Reconstruct snapped images
|
|
502
|
-
self._check_abort()
|
|
503
|
-
self.n_slices = stack.shape[2]
|
|
504
|
-
birefringence, phase, scale = self._reconstruct()
|
|
505
|
-
self._check_abort()
|
|
506
|
-
|
|
507
|
-
# Warn the user about rotations and flips
|
|
508
|
-
if self.calib_window.rotate_orientation:
|
|
509
|
-
show_warning(
|
|
510
|
-
"Applying a +90 degree rotation to the orientation channel. This affects the visualization and saved reconstruction."
|
|
511
|
-
)
|
|
512
|
-
if self.calib_window.flip_orientation:
|
|
513
|
-
show_warning(
|
|
514
|
-
"Applying a flip to orientation channel. This affects the visualization and saved reconstruction."
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
# Warn user about mismatched scales
|
|
518
|
-
recon_scale = np.array(
|
|
519
|
-
(self.calib_window.z_step,)
|
|
520
|
-
+ 2 * (self.calib_window.ps / self.calib_window.mag,)
|
|
521
|
-
)
|
|
522
|
-
_check_scale_mismatch(recon_scale, scale)
|
|
523
|
-
|
|
524
|
-
logging.info("Finished Acquisition")
|
|
525
|
-
logging.debug("Finished Acquisition")
|
|
526
|
-
|
|
527
|
-
# Emit the images and let thread know function is finished
|
|
528
|
-
self.bire_image_emitter.emit((birefringence, scale))
|
|
529
|
-
self.phase_image_emitter.emit((phase, scale))
|
|
530
|
-
|
|
531
|
-
def _check_exposure(self) -> None:
|
|
532
|
-
"""
|
|
533
|
-
Check that all LF channels have the same exposure settings. If not, abort Acquisition.
|
|
534
|
-
"""
|
|
535
|
-
# parse exposure times
|
|
536
|
-
channel_exposures = []
|
|
537
|
-
for channel in self.settings["channels"]:
|
|
538
|
-
channel_exposures.append(channel["exposure"])
|
|
539
|
-
logging.debug(f"Verifying exposure times: {channel_exposures}")
|
|
540
|
-
channel_exposures = np.array(channel_exposures)
|
|
541
|
-
# check if exposure times are equal
|
|
542
|
-
if not np.all(channel_exposures == channel_exposures[0]):
|
|
543
|
-
error_exposure_msg = (
|
|
544
|
-
f"The MDA exposure times are not equal! Aborting Acquisition.\n"
|
|
545
|
-
f"Please manually set the exposure times to the same value from the MDA menu."
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
raise ValueError(error_exposure_msg)
|
|
549
|
-
|
|
550
|
-
self._check_abort()
|
|
551
|
-
|
|
552
|
-
def _acquire(self) -> np.ndarray:
|
|
553
|
-
"""
|
|
554
|
-
Acquire images.
|
|
555
|
-
|
|
556
|
-
Returns
|
|
557
|
-
-------
|
|
558
|
-
stack: (nd-array) Dimensions are (C, Z, Y, X). Z=1 for 2D acquisition.
|
|
559
|
-
"""
|
|
560
|
-
# check if exposure times are the same
|
|
561
|
-
self._check_exposure()
|
|
562
|
-
|
|
563
|
-
# Acquire from MDA settings uses MM MDA GUI
|
|
564
|
-
# Returns (1, 4/5, Z, Y, X) array
|
|
565
|
-
stack = acquire_from_settings(
|
|
566
|
-
self.calib_window.mm,
|
|
567
|
-
self.settings,
|
|
568
|
-
grab_images=True,
|
|
569
|
-
restore_settings=True,
|
|
570
|
-
)
|
|
571
|
-
self._check_abort()
|
|
572
|
-
|
|
573
|
-
return stack
|
|
574
|
-
|
|
575
|
-
def _reconstruct(self):
|
|
576
|
-
"""
|
|
577
|
-
Method to reconstruct. First need to initialize the reconstructor given
|
|
578
|
-
what type of acquisition it is (birefringence only skips a lot of heavy compute needed for phase).
|
|
579
|
-
This function also checks to see if the reconstructor needs to be updated from previous acquisitions
|
|
580
|
-
|
|
581
|
-
"""
|
|
582
|
-
self._check_abort()
|
|
583
|
-
|
|
584
|
-
# Create config and i/o paths
|
|
585
|
-
transfer_function_path = Path(self.snap_dir) / "transfer_function.zarr"
|
|
586
|
-
reconstruction_path = Path(self.snap_dir) / "reconstruction.zarr"
|
|
587
|
-
input_data_path = Path(self.latest_out_path) / "0" / "0" / "0"
|
|
588
|
-
|
|
589
|
-
# TODO: skip if config files match
|
|
590
|
-
_compute_transfer_function_cli(
|
|
591
|
-
input_position_dirpath=input_data_path,
|
|
592
|
-
config_filepath=self.config_path,
|
|
593
|
-
output_dirpath=transfer_function_path,
|
|
594
|
-
)
|
|
595
|
-
|
|
596
|
-
_apply_inverse_transfer_function_cli(
|
|
597
|
-
input_position_dirpaths=[input_data_path],
|
|
598
|
-
transfer_function_dirpath=transfer_function_path,
|
|
599
|
-
config_filepath=self.config_path,
|
|
600
|
-
output_dirpath=reconstruction_path,
|
|
601
|
-
)
|
|
602
|
-
|
|
603
|
-
# Read reconstruction to pass to emitters
|
|
604
|
-
with open_ome_zarr(reconstruction_path, mode="r") as dataset:
|
|
605
|
-
czyx_data = dataset["0/0/0/0"][0]
|
|
606
|
-
birefringence = czyx_data[0:4]
|
|
607
|
-
try:
|
|
608
|
-
phase = czyx_data[4]
|
|
609
|
-
except:
|
|
610
|
-
phase = None
|
|
611
|
-
scale = dataset["0/0/0"].scale
|
|
612
|
-
|
|
613
|
-
return birefringence, phase, scale
|
|
614
|
-
|
|
615
|
-
def _cleanup_acq(self):
|
|
616
|
-
# Get display windows
|
|
617
|
-
disps = self.dm.getAllDataViewers()
|
|
618
|
-
|
|
619
|
-
# loop through display window and find one with matching prefix
|
|
620
|
-
for i in range(disps.size()):
|
|
621
|
-
disp = disps.get(i)
|
|
622
|
-
|
|
623
|
-
# close the datastore and grab the path to where the data is saved
|
|
624
|
-
if self.prefix in disp.getName():
|
|
625
|
-
dp = disp.getDataProvider()
|
|
626
|
-
dir_ = dp.getSummaryMetadata().getDirectory()
|
|
627
|
-
prefix = dp.getSummaryMetadata().getPrefix()
|
|
628
|
-
closed = False
|
|
629
|
-
disp.close()
|
|
630
|
-
while not closed:
|
|
631
|
-
closed = disp.isClosed()
|
|
632
|
-
dp.close()
|
|
633
|
-
|
|
634
|
-
# Try to delete the data, sometime it isn't cleaned up quickly enough and will
|
|
635
|
-
# return an error. In this case, catch the error and then try to close again (seems to work).
|
|
636
|
-
try:
|
|
637
|
-
self.latest_out_path = self.snap_dir / "raw_data.zarr"
|
|
638
|
-
converter = TIFFConverter(
|
|
639
|
-
str(Path(dir_) / prefix),
|
|
640
|
-
str(self.latest_out_path),
|
|
641
|
-
data_type="ometiff",
|
|
642
|
-
grid_layout=False,
|
|
643
|
-
)
|
|
644
|
-
converter.run()
|
|
645
|
-
shutil.rmtree(Path(dir_) / prefix)
|
|
646
|
-
except PermissionError as ex:
|
|
647
|
-
dp.close()
|
|
648
|
-
break
|
|
649
|
-
else:
|
|
650
|
-
continue
|
|
File without changes
|
|
File without changes
|