Coverage for src\pytribeam\insertable_devices.py: 88%
256 statements
« prev ^ index » next coverage.py v7.5.1, created at 2025-03-04 17:41 -0800
« prev ^ index » next coverage.py v7.5.1, created at 2025-03-04 17:41 -0800
1#!/usr/bin/python3
3# Default python modules
4# from functools import singledispatch
5import os
6from pathlib import Path
7import time
8import warnings
9import math
10from typing import NamedTuple, List, Tuple
11from types import ModuleType
13# Autoscript included modules
14import numpy as np
15from matplotlib import pyplot as plt
17# 3rd party module
19# Local scripts
20import pytribeam.utilities as ut
21import pytribeam.constants as cs
22from pytribeam.constants import Constants
23import pytribeam.image as img
25try:
26 from pytribeam.laser import tfs_laser as external
27except:
28 pass
29import pytribeam.types as tbt
32def detector_insertable(
33 microscope: tbt.Microscope,
34 detector: tbt.DetectorType,
35) -> bool:
36 """Determines whether or not the built-in microscope detector is insertable and returns it state"""
37 # check if the detector is being read by Autoscript
38 try:
39 # make requested detector the active detector
40 microscope.detector.type.value = detector.value
41 except:
42 warnings.warn(
43 f"""Warning. Invalid detector type of "{detector.value}" for currently selected device
44 of "{tbt.Device(microscope.imaging.get_active_device()).value}" or detector not found on this system.
45 Detector will be assumed to not be insertable."""
46 )
47 return False
49 # check if it is insertable
50 try:
51 microscope.detector.state
52 return True
53 except Exception:
54 return False
57def detector_state(
58 microscope: tbt.Microscope,
59 detector: tbt.DetectorType,
60) -> tbt.RetractableDeviceState:
61 """Determine state of detector, only valid if detector is insertable"""
62 # check if the detector is being read by Autoscriptdevice_access(microscope)
63 if not detector_insertable(
64 microscope=microscope,
65 detector=detector,
66 ):
67 return None
68 return tbt.RetractableDeviceState(microscope.detector.state)
71def detectors_will_collide(
72 microscope: tbt.Microscope,
73 detector_to_insert: tbt.DetectorType,
74) -> bool:
75 """Determines if collision may occur"""
76 device_retracted = tbt.RetractableDeviceState.RETRACTED.value
77 for detector_combo in Constants.detector_collisions:
78 if detector_to_insert in detector_combo:
79 for detector in detector_combo:
80 if detector == detector_to_insert:
81 continue
82 if detector == tbt.DetectorType.EDS:
83 if external.EDS_CameraStatus() != device_retracted:
84 return True
85 elif detector == tbt.DetectorType.EBSD:
86 if external.EBSD_CameraStatus() != device_retracted:
87 return True
88 else:
89 state = detector_state(microscope=microscope, detector=detector)
90 if state.value != device_retracted:
91 return True
92 return False
95def device_access(microscope: tbt.Microscope) -> tbt.ViewQuad:
96 """Switches to upper-left quad and assign electron beam as active device,
97 which is the only device with access to insertable devices like the CBS/ABS detector
98 Other devices, like ion-beam, CCD or Nav-Cam, do not have CBS/ABS access
99 """
100 img.set_view(
101 microscope=microscope,
102 quad=tbt.ViewQuad.UPPER_LEFT,
103 )
104 img.set_beam_device(
105 microscope=microscope,
106 device=tbt.Device.ELECTRON_BEAM,
107 )
108 return True
111def insert_EBSD(
112 microscope: tbt.Microscope,
113) -> bool:
114 connect_EBSD()
115 if detectors_will_collide(
116 microscope=microscope,
117 detector_to_insert=tbt.DetectorType.EBSD,
118 ):
119 raise SystemError(
120 f"""Error. Cannot insert EBSD which may collide with another detector.
121 Disallowed detector combinations are: {Constants.detector_collisions}"""
122 )
123 ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
124 map_status = tbt.MapStatus(external.EBSD_MappingStatus())
125 if ebsd_cam_status == tbt.RetractableDeviceState.ERROR:
126 raise SystemError("Error, EDS Camera in error state, workflow stopped.")
127 if map_status != tbt.MapStatus.IDLE:
128 raise SystemError(
129 f'Error, EBSD mapping not in "{tbt.MapStatus.IDLE.value}" state.'
130 )
131 if ebsd_cam_status != tbt.RetractableDeviceState.INSERTED:
132 print("\tInserting EBSD Camera...")
133 # TODO change to constants
134 minutes_to_wait = 3
135 timeout = minutes_to_wait * 60 # seconds
136 waittime = 4 # seconds
137 CCD_view(microscope=microscope)
138 # Oxford Inst requires 2 inserts
139 while True:
140 external.EBSD_InsertCamera() # inserted state
141 if (
142 external.EBSD_CameraStatus()
143 == tbt.RetractableDeviceState.INSERTED.value
144 ):
145 break
146 time.sleep(waittime)
147 timeout = timeout - waittime
148 if timeout < 1:
149 warnings.warn("Warning: EBSD insert timeout. Trying to continue...")
150 break
151 CCD_pause(microscope=microscope)
153 new_ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
154 if new_ebsd_cam_status == tbt.RetractableDeviceState.INSERTED:
155 print("\tEBSD Camera inserted")
156 return True
157 raise SystemError(
158 f'EBSDS Camera is not inserted, currently in "{new_ebsd_cam_status}" state'
159 )
162def insert_EDS(
163 microscope: tbt.Microscope,
164) -> bool:
165 connect_EDS()
166 if detectors_will_collide(
167 microscope=microscope,
168 detector_to_insert=tbt.DetectorType.EDS,
169 ):
170 raise SystemError(
171 f"""Error. Cannot insert EDS while CBS not in "Retracted" state.
172 CBS detector currently in "{detector_state(microscope=microscope, detector=tbt.DetectorType.CBS).value}" state."""
173 )
174 eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
175 map_status = tbt.MapStatus(external.EDS_MappingStatus())
176 if eds_cam_status == tbt.RetractableDeviceState.ERROR:
177 raise SystemError("Error, EDS Camera in error state, workflow stopped.")
178 if map_status != tbt.MapStatus.IDLE:
179 raise SystemError(
180 f'Error, EDS mapping not in "{tbt.MapStatus.IDLE.value}" state.'
181 )
182 if eds_cam_status != tbt.RetractableDeviceState.INSERTED:
183 print("\tInserting EDS Camera...")
184 CCD_view(microscope=microscope)
185 external.EDS_InsertCamera()
186 CCD_pause(microscope=microscope)
188 new_eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
189 if new_eds_cam_status == tbt.RetractableDeviceState.INSERTED:
190 print("\tEDS Camera inserted")
191 return True
192 raise SystemError(
193 f'EDS Camera is not inserted, currently in "{new_eds_cam_status}" state'
194 )
197def insert_detector(
198 microscope: tbt.Microscope,
199 detector: tbt.DetectorType,
200 time_delay_s: float = 0.5,
201) -> bool:
202 """inserts the selected detector"""
203 # ensure detector is the active one
204 microscope.detector.type.value = detector.value
205 # confirm detector is insertable
206 try:
207 state = microscope.detector.state
208 except:
209 raise ValueError(f"{detector.value} detector is not insertable.")
210 if state == tbt.RetractableDeviceState.RETRACTED.value:
211 if detectors_will_collide(
212 microscope=microscope,
213 detector_to_insert=detector,
214 ):
215 raise SystemError(
216 f"""Error. Cannot insert {detector.value} which may collide with another detector.
217 Disallowed detector combinations are: {Constants.detector_collisions}"""
218 )
220 print(f"\tInserting {detector.value} detector...")
221 CCD_view(microscope=microscope)
222 microscope.detector.insert()
223 time.sleep(time_delay_s)
224 CCD_pause(microscope=microscope)
225 if microscope.detector.state == tbt.RetractableDeviceState.INSERTED.value:
226 print(f"\t\t{detector.value} detector inserted.")
227 return True
228 elif state == tbt.RetractableDeviceState.INSERTED.value:
229 print(f"\t{detector.value} detector is already inserted.")
230 return True
231 raise SystemError(
232 f'Cannot insert {detector.value} detector, current detector state is "{state}".'
233 )
236def retract_all_devices(
237 microscope: tbt.Microscope,
238 enable_EBSD: bool,
239 enable_EDS: bool,
240) -> bool:
241 # TODO come up with better system for enable_EBSD_EDS
242 """Retracts all insertable devices. First microscope detectors,
243 then EBSD/EDS detectors if integrated. Must overwrite EBSD/EDS option to ignore
244 these detectors
246 microscope: microscope object for accessing autoscript API
247 """
248 print("\tRetracting devices, do not interact with xTUI during this process...")
249 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
250 device_access(microscope)
252 for detector in microscope.detector.type.available_values:
253 detector = tbt.DetectorType(detector) # overwrite
254 state = detector_state(
255 microscope=microscope,
256 detector=detector,
257 )
258 if (state is not None) and (state != tbt.RetractableDeviceState.RETRACTED):
259 retract_device(
260 microscope=microscope,
261 detector=detector,
262 )
264 # EBSD/EDS detectors:
265 try:
266 external
267 except NameError:
268 pass
269 print("\t\tLaser API not imported, EBSD and EDS detectors are unavailable")
270 else:
271 if enable_EBSD:
272 retract_EBSD(microscope=microscope)
273 if enable_EDS:
274 retract_EDS(microscope=microscope)
275 # else:
276 # warnings.warn(
277 # "\t\tWarning: EBSD and EDS device control API is available but not being used."
278 # )
280 # reset initial settings:
281 img.set_view(
282 microscope=microscope,
283 quad=initial_view,
284 )
285 print("\t\tAll available and enabled devices retracted.")
286 return True
289def connect_EBSD() -> tbt.RetractableDeviceState:
290 try:
291 status = external.EBSD_CameraStatus()
292 except:
293 raise ConnectionError(
294 """EBSD control not connected, "Laser Control" from ThermoFisher must be open.
295 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again."""
296 )
297 return tbt.RetractableDeviceState(status)
300def retract_EBSD(microscope: tbt.Microscope) -> bool:
301 # print(f"\t\tRetracting EBSD detector")
302 connect_EBSD()
303 ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
304 if ebsd_status == tbt.RetractableDeviceState.ERROR:
305 raise SystemError(
306 "EBSD Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control"
307 )
308 if ebsd_status != tbt.RetractableDeviceState.RETRACTED:
309 print(
310 '\t\t\tEBSD Camera Retraction requested, please wait for "mapping complete" verification...'
311 )
312 map_status = tbt.MapStatus(external.EBSD_MappingStatus())
313 # first check if mapping is finished properly
314 minutes_to_wait = 5 # TODO set constant
315 timeout = minutes_to_wait * 60 # seconds #TODO
316 cameraokconfirmations = 3 # synchronization issue with EDAX, try to get map completed 3x before continuing
317 waittime = 10 # seconds
318 if map_status == tbt.MapStatus.ACTIVE:
319 print("\t\t\tEBSD mapping currently active, waiting for mapping to finish")
320 while True:
321 current_map_status = tbt.MapStatus(external.EBSD_MappingStatus())
322 if current_map_status != tbt.MapStatus.ACTIVE:
323 cameraokconfirmations = cameraokconfirmations - 1
324 waittime = 3 # shorten wait time, polling 3x to see if mapping was really completed.
325 time.sleep(waittime)
326 timeout = timeout - waittime
327 if cameraokconfirmations < 1:
328 delay = minutes_to_wait * 60 - timeout
329 print(f"\t\t\t\tEBSD mapping finished. Delay of {delay} seconds")
330 break
331 if timeout < 1:
332 warnings.warn(
333 "\t\t\tWarning, EBSD mapping timeout. Trying to continue..."
334 )
335 break
336 CCD_view(microscope=microscope)
337 print("\t\t\tEBSD Camera retracting...")
338 external.EBSD_RetractCamera()
339 time.sleep(1)
340 CCD_pause(microscope=microscope)
341 current_ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
342 if current_ebsd_status != tbt.RetractableDeviceState.RETRACTED:
343 raise SystemError("Error, EBSD Camera retraction failed, workflow stopped.")
344 print("\t\tEBSD Camera retracted")
345 return True
348def connect_EDS() -> tbt.RetractableDeviceState:
349 try:
350 status = external.EDS_CameraStatus()
351 except:
352 raise ConnectionError(
353 """EDS control not connected, "Laser Control" from ThermoFisher must be open.
354 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again."""
355 )
356 return tbt.RetractableDeviceState(status)
359def retract_EDS(microscope: tbt.Microscope) -> bool:
360 """Retract EDS dector"""
361 # print(f"\t\tRetracting EDS detector")
362 connect_EDS()
363 eds_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
364 if eds_status == tbt.RetractableDeviceState.ERROR:
365 raise SystemError(
366 "EDS Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control"
367 )
368 if eds_status != tbt.RetractableDeviceState.RETRACTED:
369 print("\t\t\tEDS Camera retracting...")
370 CCD_view(microscope=microscope)
371 external.EDS_RetractCamera()
372 time.sleep(1)
373 if (
374 tbt.RetractableDeviceState(external.EDS_CameraStatus())
375 != tbt.RetractableDeviceState.RETRACTED
376 ):
377 raise SystemError("Error, EDS Camera retraction failed, workflow stopped.")
378 CCD_pause(microscope=microscope)
379 print("\t\tEDS Camera retracted")
380 return True
383def retract_device(microscope: tbt.Microscope, detector: tbt.DetectorType) -> bool:
384 CCD_view(microscope=microscope)
385 print(f"\t\tRetracting {detector.value} detector")
386 microscope.detector.type.value = detector.value
387 microscope.detector.retract()
388 state = tbt.RetractableDeviceState(microscope.detector.state)
389 if state != tbt.RetractableDeviceState.RETRACTED:
390 raise SystemError(
391 f"{detector.value} detector not retracted, current detector state is {detector_state.value}"
392 )
393 print(f"\t\t{detector.value} detector retracted")
394 CCD_pause(microscope=microscope)
396 return True
399def CCD_pause(
400 microscope: tbt.Microscope,
401 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT,
402) -> bool:
403 """Pauses CCD, typically used after device or stage movement"""
404 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
405 img.set_view(microscope=microscope, quad=quad)
406 try:
407 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA)
408 except:
409 warnings.warn("CCD camera is not installed on this microscope.")
410 else:
411 microscope.imaging.stop_acquisition()
412 finally:
413 microscope.imaging.set_active_view(initial_view.value)
414 return True
417def CCD_view(
418 microscope: tbt.Microscope,
419 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT,
420) -> bool:
421 """visualizes detector or stage movement for the user using the CCD camera"""
422 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
423 img.set_view(microscope=microscope, quad=quad)
424 try:
425 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA)
426 except:
427 warnings.warn("CCD camera is not installed on this microscope.")
428 else:
429 microscope.imaging.start_acquisition()
430 finally:
431 microscope.imaging.set_active_view(initial_view.value)
432 return True
435def ebsd_operation(
436 step: tbt.Step,
437 general_settings: tbt.GeneralSettings,
438 slice_number: int,
439) -> bool:
440 pass
441 image_settings = step.operation_settings.image
444def specimen_current(
445 microscope: tbt.Microscope,
446 hfw_mm=Constants.specimen_current_hfw_mm,
447 delay_s=Constants.specimen_current_delay_s,
448) -> float:
449 """Measures specimen current using the electron beam, return in nA"""
450 img.set_beam_device(
451 microscope=microscope,
452 device=tbt.Device.ELECTRON_BEAM,
453 )
454 initial_hfw_m = microscope.beams.electron_beam.horizontal_field_width.value
455 initial_detector = tbt.DetectorType(microscope.detector.type.value)
457 img.detector_type(microscope=microscope, detector=tbt.DetectorType.ETD)
458 img.beam_hfw(
459 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
460 microscope=microscope,
461 hfw_mm=hfw_mm,
462 )
463 microscope.imaging.start_acquisition()
464 time.sleep(delay_s)
465 current_na = microscope.state.specimen_current.value * cs.Conversions.A_TO_NA
466 microscope.imaging.stop_acquisition()
468 # reset detector and hfw
469 microscope.beams.electron_beam.horizontal_field_width.value = initial_hfw_m
470 img.detector_type(microscope=microscope, detector=initial_detector)
471 img.beam_hfw(
472 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
473 microscope=microscope,
474 hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM,
475 )
477 return current_na