Coverage for src/pytribeam/insertable_devices.py: 46%
244 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-09-03 19:02 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-09-03 19:02 +0000
1#!/usr/bin/python3
2"""Insertable-device, detector, CCD-view, and specimen-current utilities.
4This module provides utilities for controlling insertable microscope devices
5used during `pytribeam` workflows. It handles built-in microscope detectors
6through the microscope/AutoScript interface and EBSD/EDS camera control through
7the external Laser API interface when available.
9The main responsibilities of this module are:
11- checking whether microscope detectors are insertable,
12- querying detector and EBSD/EDS camera state,
13- preventing insertion of detector combinations known to collide,
14- inserting and retracting microscope detectors,
15- inserting and retracting EBSD and EDS cameras,
16- retracting all available insertable devices before workflow operations,
17- using the CCD camera to visualize detector or stage motion, and
18- measuring specimen current with the electron beam.
20Most workflow code should use `retract_all_devices`, `insert_detector`,
21`insert_EBSD`, `insert_EDS`, `CCD_view`, `CCD_pause`, and `specimen_current`
22rather than directly manipulating detector or camera state.
24## Device types
26This module works with two classes of insertable devices:
28| Device class | Examples | Control interface |
29| --- | --- | --- |
30| Microscope detectors | CBS, ABS, ETD, other AutoScript detectors | `microscope.detector` |
31| External EBSD/EDS cameras | EBSD camera, EDS camera | Laser API bridge |
33EBSD and EDS camera functions require the external Laser API object imported
34from `pytribeam.laser`. If that API is unavailable, EBSD/EDS insertion and
35retraction functions will not be usable.
37## Typical usage
39Retract all available and enabled insertable devices:
41```python
42from pytribeam import insertable_devices as devices
44devices.retract_all_devices(
45 microscope=microscope,
46 enable_EBSD=True,
47 enable_EDS=True,
48)
49```
51Insert a microscope detector after checking collision constraints:
53```python
54import pytribeam.types as tbt
55from pytribeam import insertable_devices as devices
57devices.insert_detector(
58 microscope=microscope,
59 detector=tbt.DetectorType.CBS,
60)
61```
63Insert EBSD or EDS cameras:
65```python
66from pytribeam import insertable_devices as devices
68devices.insert_EBSD(microscope)
69devices.insert_EDS(microscope)
70```
72Use the CCD camera to visualize motion:
74```python
75from pytribeam import insertable_devices as devices
77devices.CCD_view(microscope)
78# move stage or insert/retract detector
79devices.CCD_pause(microscope)
80```
82Measure specimen current:
84```python
85from pytribeam import insertable_devices as devices
87current_na = devices.specimen_current(microscope)
88```
90## Collision handling
92`detectors_will_collide` checks requested detector insertion against known
93disallowed detector combinations defined by `Constants.detector_collisions`.
94This check includes both microscope-controlled detectors and EBSD/EDS cameras
95when the external API is available.
97Insertion functions raise `SystemError` if the requested device may collide with
98another detector or if the device cannot be inserted successfully.
100## CCD visualization
102Several insertion, retraction, and stage-motion workflows use `CCD_view` and
103`CCD_pause` to provide visual feedback during hardware motion. These functions
104temporarily switch to the CCD camera in a selected view quadrant and then restore
105the initially active view.
107If the CCD camera is not available on the microscope, a warning is issued and
108the workflow continues.
110## Specimen-current measurement
112`specimen_current` temporarily switches to the electron beam, selects the ETD
113detector, sets the horizontal field width, acquires an image, reads the specimen
114current, and restores the original detector and field width.
116Specimen current is returned in nanoamperes.
118> **Warning**
119>
120> Functions in this module can move insertable hardware inside the microscope
121> chamber. Confirm detector positions, stage position, sample geometry, chamber
122> clearance, and workflow state before inserting or retracting devices.
124<hr style="height: 12px; background-color: #333; border: none;">
125"""
127__all__ = [
128 "detector_insertable",
129 "detector_state",
130 "detectors_will_collide",
131 "device_access",
132 "insert_EBSD",
133 "insert_EDS",
134 "insert_detector",
135 "retract_all_devices",
136 "connect_EBSD",
137 "retract_EBSD",
138 "connect_EDS",
139 "retract_EDS",
140 "retract_device",
141 "CCD_pause",
142 "CCD_view",
143 "specimen_current",
144]
146# Default python modules
147# from functools import singledispatch
148import time
149import warnings
151# 3rd party module
153# Local scripts
154import pytribeam.constants as cs
155from pytribeam.constants import Constants
156import pytribeam.image as img
158import pytribeam.types as tbt
159from pytribeam.laser import tfs_laser as external
160# try:
161# from pytribeam.laser import tfs_laser as external
162# except:
163# pass
166def detector_insertable(
167 microscope: tbt.Microscope,
168 detector: tbt.DetectorType,
169) -> bool:
170 """
171 Determine whether or not the built-in microscope detector is insertable and return its state.
173 This function checks if the specified detector is being read by Autoscript and if it is insertable.
175 ## Parameters
177 - `microscope` (`tbt.Microscope`): The microscope object for which to check the detector.
178 - `detector` (`tbt.DetectorType`): The type of the detector to check.
180 ## Returns
182 - `bool`: True if the detector is insertable, False otherwise.
184 ## Warnings
186 - `UserWarning`: If the detector type is invalid for the currently selected device or if the detector is not found on the system.
187 """
188 # check if the detector is being read by Autoscript
189 try:
190 # make requested detector the active detector
191 microscope.detector.type.value = detector.value
192 except:
193 warnings.warn(
194 f"""Warning. Invalid detector type of "{detector.value}" for currently selected device
195 of "{tbt.Device(microscope.imaging.get_active_device()).value}" or detector not found on this system.
196 Detector will be assumed to not be insertable."""
197 )
198 return False
200 # check if it is insertable
201 try:
202 state = microscope.detector.state
203 if state == tbt.RetractableDeviceState.STATIONARY.value:
204 return False
205 return True
206 except Exception:
207 return False
210def detector_state(
211 microscope: tbt.Microscope,
212 detector: tbt.DetectorType,
213) -> tbt.RetractableDeviceState:
214 """
215 Determine the state of the detector, only valid if the detector is insertable.
217 This function checks if the specified detector is insertable and returns its state.
219 ## Parameters
221 - `microscope` (`tbt.Microscope`): The microscope object for which to check the detector state.
222 - `detector` (`tbt.DetectorType`): The type of the detector to check.
224 ## Returns
226 - `tbt.RetractableDeviceState`: The state of the detector if it is insertable, None otherwise.
227 """
228 # check if the detector is being read by Autoscriptdevice_access(microscope)
229 # try:
230 # return tbt.RetractableDeviceState(microscope.detector.state)
231 # except Exception:
232 # return tbt.RetractableDeviceState.STATIONARY
233 if not detector_insertable( 233 ↛ 238line 233 didn't jump to line 238 because the condition on line 233 was always true
234 microscope=microscope,
235 detector=detector,
236 ):
237 return tbt.RetractableDeviceState.STATIONARY
238 return tbt.RetractableDeviceState(microscope.detector.state)
241def detectors_will_collide(
242 microscope: tbt.Microscope,
243 detector_to_insert: tbt.DetectorType,
244) -> bool:
245 """
246 Determine if a collision may occur when inserting the specified detector.
248 This function checks if inserting the specified detector will cause a collision with any other detectors.
250 ## Parameters
252 - `microscope` (`tbt.Microscope`): The microscope object for which to check for potential collisions.
253 - `detector_to_insert` (`tbt.DetectorType`): The type of the detector to insert.
255 ## Returns
257 - `bool`: True if a collision may occur, False otherwise.
258 """
259 device_retracted = tbt.RetractableDeviceState.RETRACTED.value
260 for detector_combo in Constants.detector_collisions: 260 ↛ 275line 260 didn't jump to line 275 because the loop on line 260 didn't complete
261 if detector_to_insert in detector_combo: 261 ↛ 260, 261 ↛ 2622 missed branches: 1) line 261 didn't jump to line 260 because the condition on line 261 was always true, 2) line 261 didn't jump to line 262 because the condition on line 261 was never true
262 for detector in detector_combo:
263 if detector == detector_to_insert: 263 ↛ 264, 263 ↛ 2652 missed branches: 1) line 263 didn't jump to line 264 because the condition on line 263 was never true, 2) line 263 didn't jump to line 265 because the condition on line 263 was always true
264 continue
265 if detector == tbt.DetectorType.EDS:
266 if external.EDS_CameraStatus() != device_retracted:
267 return True
268 elif detector == tbt.DetectorType.EBSD: 268 ↛ 269, 268 ↛ 2722 missed branches: 1) line 268 didn't jump to line 269 because the condition on line 268 was never true, 2) line 268 didn't jump to line 272 because the condition on line 268 was always true
269 if external.EBSD_CameraStatus() != device_retracted:
270 return True
271 else:
272 state = detector_state(microscope=microscope, detector=detector)
273 if state.value != device_retracted: 273 ↛ 262, 273 ↛ 2742 missed branches: 1) line 273 didn't jump to line 262 because the condition on line 273 was always true, 2) line 273 didn't jump to line 274 because the condition on line 273 was never true
274 return True
275 return False
278def device_access(microscope: tbt.Microscope) -> bool:
279 """
280 Switch to the upper-left quadrant and assign the electron beam as the active device.
282 This function switches the view to the upper-left quadrant and assigns the electron beam as the active device, which is the only device with access to insertable devices like the CBS/ABS detector. Other devices, like the ion beam, CCD, or Nav-Cam, do not have CBS/ABS access.
284 ## Parameters
286 - `microscope` (`tbt.Microscope`): The microscope object for which to switch the view and assign the active device.
288 ## Returns
290 - `True`: True if the operation was successful.
291 """
292 img.set_view(
293 microscope=microscope,
294 quad=tbt.ViewQuad.UPPER_LEFT,
295 )
296 img.set_beam_device(
297 microscope=microscope,
298 device=tbt.Device.ELECTRON_BEAM,
299 )
300 return True
303def insert_EBSD(
304 microscope: tbt.Microscope,
305) -> bool:
306 """
307 Insert the EBSD camera into the microscope.
309 This function connects to the EBSD system, checks for potential collisions with other detectors, and inserts the EBSD camera if it is not already inserted. It raises an error if the EBSD camera cannot be inserted.
311 ## Parameters
313 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the EBSD camera.
315 ## Returns
317 - `bool`: True if the EBSD camera is successfully inserted.
319 ## Raises
321 - `SystemError`: If a collision may occur with another detector, if the EBSD camera is in an error state, if the EBSD mapping is not idle, or if the EBSD camera cannot be inserted.
322 """
323 connect_EBSD()
324 if detectors_will_collide( 324 ↛ 332line 324 didn't jump to line 332 because the condition on line 324 was always true
325 microscope=microscope,
326 detector_to_insert=tbt.DetectorType.EBSD,
327 ):
328 raise SystemError(
329 f"""Error. Cannot insert EBSD which may collide with another detector.
330 Disallowed detector combinations are: {Constants.detector_collisions}"""
331 )
332 ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
333 map_status = tbt.MapStatus(external.EBSD_MappingStatus())
334 if ebsd_cam_status == tbt.RetractableDeviceState.ERROR: 334 ↛ 335, 334 ↛ 3362 missed branches: 1) line 334 didn't jump to line 335 because the condition on line 334 was never true, 2) line 334 didn't jump to line 336 because the condition on line 334 was always true
335 raise SystemError("Error, EDS Camera in error state, workflow stopped.")
336 if map_status != tbt.MapStatus.IDLE:
337 raise SystemError(
338 f'Error, EBSD mapping not in "{tbt.MapStatus.IDLE.value}" state.'
339 )
340 if ebsd_cam_status != tbt.RetractableDeviceState.INSERTED: 340 ↛ 362line 340 didn't jump to line 362 because the condition on line 340 was always true
341 print("\tInserting EBSD Camera...")
342 # TODO change to constants
343 minutes_to_wait = 3
344 timeout = minutes_to_wait * 60 # seconds
345 waittime = 4 # seconds
346 CCD_view(microscope=microscope)
347 # Oxford Inst requires 2 inserts
348 while True:
349 external.EBSD_InsertCamera() # inserted state
350 if ( 350 ↛ 355line 350 didn't jump to line 355
351 external.EBSD_CameraStatus()
352 == tbt.RetractableDeviceState.INSERTED.value
353 ):
354 break
355 time.sleep(waittime)
356 timeout = timeout - waittime
357 if timeout < 1: 357 ↛ 349, 357 ↛ 3582 missed branches: 1) line 357 didn't jump to line 349 because the condition on line 357 was always true, 2) line 357 didn't jump to line 358 because the condition on line 357 was never true
358 warnings.warn("Warning: EBSD insert timeout. Trying to continue...")
359 break
360 CCD_pause(microscope=microscope)
362 new_ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
363 if new_ebsd_cam_status == tbt.RetractableDeviceState.INSERTED:
364 print("\tEBSD Camera inserted")
365 return True
366 raise SystemError(
367 f'EBSDS Camera is not inserted, currently in "{new_ebsd_cam_status}" state'
368 )
371def insert_EDS(
372 microscope: tbt.Microscope,
373) -> bool:
374 """
375 Insert the EDS camera into the microscope.
377 This function connects to the EDS system, checks for potential collisions with other detectors, and inserts the EDS camera if it is not already inserted. It raises an error if the EDS camera cannot be inserted.
379 ## Parameters
381 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the EDS camera.
383 ## Returns
385 - `bool`: True if the EDS camera is successfully inserted.
387 ## Raises
389 - `SystemError`: If a collision may occur with another detector, if the EDS camera is in an error state, if the EDS mapping is not idle, or if the EDS camera cannot be inserted.
390 """
391 connect_EDS()
392 if detectors_will_collide( 392 ↛ 400line 392 didn't jump to line 400 because the condition on line 392 was always true
393 microscope=microscope,
394 detector_to_insert=tbt.DetectorType.EDS,
395 ):
396 raise SystemError(
397 f"""Error. Cannot insert EDS while CBS not in "Retracted" state.
398 CBS detector currently in "{detector_state(microscope=microscope, detector=tbt.DetectorType.CBS).value}" state."""
399 )
400 eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
401 map_status = tbt.MapStatus(external.EDS_MappingStatus())
402 if eds_cam_status == tbt.RetractableDeviceState.ERROR: 402 ↛ 404line 402 didn't jump to line 404 because the condition on line 402 was always true
403 raise SystemError("Error, EDS Camera in error state, workflow stopped.")
404 if map_status != tbt.MapStatus.IDLE: 404 ↛ 408line 404 didn't jump to line 408 because the condition on line 404 was always true
405 raise SystemError(
406 f'Error, EDS mapping not in "{tbt.MapStatus.IDLE.value}" state.'
407 )
408 if eds_cam_status != tbt.RetractableDeviceState.INSERTED: 408 ↛ 409, 408 ↛ 4142 missed branches: 1) line 408 didn't jump to line 409 because the condition on line 408 was never true, 2) line 408 didn't jump to line 414 because the condition on line 408 was always true
409 print("\tInserting EDS Camera...")
410 CCD_view(microscope=microscope)
411 external.EDS_InsertCamera()
412 CCD_pause(microscope=microscope)
414 new_eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
415 if new_eds_cam_status == tbt.RetractableDeviceState.INSERTED:
416 print("\tEDS Camera inserted")
417 return True
418 raise SystemError(
419 f'EDS Camera is not inserted, currently in "{new_eds_cam_status}" state'
420 )
423def insert_detector(
424 microscope: tbt.Microscope,
425 detector: tbt.DetectorType,
426 time_delay_s: float = 0.5,
427) -> bool:
428 """
429 Insert the selected detector into the microscope.
431 This function ensures the specified detector is the active one, confirms it is insertable, and inserts it if it is not already inserted. It raises an error if the detector cannot be inserted.
433 ## Parameters
435 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the detector.
436 - `detector` (`tbt.DetectorType`): The type of the detector to insert.
437 - `time_delay_s` (`float, optional`): The time delay in seconds after inserting the detector (default is 0.5 seconds).
439 ## Returns
441 - `bool`: True if the detector is successfully inserted.
443 ## Raises
445 - `ValueError`: If the detector is not insertable.
446 - `SystemError`: If a collision may occur with another detector or if the detector cannot be inserted.
447 """
448 # ensure detector is the active one
449 microscope.detector.type.value = detector.value
450 # confirm detector is insertable
451 try:
452 state = microscope.detector.state
453 except:
454 raise ValueError(f"{detector.value} detector is not insertable.")
455 if state == tbt.RetractableDeviceState.RETRACTED.value:
456 if detectors_will_collide( 456 ↛ 465line 456 didn't jump to line 465 because the condition on line 456 was always true
457 microscope=microscope,
458 detector_to_insert=detector,
459 ):
460 raise SystemError(
461 f"""Error. Cannot insert {detector.value} which may collide with another detector.
462 Disallowed detector combinations are: {Constants.detector_collisions}"""
463 )
465 print(f"\tInserting {detector.value} detector...")
466 CCD_view(microscope=microscope)
467 microscope.detector.insert()
468 time.sleep(time_delay_s)
469 CCD_pause(microscope=microscope)
470 if microscope.detector.state == tbt.RetractableDeviceState.INSERTED.value:
471 print(f"\t\t{detector.value} detector inserted.")
472 return True
473 elif state == tbt.RetractableDeviceState.INSERTED.value: 473 ↛ 476line 473 didn't jump to line 476 because the condition on line 473 was always true
474 print(f"\t{detector.value} detector is already inserted.")
475 return True
476 raise SystemError(
477 f'Cannot insert {detector.value} detector, current detector state is "{state}".'
478 )
481def retract_all_devices(
482 microscope: tbt.Microscope,
483 enable_EBSD: bool,
484 enable_EDS: bool,
485) -> bool:
486 # TODO come up with better system for enable_EBSD_EDS
487 """
488 Retract all insertable devices, including microscope detectors and EBSD/EDS detectors if integrated.
490 This function retracts all insertable devices, first retracting microscope detectors and then retracting EBSD/EDS detectors if they are integrated and enabled.
492 ## Parameters
494 - `microscope` (`tbt.Microscope`): The microscope object for accessing the Autoscript API.
495 - `enable_EBSD` (`bool`): Whether to enable retraction of the EBSD detector.
496 - `enable_EDS` (`bool`): Whether to enable retraction of the EDS detector.
498 ## Returns
500 - `bool`: True if all devices are successfully retracted.
502 ## Raises
504 - `None`
505 """
506 print("\tRetracting devices, do not interact with xTUI during this process...")
507 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
508 device_access(microscope)
510 for detector in microscope.detector.type.available_values:
511 detector = tbt.DetectorType(detector) # overwrite
512 state = detector_state(
513 microscope=microscope,
514 detector=detector,
515 )
516 if ( 516 ↛ 521line 516 didn't jump to line 521
517 state != tbt.RetractableDeviceState.STATIONARY
518 and state != tbt.RetractableDeviceState.RETRACTED
519 ):
520 # if (state is not None) and (state != tbt.RetractableDeviceState.RETRACTED):
521 retract_device(
522 microscope=microscope,
523 detector=detector,
524 )
526 # EBSD/EDS detectors:
527 if external is None: 527 ↛ 534line 527 didn't jump to line 534 because the condition on line 527 was always true
528 # try:
529 # external
530 # except NameError:
531 # pass
532 print("\t\tLaser API not imported, EBSD and EDS detectors are unavailable")
533 else:
534 if enable_EBSD:
535 retract_EBSD(microscope=microscope)
536 if enable_EDS:
537 retract_EDS(microscope=microscope)
539 # reset initial settings:
540 img.set_view(
541 microscope=microscope,
542 quad=initial_view,
543 )
544 print("\t\tAll available and enabled devices retracted.")
545 return True
548def connect_EBSD() -> tbt.RetractableDeviceState:
549 """
550 Connect to the EBSD system and retrieve the camera status.
552 This function attempts to connect to the EBSD system and retrieve the camera status. It raises a ConnectionError if the connection fails.
554 ## Returns
556 tbt.RetractableDeviceState
557 The status of the EBSD camera.
559 ## Raises
561 ConnectionError
562 If the EBSD control is not connected.
564 """
565 try:
566 status = external.EBSD_CameraStatus()
567 except:
568 raise ConnectionError(
569 """EBSD control not connected, "Laser Control" from ThermoFisher must be open.
570 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again."""
571 )
572 return tbt.RetractableDeviceState(status)
575def retract_EBSD(microscope: tbt.Microscope) -> bool:
576 """
577 Retract the EBSD camera from the microscope.
579 This function connects to the EBSD system, checks the camera status, and retracts the EBSD camera if it is not already retracted. It raises an error if the EBSD camera cannot be retracted.
581 ## Parameters
583 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the EBSD camera.
585 ## Returns
587 - `bool`: True if the EBSD camera is successfully retracted.
589 ## Raises
591 - `SystemError`: If the EBSD camera is in an error state, if the EBSD mapping is not completed, or if the EBSD camera retraction fails.
592 """
593 connect_EBSD()
594 ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
595 if ebsd_status == tbt.RetractableDeviceState.ERROR:
596 raise SystemError(
597 "EBSD Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control"
598 )
599 if ebsd_status != tbt.RetractableDeviceState.RETRACTED:
600 print(
601 '\t\t\tEBSD Camera Retraction requested, please wait for "mapping complete" verification...'
602 )
603 map_status = tbt.MapStatus(external.EBSD_MappingStatus())
604 # first check if mapping is finished properly
605 minutes_to_wait = 5 # TODO set constant
606 timeout = minutes_to_wait * 60 # seconds #TODO
607 cameraokconfirmations = 3 # synchronization issue with EDAX, try to get map completed 3x before continuing
608 waittime = 10 # seconds
609 if map_status == tbt.MapStatus.ACTIVE:
610 print("\t\t\tEBSD mapping currently active, waiting for mapping to finish")
611 while True:
612 current_map_status = tbt.MapStatus(external.EBSD_MappingStatus())
613 if current_map_status != tbt.MapStatus.ACTIVE:
614 cameraokconfirmations = cameraokconfirmations - 1
615 waittime = 3 # shorten wait time, polling 3x to see if mapping was really completed.
616 time.sleep(waittime)
617 timeout = timeout - waittime
618 if cameraokconfirmations < 1:
619 delay = minutes_to_wait * 60 - timeout
620 print(f"\t\t\t\tEBSD mapping finished. Delay of {delay} seconds")
621 break
622 if timeout < 1:
623 warnings.warn(
624 "\t\t\tWarning, EBSD mapping timeout. Trying to continue..."
625 )
626 break
627 CCD_view(microscope=microscope)
628 print("\t\t\tEBSD Camera retracting...")
629 external.EBSD_RetractCamera()
630 time.sleep(1)
631 CCD_pause(microscope=microscope)
632 current_ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
633 if current_ebsd_status != tbt.RetractableDeviceState.RETRACTED: 633 ↛ 635line 633 didn't jump to line 635 because the condition on line 633 was always true
634 raise SystemError("Error, EBSD Camera retraction failed, workflow stopped.")
635 print("\t\tEBSD Camera retracted")
636 return True
639def connect_EDS() -> tbt.RetractableDeviceState:
640 """
641 Connect to the EDS system and retrieve the camera status.
643 This function attempts to connect to the EDS system and retrieve the camera status. It raises a ConnectionError if the connection fails.
645 ## Returns
647 tbt.RetractableDeviceState
648 The status of the EDS camera.
650 ## Raises
652 ConnectionError
653 If the EDS control is not connected.
655 """
656 try:
657 status = external.EDS_CameraStatus()
658 except:
659 raise ConnectionError(
660 """EDS control not connected, "Laser Control" from ThermoFisher must be open.
661 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again."""
662 )
663 return tbt.RetractableDeviceState(status)
666def retract_EDS(microscope: tbt.Microscope) -> bool:
667 """
668 Retract the EDS detector from the microscope.
670 This function connects to the EDS system, checks the camera status, and retracts the EDS camera if it is not already retracted. It raises an error if the EDS camera cannot be retracted.
672 ## Parameters
674 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the EDS camera.
676 ## Returns
678 - `bool`: True if the EDS camera is successfully retracted.
680 ## Raises
682 - `SystemError`: If the EDS camera is in an error state or if the EDS camera retraction fails.
683 """
684 # print(f"\t\tRetracting EDS detector")
685 connect_EDS()
686 eds_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
687 if eds_status == tbt.RetractableDeviceState.ERROR:
688 raise SystemError(
689 "EDS Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control"
690 )
691 if eds_status != tbt.RetractableDeviceState.RETRACTED: 691 ↛ 703line 691 didn't jump to line 703 because the condition on line 691 was always true
692 print("\t\t\tEDS Camera retracting...")
693 CCD_view(microscope=microscope)
694 external.EDS_RetractCamera()
695 time.sleep(1)
696 if (
697 tbt.RetractableDeviceState(external.EDS_CameraStatus())
698 != tbt.RetractableDeviceState.RETRACTED
699 ):
700 raise SystemError("Error, EDS Camera retraction failed, workflow stopped.")
701 CCD_pause(microscope=microscope)
702 print("\t\tEDS Camera retracted")
703 return True
706def retract_device(microscope: tbt.Microscope, detector: tbt.DetectorType) -> bool:
707 """
708 Retract the specified detector from the microscope.
710 This function ensures the specified detector is the active one, retracts it, and checks its state. It raises an error if the detector cannot be retracted.
712 ## Parameters
714 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the detector.
715 - `detector` (`tbt.DetectorType`): The type of the detector to retract.
717 ## Returns
719 - `bool`: True if the detector is successfully retracted.
721 ## Raises
723 - `SystemError`: If the detector cannot be retracted.
724 """
725 CCD_view(microscope=microscope)
726 print(f"\t\tRetracting {detector.value} detector")
727 microscope.detector.type.value = detector.value
728 microscope.detector.retract()
729 state = tbt.RetractableDeviceState(microscope.detector.state)
730 if state != tbt.RetractableDeviceState.RETRACTED: 730 ↛ 734line 730 didn't jump to line 734 because the condition on line 730 was always true
731 raise SystemError(
732 f"{detector.value} detector not retracted, current detector state is {state.value}"
733 )
734 print(f"\t\t{detector.value} detector retracted")
735 CCD_pause(microscope=microscope)
737 return True
740def CCD_pause(
741 microscope: tbt.Microscope,
742 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT,
743) -> bool:
744 """
745 Pause the CCD camera, typically used after device or stage movement.
747 This function pauses the CCD camera by switching to the specified quadrant, setting the beam device to the CCD camera, and stopping the acquisition. It restores the initial view afterward.
749 ## Parameters
751 - `microscope` (`tbt.Microscope`): The microscope object for which to pause the CCD camera.
752 - `quad` (`tbt.ViewQuad, optional`): The quadrant to switch to before pausing the CCD camera (default is tbt.ViewQuad.LOWER_RIGHT).
754 ## Returns
756 - `bool`: True if the CCD camera is successfully paused.
758 ## Warnings
760 - `UserWarning`: If the CCD camera is not installed on the microscope.
761 """
762 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
763 img.set_view(microscope=microscope, quad=quad)
764 try:
765 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA)
766 except:
767 warnings.warn("CCD camera is not installed on this microscope.")
768 else:
769 microscope.imaging.stop_acquisition()
770 finally:
771 microscope.imaging.set_active_view(initial_view.value)
773 return True
776def CCD_view(
777 microscope: tbt.Microscope,
778 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT,
779) -> bool:
780 """
781 Visualize detector or stage movement for the user using the CCD camera.
783 This function visualizes detector or stage movement by switching to the specified quadrant, setting the beam device to the CCD camera, and starting the acquisition. It restores the initial view afterward.
785 ## Parameters
787 - `microscope` (`tbt.Microscope`): The microscope object for which to visualize the movement.
788 - `quad` (`tbt.ViewQuad, optional`): The quadrant to switch to before visualizing the movement (default is tbt.ViewQuad.LOWER_RIGHT).
790 ## Returns
792 - `bool`: True if the CCD camera is successfully used for visualization.
794 ## Warnings
796 - `UserWarning`: If the CCD camera is not installed on the microscope.
797 """
798 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
799 img.set_view(microscope=microscope, quad=quad)
800 try:
801 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA)
802 except:
803 warnings.warn("CCD camera is not installed on this microscope.")
804 else:
805 microscope.imaging.start_acquisition()
806 finally:
807 microscope.imaging.set_active_view(initial_view.value)
809 return True
812def specimen_current(
813 microscope: tbt.Microscope,
814 hfw_mm=Constants.specimen_current_hfw_mm,
815 delay_s=Constants.specimen_current_delay_s,
816) -> float:
817 """
818 Measure the specimen current using the electron beam and return the value in nA.
820 This function sets the beam device to the electron beam, adjusts the horizontal field width (HFW) and detector, starts the acquisition, and measures the specimen current. It then resets the detector and HFW to their initial values.
822 ## Parameters
824 - `microscope` (`tbt.Microscope`): The microscope object for which to measure the specimen current.
825 - `hfw_mm` (`float, optional`): The horizontal field width in millimeters (default is Constants.specimen_current_hfw_mm).
826 - `delay_s` (`float, optional`): The delay in seconds before measuring the specimen current (default is Constants.specimen_current_delay_s).
828 ## Returns
830 - `float`: The measured specimen current in nA.
831 """
832 img.set_beam_device(
833 microscope=microscope,
834 device=tbt.Device.ELECTRON_BEAM,
835 )
836 initial_hfw_m = microscope.beams.electron_beam.horizontal_field_width.value
837 initial_detector = tbt.DetectorType(microscope.detector.type.value)
839 # TODO: A safer structure would be a try/except block
840 # try:
841 # img.detector_type(microscope=microscope, detector=tbt.DetectorType.ETD)
842 # img.beam_hfw(
843 # beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
844 # microscope=microscope,
845 # hfw_mm=hfw_mm,
846 # )
847 # microscope.imaging.start_acquisition()
848 # time.sleep(delay_s)
849 # return microscope.state.specimen_current.value * cs.Conversions.A_TO_NA
850 # finally:
851 # microscope.imaging.stop_acquisition()
852 # microscope.beams.electron_beam.horizontal_field_width.value = initial_hfw_m
853 # img.detector_type(microscope=microscope, detector=initial_detector)
854 # img.beam_hfw(
855 # beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
856 # microscope=microscope,
857 # hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM,
858 # )
860 img.detector_type(microscope=microscope, detector=tbt.DetectorType.ETD)
861 img.beam_hfw(
862 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
863 microscope=microscope,
864 hfw_mm=hfw_mm,
865 )
866 microscope.imaging.start_acquisition()
867 time.sleep(delay_s)
868 current_na = microscope.state.specimen_current.value * cs.Conversions.A_TO_NA
869 microscope.imaging.stop_acquisition()
871 # reset detector and hfw
872 microscope.beams.electron_beam.horizontal_field_width.value = initial_hfw_m
873 img.detector_type(microscope=microscope, detector=initial_detector)
874 img.beam_hfw(
875 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
876 microscope=microscope,
877 hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM,
878 )
880 return current_na