waveorder 2.2.1b0__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.1b0.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.1b0.dist-info → waveorder-3.0.0.dist-info/licenses}/LICENSE +13 -1
- waveorder-2.2.1b0.dist-info/METADATA +0 -187
- waveorder-2.2.1b0.dist-info/RECORD +0 -27
- {waveorder-2.2.1b0.dist-info → waveorder-3.0.0.dist-info}/top_level.txt +0 -0
waveorder/focus.py
CHANGED
|
@@ -3,11 +3,53 @@ from typing import Literal, Optional
|
|
|
3
3
|
|
|
4
4
|
import matplotlib.pyplot as plt
|
|
5
5
|
import numpy as np
|
|
6
|
+
import torch
|
|
6
7
|
from scipy.signal import peak_widths
|
|
7
8
|
|
|
8
9
|
from waveorder import util
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
def compute_midband_power(
|
|
13
|
+
yx_array: torch.Tensor,
|
|
14
|
+
NA_det: float,
|
|
15
|
+
lambda_ill: float,
|
|
16
|
+
pixel_size: float,
|
|
17
|
+
midband_fractions: tuple[float, float] = (0.125, 0.25),
|
|
18
|
+
) -> torch.Tensor:
|
|
19
|
+
"""Compute midband spatial frequency power by summing over a 2D midband donut.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
yx_array : torch.Tensor
|
|
24
|
+
2D tensor in (Y, X) order.
|
|
25
|
+
NA_det : float
|
|
26
|
+
Detection NA.
|
|
27
|
+
lambda_ill : float
|
|
28
|
+
Illumination wavelength.
|
|
29
|
+
Units are arbitrary, but must match [pixel_size].
|
|
30
|
+
pixel_size : float
|
|
31
|
+
Object-space pixel size = camera pixel size / magnification.
|
|
32
|
+
Units are arbitrary, but must match [lambda_ill].
|
|
33
|
+
midband_fractions : tuple[float, float], optional
|
|
34
|
+
The minimum and maximum fraction of the cutoff frequency that define the midband.
|
|
35
|
+
Default is (0.125, 0.25).
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
torch.Tensor
|
|
40
|
+
Sum of absolute FFT values in the midband region.
|
|
41
|
+
"""
|
|
42
|
+
_, _, fxx, fyy = util.gen_coordinate(yx_array.shape, pixel_size)
|
|
43
|
+
frr = torch.tensor(np.sqrt(fxx**2 + fyy**2))
|
|
44
|
+
xy_abs_fft = torch.abs(torch.fft.fftn(yx_array))
|
|
45
|
+
cutoff = 2 * NA_det / lambda_ill
|
|
46
|
+
mask = torch.logical_and(
|
|
47
|
+
frr > cutoff * midband_fractions[0],
|
|
48
|
+
frr < cutoff * midband_fractions[1],
|
|
49
|
+
)
|
|
50
|
+
return torch.sum(xy_abs_fft[mask])
|
|
51
|
+
|
|
52
|
+
|
|
11
53
|
def focus_from_transverse_band(
|
|
12
54
|
zyx_array,
|
|
13
55
|
NA_det,
|
|
@@ -15,8 +57,11 @@ def focus_from_transverse_band(
|
|
|
15
57
|
pixel_size,
|
|
16
58
|
midband_fractions=(0.125, 0.25),
|
|
17
59
|
mode: Literal["min", "max"] = "max",
|
|
60
|
+
polynomial_fit_order: Optional[int] = None,
|
|
18
61
|
plot_path: Optional[str] = None,
|
|
19
62
|
threshold_FWHM: float = 0,
|
|
63
|
+
return_statistics: bool = False,
|
|
64
|
+
enable_subpixel_precision: bool = False,
|
|
20
65
|
):
|
|
21
66
|
"""Estimates the in-focus slice from a 3D stack by optimizing a transverse spatial frequency band.
|
|
22
67
|
|
|
@@ -36,8 +81,10 @@ def focus_from_transverse_band(
|
|
|
36
81
|
midband_fractions: Tuple[float, float], optional
|
|
37
82
|
The minimum and maximum fraction of the cutoff frequency that define the midband.
|
|
38
83
|
Requires: 0 <= midband_fractions[0] < midband_fractions[1] <= 1.
|
|
39
|
-
mode: {'
|
|
40
|
-
Option to choose the in-focus slice by minimizing or maximizing the midband
|
|
84
|
+
mode: {'min', 'max'}, optional
|
|
85
|
+
Option to choose the in-focus slice by minimizing or maximizing the midband power. By default 'max'.
|
|
86
|
+
polynomial_fit_order: int, optional
|
|
87
|
+
Default None is no fit. If integer, a polynomial of that degree is fit to the midband power before choosing the extreme point as the in-focus slice.
|
|
41
88
|
plot_path: str or None, optional
|
|
42
89
|
File name for a diagnostic plot (supports matplotlib filetypes .png, .pdf, .svg, etc.).
|
|
43
90
|
Use None to skip.
|
|
@@ -46,14 +93,22 @@ def focus_from_transverse_band(
|
|
|
46
93
|
The default value, 0, applies no threshold, and the maximum midband power is always considered in focus.
|
|
47
94
|
For values > 0, the peak's FWHM must be greater than the threshold for the slice to be considered in focus.
|
|
48
95
|
If the peak does not meet this threshold, the function returns None.
|
|
96
|
+
return_statistics: bool, optional
|
|
97
|
+
If True, returns a tuple (in_focus_index, peak_stats) instead of just in_focus_index.
|
|
98
|
+
Default is False for backward compatibility.
|
|
99
|
+
enable_subpixel_precision: bool, optional
|
|
100
|
+
If True and polynomial_fit_order is provided, enables sub-pixel precision focus detection
|
|
101
|
+
by finding the continuous extremum of the polynomial fit. Default is False for backward compatibility.
|
|
49
102
|
|
|
50
103
|
Returns
|
|
51
|
-
|
|
52
|
-
slice : int or
|
|
53
|
-
If
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
104
|
+
-------
|
|
105
|
+
slice : int, float, None, or tuple
|
|
106
|
+
If return_statistics is False (default):
|
|
107
|
+
Returns in_focus_index (int if enable_subpixel_precision=False,
|
|
108
|
+
float if enable_subpixel_precision=True and polynomial_fit_order is not None, or None).
|
|
109
|
+
If return_statistics is True:
|
|
110
|
+
Returns tuple (in_focus_index, peak_stats) where peak_stats is a dict
|
|
111
|
+
containing 'peak_index' and 'peak_FWHM'.
|
|
57
112
|
|
|
58
113
|
Example
|
|
59
114
|
------
|
|
@@ -64,6 +119,7 @@ def focus_from_transverse_band(
|
|
|
64
119
|
>>> in_focus_data = data[slice,:,:]
|
|
65
120
|
"""
|
|
66
121
|
minmaxfunc = _mode_to_minmaxfunc(mode)
|
|
122
|
+
peak_stats = {"peak_index": None, "peak_FWHM": None}
|
|
67
123
|
|
|
68
124
|
_check_focus_inputs(
|
|
69
125
|
zyx_array, NA_det, lambda_ill, pixel_size, midband_fractions
|
|
@@ -74,29 +130,71 @@ def focus_from_transverse_band(
|
|
|
74
130
|
warnings.warn(
|
|
75
131
|
"The dataset only contained a single slice. Returning trivial slice index = 0."
|
|
76
132
|
)
|
|
133
|
+
if return_statistics:
|
|
134
|
+
return 0, peak_stats
|
|
77
135
|
return 0
|
|
78
136
|
|
|
79
|
-
# Calculate
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
137
|
+
# Calculate midband power for each slice
|
|
138
|
+
midband_sum = np.array(
|
|
139
|
+
[
|
|
140
|
+
compute_midband_power(
|
|
141
|
+
torch.from_numpy(zyx_array[z]),
|
|
142
|
+
NA_det,
|
|
143
|
+
lambda_ill,
|
|
144
|
+
pixel_size,
|
|
145
|
+
midband_fractions,
|
|
146
|
+
).numpy()
|
|
147
|
+
for z in range(zyx_array.shape[0])
|
|
148
|
+
]
|
|
149
|
+
)
|
|
83
150
|
|
|
84
|
-
|
|
85
|
-
|
|
151
|
+
if polynomial_fit_order is None:
|
|
152
|
+
peak_index = minmaxfunc(midband_sum)
|
|
153
|
+
else:
|
|
154
|
+
x = np.arange(len(midband_sum))
|
|
155
|
+
coeffs = np.polyfit(x, midband_sum, polynomial_fit_order)
|
|
156
|
+
poly_func = np.poly1d(coeffs)
|
|
86
157
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
158
|
+
if enable_subpixel_precision:
|
|
159
|
+
# Find the continuous extremum using derivative
|
|
160
|
+
poly_deriv = np.polyder(coeffs)
|
|
161
|
+
# Find roots of the derivative (critical points)
|
|
162
|
+
critical_points = np.roots(poly_deriv)
|
|
163
|
+
|
|
164
|
+
# Filter for real roots within the data range
|
|
165
|
+
real_critical_points = []
|
|
166
|
+
for cp in critical_points:
|
|
167
|
+
if np.isreal(cp) and 0 <= cp.real < len(midband_sum):
|
|
168
|
+
real_critical_points.append(cp.real)
|
|
169
|
+
|
|
170
|
+
if real_critical_points:
|
|
171
|
+
# Evaluate the polynomial at critical points to find extremum
|
|
172
|
+
critical_values = [
|
|
173
|
+
poly_func(cp) for cp in real_critical_points
|
|
174
|
+
]
|
|
175
|
+
if mode == "max":
|
|
176
|
+
best_idx = np.argmax(critical_values)
|
|
177
|
+
else: # mode == "min"
|
|
178
|
+
best_idx = np.argmin(critical_values)
|
|
179
|
+
peak_index = real_critical_points[best_idx]
|
|
180
|
+
else:
|
|
181
|
+
# Fall back to discrete maximum if no valid critical points
|
|
182
|
+
peak_index = float(minmaxfunc(poly_func(x)))
|
|
183
|
+
else:
|
|
184
|
+
peak_index = minmaxfunc(poly_func(x))
|
|
93
185
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
186
|
+
# For peak width calculation, use integer peak index
|
|
187
|
+
if enable_subpixel_precision and polynomial_fit_order is not None:
|
|
188
|
+
# Use the closest integer index for peak width calculation
|
|
189
|
+
integer_peak_index = int(np.round(peak_index))
|
|
190
|
+
else:
|
|
191
|
+
integer_peak_index = int(peak_index)
|
|
97
192
|
|
|
98
|
-
peak_results = peak_widths(midband_sum, [
|
|
193
|
+
peak_results = peak_widths(midband_sum, [integer_peak_index])
|
|
99
194
|
peak_FWHM = peak_results[0][0]
|
|
195
|
+
peak_stats.update(
|
|
196
|
+
{"peak_index": int(peak_index), "peak_FWHM": float(peak_FWHM)}
|
|
197
|
+
)
|
|
100
198
|
|
|
101
199
|
if peak_FWHM >= threshold_FWHM:
|
|
102
200
|
in_focus_index = peak_index
|
|
@@ -114,6 +212,9 @@ def focus_from_transverse_band(
|
|
|
114
212
|
threshold_FWHM,
|
|
115
213
|
)
|
|
116
214
|
|
|
215
|
+
if return_statistics:
|
|
216
|
+
return in_focus_index, peak_stats
|
|
217
|
+
|
|
117
218
|
return in_focus_index
|
|
118
219
|
|
|
119
220
|
|
|
@@ -168,9 +269,19 @@ def _plot_focus_metric(
|
|
|
168
269
|
):
|
|
169
270
|
_, ax = plt.subplots(1, 1, figsize=(4, 4))
|
|
170
271
|
ax.plot(midband_sum, "-k")
|
|
272
|
+
|
|
273
|
+
# Handle floating-point peak_index for plotting
|
|
274
|
+
if isinstance(peak_index, float) and not peak_index.is_integer():
|
|
275
|
+
# Use interpolation to get the y-value at the floating-point x-position
|
|
276
|
+
peak_y_value = np.interp(
|
|
277
|
+
peak_index, np.arange(len(midband_sum)), midband_sum
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
peak_y_value = midband_sum[int(peak_index)]
|
|
281
|
+
|
|
171
282
|
ax.plot(
|
|
172
283
|
peak_index,
|
|
173
|
-
|
|
284
|
+
peak_y_value,
|
|
174
285
|
"go" if in_focus_index is not None else "ro",
|
|
175
286
|
)
|
|
176
287
|
ax.hlines(*peak_results[1:], color="k", linestyles="dashed")
|
waveorder/io/__init__.py
ADDED
|
File without changes
|
waveorder/io/_reader.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import Dict, List, Tuple, Union
|
|
2
|
+
|
|
3
|
+
import zarr
|
|
4
|
+
from iohub import read_micromanager
|
|
5
|
+
from napari_ome_zarr._reader import napari_get_reader as fallback_reader
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def napari_get_reader(path):
|
|
9
|
+
if isinstance(path, str):
|
|
10
|
+
if ".zarr" in path:
|
|
11
|
+
with zarr.open(path) as root:
|
|
12
|
+
if "plate" in root.attrs:
|
|
13
|
+
return hcs_zarr_reader
|
|
14
|
+
else:
|
|
15
|
+
return fallback_reader(path)
|
|
16
|
+
else:
|
|
17
|
+
return ome_tif_reader
|
|
18
|
+
else:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def hcs_zarr_reader(
|
|
23
|
+
path: Union[str, List[str]],
|
|
24
|
+
) -> List[Tuple[zarr.Array, Dict]]:
|
|
25
|
+
reader = read_micromanager(path)
|
|
26
|
+
results = list()
|
|
27
|
+
|
|
28
|
+
zs = zarr.open(path, "r")
|
|
29
|
+
names = []
|
|
30
|
+
|
|
31
|
+
dict_ = zs.attrs.asdict()
|
|
32
|
+
wells = dict_["plate"]["wells"]
|
|
33
|
+
for well in wells:
|
|
34
|
+
path = well["path"]
|
|
35
|
+
well_dict = zs[path].attrs.asdict()
|
|
36
|
+
for name in well_dict["well"]["images"]:
|
|
37
|
+
names.append(name["path"])
|
|
38
|
+
for pos in range(reader.get_num_positions()):
|
|
39
|
+
meta = dict()
|
|
40
|
+
name = names[pos]
|
|
41
|
+
meta["name"] = name
|
|
42
|
+
results.append((reader.get_zarr(pos), meta))
|
|
43
|
+
return results
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ome_tif_reader(
|
|
47
|
+
path: Union[str, List[str]],
|
|
48
|
+
) -> List[Tuple[zarr.Array, Dict]]:
|
|
49
|
+
reader = read_micromanager(path)
|
|
50
|
+
results = list()
|
|
51
|
+
|
|
52
|
+
npos = reader.get_num_positions()
|
|
53
|
+
for pos in range(npos):
|
|
54
|
+
meta = dict()
|
|
55
|
+
if npos == 1:
|
|
56
|
+
meta["name"] = "Pos000_000"
|
|
57
|
+
else:
|
|
58
|
+
meta["name"] = reader.stage_positions[pos]["Label"][2:]
|
|
59
|
+
results.append((reader.get_zarr(pos), meta))
|
|
60
|
+
|
|
61
|
+
return results
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@contextmanager
|
|
8
|
+
def suspend_live_sm(snap_manager):
|
|
9
|
+
"""Context manager that suspends/unsuspends MM live mode for `SnapLiveManager`.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
snap_manager : object
|
|
14
|
+
`org.micromanager.internal.SnapLiveManager` object via pycromanager
|
|
15
|
+
|
|
16
|
+
Yields
|
|
17
|
+
------
|
|
18
|
+
object
|
|
19
|
+
`org.micromanager.internal.SnapLiveManager` object via pycromanager
|
|
20
|
+
|
|
21
|
+
Usage
|
|
22
|
+
-----
|
|
23
|
+
```py
|
|
24
|
+
with suspend_live_sm(snap_manager) as sm:
|
|
25
|
+
pass # do something with MM that can't be done in live mode
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
snap_manager.setSuspended(True)
|
|
29
|
+
try:
|
|
30
|
+
yield snap_manager
|
|
31
|
+
finally:
|
|
32
|
+
snap_manager.setSuspended(False)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def snap_and_get_image(snap_manager):
|
|
36
|
+
"""
|
|
37
|
+
Snap and get image using Snap Live Window Manager + transfer of ZMQ
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
snap_manager: (object) MM Snap Live Window object
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
image: (array) 2D array of size (Y, X)
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
snap_manager.snap(True)
|
|
49
|
+
time.sleep(
|
|
50
|
+
0.3
|
|
51
|
+
) # sleep after snap to make sure the image we grab is the correct one
|
|
52
|
+
|
|
53
|
+
# get pixels + dimensions
|
|
54
|
+
height = snap_manager.getDisplay().getDisplayedImages().get(0).getHeight()
|
|
55
|
+
width = snap_manager.getDisplay().getDisplayedImages().get(0).getWidth()
|
|
56
|
+
array = (
|
|
57
|
+
snap_manager.getDisplay().getDisplayedImages().get(0).getRawPixels()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return np.reshape(array, (height, width))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def snap_and_average(snap_manager, display=True):
|
|
64
|
+
"""
|
|
65
|
+
Snap an image with Snap Live manager + grab only the mean (computed in java)
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
snap_manager: (object) MM Snap Live Window object
|
|
70
|
+
display: (bool) Whether to show the snap on the Snap Live Window in MM
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
mean: (float) mean of snapped image
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
snap_manager.snap(display)
|
|
79
|
+
time.sleep(
|
|
80
|
+
0.3
|
|
81
|
+
) # sleep after snap to make sure the image we grab is the correct one
|
|
82
|
+
|
|
83
|
+
return snap_manager.getDisplay().getImagePlus().getStatistics().umean
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def set_lc_waves(mmc, device_property: tuple, value: float):
|
|
87
|
+
"""
|
|
88
|
+
Set retardance in waves for LC in device_property
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
mmc : object
|
|
93
|
+
MM Core object
|
|
94
|
+
device_property : tuple
|
|
95
|
+
(device_name, property_name) set
|
|
96
|
+
value : float
|
|
97
|
+
Retardance to set as fraction of a wavelength
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
device_name = device_property[0]
|
|
104
|
+
prop_name = device_property[1]
|
|
105
|
+
|
|
106
|
+
if value > 1.6 or value < 0.001:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Requested retardance value is {value} waves. "
|
|
109
|
+
f"Retardance must be greater than 0.001 and less than 1.6 waves."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
mmc.setProperty(device_name, prop_name, str(value))
|
|
113
|
+
time.sleep(20 / 1000)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def set_lc_voltage(mmc, device_property: tuple, value: float):
|
|
117
|
+
"""
|
|
118
|
+
Set LC retardance by specifying LC voltage
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
mmc : object
|
|
123
|
+
MM Core object
|
|
124
|
+
device_property : tuple
|
|
125
|
+
(device_name, property_name) set
|
|
126
|
+
value : float
|
|
127
|
+
LC voltage in volts. Applied voltage is limited to 20V
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
device_name = device_property[0]
|
|
134
|
+
prop_name = device_property[1]
|
|
135
|
+
|
|
136
|
+
if value > 20.0 or value < 0.0:
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f"Requested LC voltage is {value} V. "
|
|
139
|
+
f"LC voltage must be greater than 0.0 and less than 20.0 V."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
mmc.setProperty(device_name, prop_name, str(value))
|
|
143
|
+
time.sleep(20 / 1000)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def set_lc_daq(mmc, device_property: tuple, value: float):
|
|
147
|
+
"""
|
|
148
|
+
Set LC retardance based on DAQ output
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
mmc : object
|
|
153
|
+
MM Core object
|
|
154
|
+
device_property : tuple
|
|
155
|
+
(device_name, property_name) set
|
|
156
|
+
value : float
|
|
157
|
+
DAQ output voltage in volts. DAQ output must be in 0-5V range
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
device_name = device_property[0]
|
|
164
|
+
prop_name = device_property[1]
|
|
165
|
+
|
|
166
|
+
if value > 5.0 or value < 0.0:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
"DAC voltage must be greater than 0.0 and less than 5.0"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
mmc.setProperty(device_name, prop_name, str(value))
|
|
172
|
+
time.sleep(20 / 1000)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_lc(mmc, device_property: tuple):
|
|
176
|
+
"""
|
|
177
|
+
Get LC state in the native units of the device property
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
mmc : object
|
|
182
|
+
MM Core object
|
|
183
|
+
device_property : tuple
|
|
184
|
+
(device_name, property_name) set
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
device_name = device_property[0]
|
|
192
|
+
prop_name = device_property[1]
|
|
193
|
+
|
|
194
|
+
val = float(mmc.getProperty(device_name, prop_name))
|
|
195
|
+
return val
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def define_meadowlark_state(mmc, device_property: tuple):
|
|
199
|
+
"""
|
|
200
|
+
Defines pallet element in the Meadowlark device adapter for the given state.
|
|
201
|
+
Make sure LC values for this state are set before calling this function
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
mmc : object
|
|
206
|
+
MM Core object
|
|
207
|
+
device_property : tuple
|
|
208
|
+
(device_name, property_name) set, e.g.
|
|
209
|
+
('MeadowlarkLC', 'Pal. elem. 00; enter 0 to define; 1 to activate')
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
device_name = device_property[0]
|
|
217
|
+
prop_name = device_property[1]
|
|
218
|
+
|
|
219
|
+
# define LC state
|
|
220
|
+
# setting pallet elements to 0 defines LC state
|
|
221
|
+
mmc.setProperty(device_name, prop_name, 0)
|
|
222
|
+
mmc.waitForDevice(device_name)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def define_config_state(
|
|
226
|
+
mmc, group: str, config: str, device_properties: list, values: list
|
|
227
|
+
):
|
|
228
|
+
"""
|
|
229
|
+
Define config state by specifying the values for all device properties in this config
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
mmc : object
|
|
234
|
+
MM Core object
|
|
235
|
+
group : str
|
|
236
|
+
Name of config group
|
|
237
|
+
config : str
|
|
238
|
+
Name of config, e.g. State0
|
|
239
|
+
device_properties: list
|
|
240
|
+
List of (device_name, property_name) tuples in config
|
|
241
|
+
values: list
|
|
242
|
+
List of matching device property values
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
for device_property, value in zip(device_properties, values):
|
|
250
|
+
device_name = device_property[0]
|
|
251
|
+
prop_name = device_property[1]
|
|
252
|
+
mmc.defineConfig(group, config, device_name, prop_name, str(value))
|
|
253
|
+
mmc.waitForConfig(group, config)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def set_lc_state(mmc, group: str, config: str):
|
|
257
|
+
"""
|
|
258
|
+
Change to the specific LC State
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
mmc : object
|
|
263
|
+
MM Core object
|
|
264
|
+
group : str
|
|
265
|
+
Name of config group
|
|
266
|
+
config : str
|
|
267
|
+
Name of config, e.g. State0
|
|
268
|
+
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
mmc.setConfig(group, config)
|
|
272
|
+
time.sleep(20 / 1000) # delay for LC settle time
|