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.
Files changed (58) hide show
  1. waveorder/_version.py +16 -3
  2. waveorder/acq/__init__.py +0 -0
  3. waveorder/acq/acq_functions.py +166 -0
  4. waveorder/assets/HSV_legend.png +0 -0
  5. waveorder/assets/JCh_legend.png +0 -0
  6. waveorder/assets/waveorder_plugin_logo.png +0 -0
  7. waveorder/calib/Calibration.py +1512 -0
  8. waveorder/calib/Optimization.py +470 -0
  9. waveorder/calib/__init__.py +0 -0
  10. waveorder/calib/calibration_workers.py +464 -0
  11. waveorder/cli/apply_inverse_models.py +328 -0
  12. waveorder/cli/apply_inverse_transfer_function.py +379 -0
  13. waveorder/cli/compute_transfer_function.py +432 -0
  14. waveorder/cli/gui_widget.py +58 -0
  15. waveorder/cli/main.py +39 -0
  16. waveorder/cli/monitor.py +163 -0
  17. waveorder/cli/option_eat_all.py +47 -0
  18. waveorder/cli/parsing.py +122 -0
  19. waveorder/cli/printing.py +16 -0
  20. waveorder/cli/reconstruct.py +67 -0
  21. waveorder/cli/settings.py +187 -0
  22. waveorder/cli/utils.py +175 -0
  23. waveorder/filter.py +1 -2
  24. waveorder/focus.py +136 -25
  25. waveorder/io/__init__.py +0 -0
  26. waveorder/io/_reader.py +61 -0
  27. waveorder/io/core_functions.py +272 -0
  28. waveorder/io/metadata_reader.py +195 -0
  29. waveorder/io/utils.py +175 -0
  30. waveorder/io/visualization.py +160 -0
  31. waveorder/models/inplane_oriented_thick_pol3d_vector.py +3 -3
  32. waveorder/models/isotropic_fluorescent_thick_3d.py +92 -0
  33. waveorder/models/isotropic_fluorescent_thin_3d.py +331 -0
  34. waveorder/models/isotropic_thin_3d.py +73 -72
  35. waveorder/models/phase_thick_3d.py +103 -4
  36. waveorder/napari.yaml +36 -0
  37. waveorder/plugin/__init__.py +9 -0
  38. waveorder/plugin/gui.py +1094 -0
  39. waveorder/plugin/gui.ui +1440 -0
  40. waveorder/plugin/job_manager.py +42 -0
  41. waveorder/plugin/main_widget.py +1605 -0
  42. waveorder/plugin/tab_recon.py +3294 -0
  43. waveorder/scripts/__init__.py +0 -0
  44. waveorder/scripts/launch_napari.py +13 -0
  45. waveorder/scripts/repeat-cal-acq-rec.py +147 -0
  46. waveorder/scripts/repeat-calibration.py +31 -0
  47. waveorder/scripts/samples.py +85 -0
  48. waveorder/scripts/simulate_zarr_acq.py +204 -0
  49. waveorder/util.py +1 -1
  50. waveorder/visuals/napari_visuals.py +1 -1
  51. waveorder-3.0.0.dist-info/METADATA +350 -0
  52. waveorder-3.0.0.dist-info/RECORD +69 -0
  53. {waveorder-2.2.1.dist-info → waveorder-3.0.0.dist-info}/WHEEL +1 -1
  54. waveorder-3.0.0.dist-info/entry_points.txt +5 -0
  55. {waveorder-2.2.1.dist-info → waveorder-3.0.0.dist-info}/licenses/LICENSE +13 -1
  56. waveorder-2.2.1.dist-info/METADATA +0 -188
  57. waveorder-2.2.1.dist-info/RECORD +0 -27
  58. {waveorder-2.2.1.dist-info → waveorder-3.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,464 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
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 napari.qt.threading import WorkerBase, WorkerBaseSignals, thread_worker
13
+ from qtpy.QtCore import Signal
14
+
15
+ from waveorder.calib.Calibration import LC_DEVICE_NAME
16
+ from waveorder.cli import settings
17
+ from waveorder.cli.apply_inverse_transfer_function import (
18
+ apply_inverse_transfer_function_cli,
19
+ )
20
+ from waveorder.cli.compute_transfer_function import (
21
+ compute_transfer_function_cli,
22
+ )
23
+ from waveorder.io.core_functions import set_lc_state, snap_and_average
24
+ from waveorder.io.metadata_reader import MetadataReader
25
+ from waveorder.io.utils import MockEmitter, add_index_to_path, model_to_yaml
26
+
27
+ # avoid runtime import error
28
+ if TYPE_CHECKING:
29
+ pass
30
+
31
+ from waveorder.calib.Calibration import QLIPP_Calibration
32
+ from waveorder.plugin.main_widget import MainWidget
33
+
34
+
35
+ class CalibrationSignals(WorkerBaseSignals):
36
+ """
37
+ Custom Signals class that includes napari native signals
38
+ """
39
+
40
+ progress_update = Signal(tuple)
41
+ extinction_update = Signal(str)
42
+ intensity_update = Signal(object)
43
+ calib_assessment = Signal(str)
44
+ calib_assessment_msg = Signal(str)
45
+ calib_file_emit = Signal(Path)
46
+ plot_sequence_emit = Signal(str)
47
+ lc_states = Signal(tuple)
48
+ aborted = Signal()
49
+
50
+
51
+ class BackgroundSignals(WorkerBaseSignals):
52
+ """
53
+ Custom Signals class that includes napari native signals
54
+ """
55
+
56
+ bg_image_emitter = Signal(tuple)
57
+ bire_image_emitter = Signal(tuple)
58
+ bg_path_update_emitter = Signal(Path)
59
+ aborted = Signal()
60
+
61
+
62
+ class CalibrationWorkerBase(WorkerBase):
63
+ """
64
+ Base class for creating calibration workers.
65
+ """
66
+
67
+ def __init_subclass__(cls, signals: WorkerBaseSignals):
68
+ """Called when creating calibration worker classes.
69
+
70
+ Parameters
71
+ ----------
72
+ signals : WorkerBaseSignals
73
+ Qt Signals class for the created worker class to send data across threads.
74
+ """
75
+ super().__init_subclass__()
76
+ cls.signals = signals
77
+
78
+ def __init__(self, calib_window: MainWidget, calib: QLIPP_Calibration):
79
+ """Initialize the worker object.
80
+
81
+ Parameters
82
+ ----------
83
+ calib_window : MainWidget
84
+ The waveorder plugin's main GUI widget object containing metadata input.
85
+ calib : QLIPP_Calibration
86
+ waveorder calibration backend object.
87
+ """
88
+ super().__init__(SignalsClass=self.signals)
89
+ self.calib_window = calib_window
90
+ self.calib = calib
91
+
92
+ def _check_abort(self):
93
+ """
94
+ Called if the user presses the STOP button.
95
+ Needs to be checked after every major step to stop the process
96
+ """
97
+ if self.abort_requested:
98
+ self.aborted.emit()
99
+ raise TimeoutError("Stop Requested.")
100
+
101
+ def _write_meta_file(self, meta_file: str):
102
+ self.calib.meta_file = meta_file
103
+ self.calib.write_metadata(
104
+ notes=self.calib_window.ui.le_notes_field.text()
105
+ )
106
+
107
+
108
+ class CalibrationWorker(CalibrationWorkerBase, signals=CalibrationSignals):
109
+ """
110
+ Class to execute calibration
111
+ """
112
+
113
+ def __init__(self, calib_window, calib):
114
+ super().__init__(calib_window, calib)
115
+
116
+ def work(self):
117
+ """
118
+ Runs the full calibration algorithm and emits necessary signals.
119
+ """
120
+
121
+ self.plot_sequence_emit.emit("Coarse")
122
+ self.calib.intensity_emitter = self.intensity_update
123
+ self.calib.plot_sequence_emitter = self.plot_sequence_emit
124
+ self.progress_update.emit((1, "Calculating Blacklevel..."))
125
+ self._check_abort()
126
+
127
+ logging.info("Calculating Black Level ...")
128
+ logging.debug("Calculating Black Level ...")
129
+ self.calib.close_shutter_and_calc_blacklevel()
130
+
131
+ # Calculate Blacklevel
132
+ logging.info(f"Black Level: {self.calib.I_Black:.0f}\n")
133
+ logging.debug(f"Black Level: {self.calib.I_Black:.0f}\n")
134
+
135
+ self._check_abort()
136
+ self.progress_update.emit((10, "Calibrating Extinction State..."))
137
+
138
+ # Open shutter
139
+ self.calib.open_shutter()
140
+
141
+ # Set LC Wavelength:
142
+ self.calib.set_wavelength(int(self.calib_window.wavelength))
143
+ if self.calib_window.calib_mode == "MM-Retardance":
144
+ self.calib_window.mmc.setProperty(
145
+ LC_DEVICE_NAME, "Wavelength", self.calib_window.wavelength
146
+ )
147
+
148
+ self._check_abort()
149
+
150
+ # Optimize States
151
+ (
152
+ self._calibrate_4state()
153
+ if self.calib_window.calib_scheme == "4-State"
154
+ else self._calibrate_5state()
155
+ )
156
+
157
+ # Reset shutter autoshutter
158
+ self.calib.reset_shutter()
159
+
160
+ # Calculate Extinction
161
+ extinction_ratio = self.calib.calculate_extinction(
162
+ self.calib.swing,
163
+ self.calib.I_Black,
164
+ self.calib.I_Ext,
165
+ self.calib.I_Elliptical,
166
+ )
167
+ self._check_abort()
168
+
169
+ # Update main GUI with extinction ratio
170
+ self.calib.extinction_ratio = extinction_ratio
171
+ self.extinction_update.emit(str(extinction_ratio))
172
+
173
+ # determine metadata filename
174
+ meta_file = (
175
+ Path(self.calib_window.directory) / "calibration_metadata.txt"
176
+ )
177
+ meta_file = add_index_to_path(meta_file)
178
+
179
+ # Write Metadata
180
+ self._write_meta_file(meta_file)
181
+ self.calib_file_emit.emit(self.calib.meta_file)
182
+ self.progress_update.emit((100, "Finished"))
183
+
184
+ self._check_abort()
185
+
186
+ # Perform calibration assessment based on retardance values
187
+ self._assess_calibration()
188
+
189
+ self._check_abort()
190
+
191
+ # Emit calibrated LC states for plotting
192
+ self.lc_states.emit((self.calib.pol_states, self.calib.lc_states))
193
+
194
+ logging.info("\n=======Finished Calibration=======\n")
195
+ logging.info(f"EXTINCTION = {extinction_ratio:.2f}")
196
+ logging.debug("\n=======Finished Calibration=======\n")
197
+ logging.debug(f"EXTINCTION = {extinction_ratio:.2f}")
198
+
199
+ def _calibrate_4state(self):
200
+ """
201
+ Run through the 4-state calibration algorithm
202
+ """
203
+
204
+ search_radius = np.min((self.calib.swing / self.calib.ratio, 0.05))
205
+
206
+ self.calib.calib_scheme = "4-State"
207
+
208
+ self._check_abort()
209
+
210
+ # Optimize Extinction State
211
+ self.calib.opt_Iext()
212
+
213
+ self._check_abort()
214
+ self.progress_update.emit((60, "Calibrating State 1..."))
215
+
216
+ # Optimize first elliptical (reference) state
217
+ self.calib.opt_I0()
218
+ self.progress_update.emit((65, "Calibrating State 2..."))
219
+
220
+ self._check_abort()
221
+
222
+ # Optimize 60 deg state
223
+ self.calib.opt_I60(search_radius, search_radius)
224
+ self.progress_update.emit((75, "Calibrating State 3..."))
225
+
226
+ self._check_abort()
227
+
228
+ # Optimize 120 deg state
229
+ self.calib.opt_I120(search_radius, search_radius)
230
+ self.progress_update.emit((85, "Writing Metadata..."))
231
+
232
+ self._check_abort()
233
+
234
+ def _calibrate_5state(self):
235
+ search_radius = np.min((self.calib.swing, 0.05))
236
+
237
+ self.calib.calib_scheme = "5-State"
238
+
239
+ # Optimize Extinction State
240
+ self.calib.opt_Iext()
241
+ self.progress_update.emit((50, "Calibrating State 1..."))
242
+
243
+ self._check_abort()
244
+
245
+ # Optimize First elliptical state
246
+ self.calib.opt_I0()
247
+ self.progress_update.emit((55, "Calibrating State 2..."))
248
+
249
+ self._check_abort()
250
+
251
+ # Optimize 45 deg state
252
+ self.calib.opt_I45(search_radius, search_radius)
253
+ self.progress_update.emit((65, "Calibrating State 3..."))
254
+
255
+ self._check_abort()
256
+
257
+ # Optimize 90 deg state
258
+ self.calib.opt_I90(search_radius, search_radius)
259
+ self.progress_update.emit((75, "Calibrating State 4..."))
260
+
261
+ self._check_abort()
262
+
263
+ # Optimize 135 deg state
264
+ self.calib.opt_I135(search_radius, search_radius)
265
+ self.progress_update.emit((85, "Writing Metadata..."))
266
+
267
+ self._check_abort()
268
+
269
+ def _assess_calibration(self):
270
+ """
271
+ Assesses the quality of calibration based off retardance values.
272
+ Attempts to determine whether certain optical components are out of place.
273
+ """
274
+
275
+ if self.calib.extinction_ratio >= 100:
276
+ self.calib_assessment.emit("good")
277
+ self.calib_assessment_msg.emit("Successful Calibration")
278
+ elif 80 <= self.calib.extinction_ratio < 100:
279
+ self.calib_assessment.emit("okay")
280
+ self.calib_assessment_msg.emit(
281
+ "Successful Calibration, Okay Extinction Ratio"
282
+ )
283
+ else:
284
+ self.calib_assessment.emit("bad")
285
+ message = (
286
+ "Possibilities are: a) linear polarizer and LC are not oriented properly, "
287
+ "b) circular analyzer has wrong handedness, "
288
+ "c) the condenser is not setup for Kohler illumination, "
289
+ "d) a component, such as autofocus dichroic or sample chamber, distorts the polarization state"
290
+ )
291
+
292
+ self.calib_assessment_msg.emit("Poor Extinction. " + message)
293
+
294
+
295
+ class BackgroundCaptureWorker(
296
+ CalibrationWorkerBase, signals=BackgroundSignals
297
+ ):
298
+ """
299
+ Class to execute background capture.
300
+ """
301
+
302
+ def __init__(self, calib_window, calib):
303
+ super().__init__(calib_window, calib)
304
+
305
+ def work(self):
306
+ # Make the background folder
307
+ bg_path = (
308
+ Path(self.calib_window.directory)
309
+ / self.calib_window.ui.le_bg_folder.text()
310
+ )
311
+ bg_path = add_index_to_path(bg_path)
312
+ bg_path.mkdir()
313
+
314
+ self._check_abort()
315
+
316
+ # capture and return background images
317
+ imgs = self.calib.capture_bg(self.calib_window.n_avg, bg_path)
318
+
319
+ # build background-specific reconstruction settings
320
+ reconstruction_settings = settings.ReconstructionSettings(
321
+ input_channel_names=[
322
+ f"State{i}"
323
+ for i in range(int(self.calib_window.calib_scheme[0]))
324
+ ],
325
+ reconstruction_dimension=2,
326
+ birefringence=settings.BirefringenceSettings(
327
+ transfer_function=settings.BirefringenceTransferFunctionSettings(
328
+ swing=self.calib_window.swing
329
+ ),
330
+ apply_inverse=settings.BirefringenceApplyInverseSettings(
331
+ wavelength_illumination=self.calib_window.recon_wavelength
332
+ / 1000,
333
+ background_path="",
334
+ remove_estimated_background=False,
335
+ flip_orientation=False,
336
+ rotate_orientation=False,
337
+ ),
338
+ ),
339
+ )
340
+
341
+ reconstruction_config_path = bg_path / "reconstruction_settings.yml"
342
+ model_to_yaml(reconstruction_settings, reconstruction_config_path)
343
+
344
+ input_data_path = bg_path / "background.zarr" / "0" / "0" / "0"
345
+ transfer_function_path = bg_path / "transfer_function.zarr"
346
+ reconstruction_path = bg_path / "reconstruction.zarr"
347
+
348
+ compute_transfer_function_cli(
349
+ input_position_dirpath=input_data_path,
350
+ config_filepath=reconstruction_config_path,
351
+ output_dirpath=transfer_function_path,
352
+ )
353
+
354
+ apply_inverse_transfer_function_cli(
355
+ input_position_dirpaths=[input_data_path],
356
+ transfer_function_dirpath=transfer_function_path,
357
+ config_filepath=reconstruction_config_path,
358
+ output_dirpath=reconstruction_path,
359
+ num_processes=1,
360
+ )
361
+
362
+ # Load reconstructions from file for layers
363
+ with open_ome_zarr(reconstruction_path, mode="r") as dataset:
364
+ self.retardance = dataset["0/0/0/0"][0, 0, 0]
365
+ self.birefringence = dataset["0/0/0/0"][0, :, 0]
366
+ scale = dataset["0/0/0"].scale
367
+
368
+ # Save metadata file and emit imgs
369
+ meta_file = bg_path / "polarization_calibration.txt"
370
+ self._write_meta_file(meta_file)
371
+
372
+ # Update last calibration file
373
+ note = self.calib_window.ui.le_notes_field.text()
374
+
375
+ with open(self.calib_window.last_calib_meta_file, "r") as file:
376
+ current_json = json.load(file)
377
+
378
+ old_note = current_json["Notes"]
379
+ if old_note is None or old_note == "" or old_note == note:
380
+ current_json["Notes"] = note
381
+ else:
382
+ current_json["Notes"] = old_note + ", " + note
383
+
384
+ with open(self.calib_window.last_calib_meta_file, "w") as file:
385
+ json.dump(current_json, file, indent=1)
386
+
387
+ self._check_abort()
388
+
389
+ # Emit background images + background birefringence
390
+ self.bg_image_emitter.emit((imgs, scale))
391
+ self.bire_image_emitter.emit(
392
+ ((self.retardance, self.birefringence[1]), scale)
393
+ )
394
+
395
+ # Emit bg path
396
+ self.bg_path_update_emitter.emit(bg_path)
397
+
398
+
399
+ @thread_worker
400
+ def load_calibration(calib, metadata: MetadataReader):
401
+ """
402
+ Sets MM properties based upon calibration metadata file
403
+
404
+
405
+ Parameters
406
+ ----------
407
+ calib: (object) waveorder Calibration Class
408
+ metadata: (object) MetadataReader instance
409
+
410
+ Returns
411
+ -------
412
+ calib (object) updated waveorder Calibration Class
413
+ """
414
+ calib.calib_scheme = metadata.Calibration_scheme
415
+
416
+ def _set_calib_attrs(calib, metadata):
417
+ """Set the retardance attributes in the waveorder Calibration object"""
418
+ if calib.calib_scheme == "4-State":
419
+ lc_states = ["ext", "0", "60", "120"]
420
+ elif calib.calib_scheme == "5-State":
421
+ lc_states = ["ext", "0", "45", "90", "135"]
422
+ else:
423
+ raise ValueError(
424
+ "Invalid calibration scheme in metadata: {calib.calib_scheme}"
425
+ )
426
+ for side in ("A", "B"):
427
+ retardance_values = metadata.__getattribute__(
428
+ "LC" + side + "_retardance"
429
+ )
430
+ for i, state in enumerate(lc_states):
431
+ # set the retardance value attribute (e.g. 'lca_0')
432
+ retardance_name = "lc" + side.lower() + "_" + state
433
+ setattr(calib, retardance_name, retardance_values[i])
434
+ # set the swing value attribute (e.g. 'swing0')
435
+ if state != "ext":
436
+ swing_name = "swing" + state
437
+ setattr(calib, swing_name, metadata.Swing_measured[i - 1])
438
+
439
+ _set_calib_attrs(calib, metadata)
440
+
441
+ for state, lca, lcb in zip(
442
+ [f"State{i}" for i in range(5)],
443
+ metadata.LCA_retardance,
444
+ metadata.LCB_retardance,
445
+ ):
446
+ calib.define_lc_state(state, lca, lcb)
447
+
448
+ # Calculate black level after loading these properties
449
+ calib.intensity_emitter = MockEmitter()
450
+ calib.close_shutter_and_calc_blacklevel()
451
+ calib.open_shutter()
452
+ set_lc_state(calib.mmc, calib.group, "State0")
453
+ calib.I_Ext = snap_and_average(calib.snap_manager)
454
+ set_lc_state(calib.mmc, calib.group, "State1")
455
+ calib.I_Elliptical = snap_and_average(calib.snap_manager)
456
+ calib.reset_shutter()
457
+
458
+ yield str(
459
+ calib.calculate_extinction(
460
+ calib.swing, calib.I_Black, calib.I_Ext, calib.I_Elliptical
461
+ )
462
+ )
463
+
464
+ return calib