Skip to content

Commit 37c53b3

Browse files
committed
[fix] fixed _checkcancelled and merged unittests goffset + goffset_alignment
1 parent 1d430b9 commit 37c53b3

4 files changed

Lines changed: 112 additions & 33 deletions

File tree

src/odemis/acq/align/goffset.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import threading
33
import time
4+
from odemis.acq.stream import Stream
45
from collections.abc import Iterable
56
from concurrent.futures import CancelledError
67
from concurrent.futures._base import CANCELLED, FINISHED, RUNNING
@@ -91,7 +92,7 @@ def find_peak_position(data: numpy.ndarray, window_radius: int = 15) -> float:
9192

9293
def estimate_goffset_scale(spgr: model.Actuator, detector: model.Detector, delta=5.0, retries = 1) -> Tuple[float, float, float]:
9394
"""
94-
Estimate the scale factor between a change in the grating offset ('goffset')
95+
Estimate the scale factor between a change in the grating offset ('goffset')
9596
and the resulting shift of the spectral peak on the detector.
9697
9798
The function moves the actuator by a small step (delta) and measures the peak position before and after the move.
@@ -106,7 +107,6 @@ def estimate_goffset_scale(spgr: model.Actuator, detector: model.Detector, delta
106107
:param delta: The relative goffset step size to apply when measuring the scale (default: 5.0).
107108
The actual step may be negated to avoid exceeding hardware limits.
108109
:param retries: number of retries allowed if the estimated scale is unreliable (default: 1).
109-
110110
:return: Tuple (scale, p0, p1)
111111
scale: estimated pixels per unit of goffset
112112
p0: peak position at the initial goffset
@@ -226,7 +226,7 @@ def _do_sparc_auto_grating_offset(future: model.ProgressiveFuture,
226226
max_step = 0.1 * (maxv - minv) # max 10% of range
227227

228228
for i in range(max_it):
229-
_checkCancelled()
229+
_checkCancelled(future)
230230

231231
if i == 0:
232232
peak_px = p0
@@ -253,7 +253,8 @@ def _do_sparc_auto_grating_offset(future: model.ProgressiveFuture,
253253
logging.debug(
254254
"DEBUG | Iter: %d | Peak: %.1f | Error: %.1f | Move: %.4f | Total Change: %.4f",
255255
i, peak_px, error_px, delta_goffset, total_goffset_displacement
256-
) spgr.moveRelSync({"goffset": delta_goffset})
256+
)
257+
spgr.moveRelSync({"goffset": delta_goffset})
257258

258259
future.set_progress(
259260
end=time.time() + (max_it - i - 1) * 0.5) # update estimated end time
@@ -268,16 +269,20 @@ def _do_sparc_auto_grating_offset(future: model.ProgressiveFuture,
268269
logging.error("Alignment error: %s", e)
269270
raise
270271

271-
def _cancel_sparc_auto_grating_offset(future: model.ProgressiveFuture):
272+
def _cancel_sparc_auto_grating_offset(future: model.ProgressiveFuture) -> Bool:
272273

273274
"""
274275
Canceller of _do_sparc_auto_grating_offset task.
275276
"""
276277
with future._task_lock:
278+
if future._task_state == FINISHED:
279+
return False
277280
future._task_state = CANCELLED
281+
logging.debug("Cancelling alignment")
278282

283+
return True
279284

280-
def _checkCancelled(future: "model.ProgressiveFuture"):
285+
def _checkCancelled(future: "model.ProgressiveFuture") -> None:
281286

282287
"""
283288
Check if the future has been cancelled, and if so raise CancelledError.
@@ -412,11 +417,11 @@ def is_current_detector(d):
412417

413418
if selector:
414419
selector.moveAbsSync({selector_axes: detector_to_selector[d]})
415-
future._subfuture = sparc_auto_grating_offset(spectrograph, d)
416-
success = future._subfuture.result()
417-
results[(g0, d.name)] = success
420+
future._subfuture = sparc_auto_grating_offset(spectrograph, d)
421+
success = future._subfuture.result()
422+
results[(g0, d.name)] = success
418423

419-
logging.info("Finished alignment | Detector: %s | Grating: %s | Success: %s", d.name, g0)
424+
logging.info("Finished alignment | Detector: %s | Grating: %s", d.name, g0)
420425

421426
if selector:
422427
selector.moveAbsSync({selector_axes: detector_to_selector[first_detector]})
@@ -464,4 +469,4 @@ def _cancel_auto_align_grating_detector_offsets(future: model.ProgressiveFuture)
464469
future._subfuture.cancel()
465470
logging.debug("Auto-alignment cancellation requested")
466471

467-
return True
472+
return True

src/odemis/acq/align/goffset_alignment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,4 @@ def _cancel_auto_align_grating_detector_offsets(future: model.ProgressiveFuture)
197197
future._subfuture.cancel()
198198
logging.debug("Auto-alignment cancellation requested")
199199

200-
return True
200+
return True

src/odemis/acq/align/test/goffset_alignment_test.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,12 @@
22
import os
33
import time
44
import unittest
5-
import numpy
6-
from collections.abc import Iterable
75
from concurrent.futures import CancelledError
8-
from scipy import ndimage
96

10-
from odemis import model, acq
11-
import odemis
12-
from odemis.acq import align, stream, path
13-
from odemis.acq.align.autofocus import Sparc2AutoFocus, MTD_BINARY
7+
from odemis import model
148
from odemis.acq.align.goffset_alignment import auto_align_grating_detector_offsets
15-
from odemis.dataio import tiff, hdf5
16-
from odemis.util import testing, timeout, img
9+
from odemis.util import timeout
1710
import odemis.util.focus
18-
from unittest.mock import patch
19-
2011

2112
CONFIG_PATH = os.path.dirname(odemis.__file__) + "/../../install/linux/usr/share/odemis/"
2213
SPARC_CONFIG = CONFIG_PATH + "sim/sparc2-focus-test.odm.yaml"
@@ -114,12 +105,11 @@ def test_multi_detector_iteration(self):
114105
# move to spectral camera
115106
self.selector.moveAbsSync({"rx": 1.5707963267948966})
116107
data = spccd.data.get(asap=False)
117-
118108
# check data is not flat
119109
if data.max() == data.min():
120110
print("WARNING: sp-ccd is returning a flat image!")
121111
else:
122112
print(f"sp-ccd signal range: {data.min()} to {data.max()}")
123113

124114
if __name__ == '__main__':
125-
unittest.main()
115+
unittest.main()

src/odemis/acq/align/test/goffset_test.py

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@
88
import unittest
99
import logging
1010
import numpy as np
11+
import odemis
1112

1213
from concurrent.futures._base import CancelledError
13-
1414
from odemis import model
15-
from odemis.util import testing, timeout
15+
from odemis.util import timeout
1616

1717
from odemis.acq.align.goffset import (find_peak_position,
1818
estimate_goffset_scale,
19-
sparc_auto_grating_offset)
20-
21-
import odemis
19+
sparc_auto_grating_offset,
20+
auto_align_grating_detector_offsets)
2221

2322
logging.getLogger().setLevel(logging.DEBUG)
2423

@@ -59,7 +58,7 @@ def test_find_peak_position_synthetic(self):
5958
"""
6059
x = np.arange(200)
6160
true_center = 83.4
62-
spectrum = np.exp(-0.5*((x-true_center)/3.0)**2)
61+
spectrum = np.exp(-0.5 * ((x - true_center) / 3.0)**2)
6362

6463
peak = find_peak_position(spectrum)
6564
self.assertAlmostEqual(peak, true_center, places=1)
@@ -70,7 +69,7 @@ def test_find_peak_position_2d(self):
7069
"""
7170
x = np.arange(200)
7271
true_center = 120.0
73-
line = np.exp(-0.5*((x-true_center)/4.0)**2)
72+
line = np.exp(-0.5 * ((x - true_center) / 4.0)**2)
7473
image = np.tile(line, (50, 1))
7574

7675
peak = find_peak_position(image)
@@ -116,5 +115,90 @@ def test_cancel(self):
116115
pass
117116
self.assertTrue(f.done())
118117

118+
class TestAutoAlignGratingDetectorOffsets(unittest.TestCase):
119+
120+
@classmethod
121+
def setUpClass(cls):
122+
cls.spgr = model.getComponent(role="spectrograph")
123+
cls.ccd = model.getComponent(role="ccd")
124+
#cls.spccd = model.getComponent(role="sp-ccd")
125+
cls.selector = model.getComponent(role="spec-det-selector")
126+
127+
def setUp(self):
128+
# Speed up acquisition
129+
self.ccd.exposureTime.value = self.ccd.exposureTime.range[0]
130+
131+
@timeout(1000)
132+
def test_single_detector_iteration(self):
133+
f = auto_align_grating_detector_offsets(spectrograph=self.spgr, detectors=[self.ccd], selector=self.selector)
134+
res = f.result(timeout=900)
135+
136+
n_gratings = len(self.spgr.axes["grating"].choices)
137+
n_detectors = 1
138+
expected = n_detectors + (n_gratings - 1)
139+
140+
self.assertEqual(len(res), expected)
141+
142+
first_grating = list(self.spgr.axes["grating"].choices.keys())[0]
143+
dets_first = [d for (g, d) in res.keys() if g == first_grating]
144+
145+
self.assertEqual(len(dets_first), n_detectors)
146+
147+
def test_multi_detector_iteration(self):
148+
spccd = model.getComponent(role="sp-ccd")
149+
spccd.exposureTime.value = spccd.exposureTime.range[0]
150+
151+
detectors = [self.ccd, spccd]
152+
153+
# run alignment
154+
f = auto_align_grating_detector_offsets(spectrograph=self.spgr, detectors=detectors, selector=self.selector)
155+
res = f.result(timeout=900)
156+
157+
# calculate expected results
158+
n_gratings = len(self.spgr.axes["grating"].choices)
159+
n_detectors = len(detectors)
160+
expected_count = n_detectors + (n_gratings - 1)
161+
162+
self.assertEqual(len(res), expected_count, f"Expected {expected_count} results, got {len(res)}")
163+
164+
# verify that every detector was used for the first grating
165+
gratings_list = list(self.spgr.axes["grating"].choices.keys())
166+
first_grating = gratings_list[0]
167+
168+
dets_for_first_grating = [d for (g, d) in res.keys() if g == first_grating]
169+
self.assertEqual(len(dets_for_first_grating), n_detectors)
170+
self.assertIn(self.ccd.name, dets_for_first_grating)
171+
self.assertIn(spccd.name, dets_for_first_grating)
172+
173+
# verify that only first detector is used for remaining gratings
174+
for g in gratings_list[1:]:
175+
# Ensure only the first detector (index 0) is present for these gratings
176+
dets_for_this_grating = [d for (grating, d) in res.keys() if grating == g]
177+
self.assertEqual(len(dets_for_this_grating), 1)
178+
self.assertEqual(dets_for_this_grating[0], detectors[0].name)
179+
180+
# move to spectral camera
181+
self.selector.moveAbsSync({"rx": 1.5707963267948966})
182+
data = spccd.data.get(asap=False)
183+
184+
# check data is not flat
185+
if data.max() == data.min():
186+
print("WARNING: sp-ccd is returning a flat image!")
187+
else:
188+
print(f"sp-ccd signal range: {data.min()} to {data.max()}")
189+
190+
@timeout(100)
191+
def test_cancel(self):
192+
f = auto_align_grating_detector_offsets(spectrograph=self.spgr, detectors=[self.ccd],)
193+
194+
time.sleep(1)
195+
196+
cancelled = f.cancel()
197+
self.assertTrue(cancelled)
198+
self.assertTrue(f.cancelled())
199+
200+
with self.assertRaises(CancelledError):
201+
f.result(timeout=900)
202+
119203
if __name__ == "__main__":
120-
unittest.main()
204+
unittest.main()

0 commit comments

Comments
 (0)