Coverage for src/pytribeam/insertable_devices.py: 85%
244 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-10-23 20:42 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-10-23 20:42 +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
162def detector_insertable(
163 microscope: tbt.Microscope,
164 detector: tbt.DetectorType,
165) -> bool:
166 """
167 Determine whether or not the built-in microscope detector is insertable and return its state.
169 This function checks if the specified detector is being read by Autoscript and if it is insertable.
171 ## Parameters
173 - `microscope` (`tbt.Microscope`): The microscope object for which to check the detector.
174 - `detector` (`tbt.DetectorType`): The type of the detector to check.
176 ## Returns
178 - `bool`: True if the detector is insertable, False otherwise.
180 ## Warnings
182 - `UserWarning`: If the detector type is invalid for the currently selected device or if the detector is not found on the system.
183 """
184 # check if the detector is being read by Autoscript
185 try:
186 # make requested detector the active detector
187 microscope.detector.type.value = detector.value
188 except:
189 warnings.warn(
190 f"""Warning. Invalid detector type of "{detector.value}" for currently selected device
191 of "{tbt.Device(microscope.imaging.get_active_device()).value}" or detector not found on this system.
192 Detector will be assumed to not be insertable."""
193 )
194 return False
196 # check if it is insertable
197 try:
198 state = microscope.detector.state
199 if state == tbt.RetractableDeviceState.STATIONARY.value:
200 return False
201 return True
202 except Exception:
203 return False
206def detector_state(
207 microscope: tbt.Microscope,
208 detector: tbt.DetectorType,
209) -> tbt.RetractableDeviceState:
210 """
211 Determine the state of the detector, only valid if the detector is insertable.
213 This function checks if the specified detector is insertable and returns its state.
215 ## Parameters
217 - `microscope` (`tbt.Microscope`): The microscope object for which to check the detector state.
218 - `detector` (`tbt.DetectorType`): The type of the detector to check.
220 ## Returns
222 - `tbt.RetractableDeviceState`: The state of the detector if it is insertable, None otherwise.
223 """
224 # check if the detector is being read by Autoscriptdevice_access(microscope)
225 # try:
226 # return tbt.RetractableDeviceState(microscope.detector.state)
227 # except Exception:
228 # return tbt.RetractableDeviceState.STATIONARY
229 if not detector_insertable(
230 microscope=microscope,
231 detector=detector,
232 ):
233 return tbt.RetractableDeviceState.STATIONARY
234 return tbt.RetractableDeviceState(microscope.detector.state)
237def detectors_will_collide(
238 microscope: tbt.Microscope,
239 detector_to_insert: tbt.DetectorType,
240) -> bool:
241 """
242 Determine if a collision may occur when inserting the specified detector.
244 This function checks if inserting the specified detector will cause a collision with any other detectors.
246 ## Parameters
248 - `microscope` (`tbt.Microscope`): The microscope object for which to check for potential collisions.
249 - `detector_to_insert` (`tbt.DetectorType`): The type of the detector to insert.
251 ## Returns
253 - `bool`: True if a collision may occur, False otherwise.
254 """
255 device_retracted = tbt.RetractableDeviceState.RETRACTED.value
256 for detector_combo in Constants.detector_collisions:
257 if detector_to_insert in detector_combo:
258 for detector in detector_combo:
259 if detector == detector_to_insert:
260 continue
261 if detector == tbt.DetectorType.EDS:
262 if external.EDS_CameraStatus() != device_retracted:
263 return True
264 elif detector == tbt.DetectorType.EBSD:
265 if external.EBSD_CameraStatus() != device_retracted: 265 ↛ 266line 265 didn't jump to line 266 because the condition on line 265 was never true
266 return True
267 else:
268 state = detector_state(microscope=microscope, detector=detector)
269 if state.value != device_retracted:
270 return True
271 return False
274def device_access(microscope: tbt.Microscope) -> bool:
275 """
276 Switch to the upper-left quadrant and assign the electron beam as the active device.
278 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.
280 ## Parameters
282 - `microscope` (`tbt.Microscope`): The microscope object for which to switch the view and assign the active device.
284 ## Returns
286 - `True`: True if the operation was successful.
287 """
288 img.set_view(
289 microscope=microscope,
290 quad=tbt.ViewQuad.UPPER_LEFT,
291 )
292 img.set_beam_device(
293 microscope=microscope,
294 device=tbt.Device.ELECTRON_BEAM,
295 )
296 return True
299def insert_EBSD(
300 microscope: tbt.Microscope,
301) -> bool:
302 """
303 Insert the EBSD camera into the microscope.
305 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.
307 ## Parameters
309 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the EBSD camera.
311 ## Returns
313 - `bool`: True if the EBSD camera is successfully inserted.
315 ## Raises
317 - `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.
318 """
319 connect_EBSD()
320 if detectors_will_collide( 320 ↛ 324line 320 didn't jump to line 324 because the condition on line 320 was never true
321 microscope=microscope,
322 detector_to_insert=tbt.DetectorType.EBSD,
323 ):
324 raise SystemError(
325 f"""Error. Cannot insert EBSD which may collide with another detector.
326 Disallowed detector combinations are: {Constants.detector_collisions}"""
327 )
328 ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
329 map_status = tbt.MapStatus(external.EBSD_MappingStatus())
330 if ebsd_cam_status == tbt.RetractableDeviceState.ERROR: 330 ↛ 331line 330 didn't jump to line 331 because the condition on line 330 was never true
331 raise SystemError("Error, EDS Camera in error state, workflow stopped.")
332 if map_status != tbt.MapStatus.IDLE: 332 ↛ 333line 332 didn't jump to line 333 because the condition on line 332 was never true
333 raise SystemError(
334 f'Error, EBSD mapping not in "{tbt.MapStatus.IDLE.value}" state.'
335 )
336 if ebsd_cam_status != tbt.RetractableDeviceState.INSERTED: 336 ↛ 358line 336 didn't jump to line 358 because the condition on line 336 was always true
337 print("\tInserting EBSD Camera...")
338 # TODO change to constants
339 minutes_to_wait = 3
340 timeout = minutes_to_wait * 60 # seconds
341 waittime = 4 # seconds
342 CCD_view(microscope=microscope)
343 # Oxford Inst requires 2 inserts
344 while True:
345 external.EBSD_InsertCamera() # inserted state
346 if (
347 external.EBSD_CameraStatus()
348 == tbt.RetractableDeviceState.INSERTED.value
349 ):
350 break
351 time.sleep(waittime)
352 timeout = timeout - waittime
353 if timeout < 1: 353 ↛ 354line 353 didn't jump to line 354 because the condition on line 353 was never true
354 warnings.warn("Warning: EBSD insert timeout. Trying to continue...")
355 break
356 CCD_pause(microscope=microscope)
358 new_ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
359 if new_ebsd_cam_status == tbt.RetractableDeviceState.INSERTED: 359 ↛ 362line 359 didn't jump to line 362 because the condition on line 359 was always true
360 print("\tEBSD Camera inserted")
361 return True
362 raise SystemError(
363 f'EBSDS Camera is not inserted, currently in "{new_ebsd_cam_status}" state'
364 )
367def insert_EDS(
368 microscope: tbt.Microscope,
369) -> bool:
370 """
371 Insert the EDS camera into the microscope.
373 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.
375 ## Parameters
377 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the EDS camera.
379 ## Returns
381 - `bool`: True if the EDS camera is successfully inserted.
383 ## Raises
385 - `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.
386 """
387 connect_EDS()
388 if detectors_will_collide(
389 microscope=microscope,
390 detector_to_insert=tbt.DetectorType.EDS,
391 ):
392 raise SystemError(
393 f"""Error. Cannot insert EDS while CBS not in "Retracted" state.
394 CBS detector currently in "{detector_state(microscope=microscope, detector=tbt.DetectorType.CBS).value}" state."""
395 )
396 eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
397 map_status = tbt.MapStatus(external.EDS_MappingStatus())
398 if eds_cam_status == tbt.RetractableDeviceState.ERROR: 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true
399 raise SystemError("Error, EDS Camera in error state, workflow stopped.")
400 if map_status != tbt.MapStatus.IDLE: 400 ↛ 401line 400 didn't jump to line 401 because the condition on line 400 was never true
401 raise SystemError(
402 f'Error, EDS mapping not in "{tbt.MapStatus.IDLE.value}" state.'
403 )
404 if eds_cam_status != tbt.RetractableDeviceState.INSERTED: 404 ↛ 410line 404 didn't jump to line 410 because the condition on line 404 was always true
405 print("\tInserting EDS Camera...")
406 CCD_view(microscope=microscope)
407 external.EDS_InsertCamera()
408 CCD_pause(microscope=microscope)
410 new_eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
411 if new_eds_cam_status == tbt.RetractableDeviceState.INSERTED: 411 ↛ 414line 411 didn't jump to line 414 because the condition on line 411 was always true
412 print("\tEDS Camera inserted")
413 return True
414 raise SystemError(
415 f'EDS Camera is not inserted, currently in "{new_eds_cam_status}" state'
416 )
419def insert_detector(
420 microscope: tbt.Microscope,
421 detector: tbt.DetectorType,
422 time_delay_s: float = 0.5,
423) -> bool:
424 """
425 Insert the selected detector into the microscope.
427 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.
429 ## Parameters
431 - `microscope` (`tbt.Microscope`): The microscope object for which to insert the detector.
432 - `detector` (`tbt.DetectorType`): The type of the detector to insert.
433 - `time_delay_s` (`float, optional`): The time delay in seconds after inserting the detector (default is 0.5 seconds).
435 ## Returns
437 - `bool`: True if the detector is successfully inserted.
439 ## Raises
441 - `ValueError`: If the detector is not insertable.
442 - `SystemError`: If a collision may occur with another detector or if the detector cannot be inserted.
443 """
444 # ensure detector is the active one
445 microscope.detector.type.value = detector.value
446 # confirm detector is insertable
447 try:
448 state = microscope.detector.state
449 except:
450 raise ValueError(f"{detector.value} detector is not insertable.")
451 if state == tbt.RetractableDeviceState.RETRACTED.value: 451 ↛ 469line 451 didn't jump to line 469 because the condition on line 451 was always true
452 if detectors_will_collide(
453 microscope=microscope,
454 detector_to_insert=detector,
455 ):
456 raise SystemError(
457 f"""Error. Cannot insert {detector.value} which may collide with another detector.
458 Disallowed detector combinations are: {Constants.detector_collisions}"""
459 )
461 print(f"\tInserting {detector.value} detector...")
462 CCD_view(microscope=microscope)
463 microscope.detector.insert()
464 time.sleep(time_delay_s)
465 CCD_pause(microscope=microscope)
466 if microscope.detector.state == tbt.RetractableDeviceState.INSERTED.value: 466 ↛ 472line 466 didn't jump to line 472 because the condition on line 466 was always true
467 print(f"\t\t{detector.value} detector inserted.")
468 return True
469 elif state == tbt.RetractableDeviceState.INSERTED.value:
470 print(f"\t{detector.value} detector is already inserted.")
471 return True
472 raise SystemError(
473 f'Cannot insert {detector.value} detector, current detector state is "{state}".'
474 )
477def retract_all_devices(
478 microscope: tbt.Microscope,
479 enable_EBSD: bool,
480 enable_EDS: bool,
481) -> bool:
482 # TODO come up with better system for enable_EBSD_EDS
483 """
484 Retract all insertable devices, including microscope detectors and EBSD/EDS detectors if integrated.
486 This function retracts all insertable devices, first retracting microscope detectors and then retracting EBSD/EDS detectors if they are integrated and enabled.
488 ## Parameters
490 - `microscope` (`tbt.Microscope`): The microscope object for accessing the Autoscript API.
491 - `enable_EBSD` (`bool`): Whether to enable retraction of the EBSD detector.
492 - `enable_EDS` (`bool`): Whether to enable retraction of the EDS detector.
494 ## Returns
496 - `bool`: True if all devices are successfully retracted.
498 ## Raises
500 - `None`
501 """
502 print("\tRetracting devices, do not interact with xTUI during this process...")
503 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
504 device_access(microscope)
506 for detector in microscope.detector.type.available_values:
507 detector = tbt.DetectorType(detector) # overwrite
508 state = detector_state(
509 microscope=microscope,
510 detector=detector,
511 )
512 if (
513 state != tbt.RetractableDeviceState.STATIONARY
514 and state != tbt.RetractableDeviceState.RETRACTED
515 ):
516 # if (state is not None) and (state != tbt.RetractableDeviceState.RETRACTED):
517 retract_device(
518 microscope=microscope,
519 detector=detector,
520 )
522 # EBSD/EDS detectors:
523 if external is None:
524 # try:
525 # external
526 # except NameError:
527 # pass
528 print("\t\tLaser API not imported, EBSD and EDS detectors are unavailable")
529 else:
530 if enable_EBSD:
531 retract_EBSD(microscope=microscope)
532 if enable_EDS:
533 retract_EDS(microscope=microscope)
535 # reset initial settings:
536 img.set_view(
537 microscope=microscope,
538 quad=initial_view,
539 )
540 print("\t\tAll available and enabled devices retracted.")
541 return True
544def connect_EBSD() -> tbt.RetractableDeviceState:
545 """
546 Connect to the EBSD system and retrieve the camera status.
548 This function attempts to connect to the EBSD system and retrieve the camera status. It raises a ConnectionError if the connection fails.
550 ## Returns
552 tbt.RetractableDeviceState
553 The status of the EBSD camera.
555 ## Raises
557 ConnectionError
558 If the EBSD control is not connected.
560 """
561 try:
562 status = external.EBSD_CameraStatus()
563 except:
564 raise ConnectionError(
565 """EBSD control not connected, "Laser Control" from ThermoFisher must be open.
566 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again."""
567 )
568 return tbt.RetractableDeviceState(status)
571def retract_EBSD(microscope: tbt.Microscope) -> bool:
572 """
573 Retract the EBSD camera from the microscope.
575 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.
577 ## Parameters
579 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the EBSD camera.
581 ## Returns
583 - `bool`: True if the EBSD camera is successfully retracted.
585 ## Raises
587 - `SystemError`: If the EBSD camera is in an error state, if the EBSD mapping is not completed, or if the EBSD camera retraction fails.
588 """
589 connect_EBSD()
590 ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
591 if ebsd_status == tbt.RetractableDeviceState.ERROR: 591 ↛ 592line 591 didn't jump to line 592 because the condition on line 591 was never true
592 raise SystemError(
593 "EBSD Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control"
594 )
595 if ebsd_status != tbt.RetractableDeviceState.RETRACTED:
596 print(
597 '\t\t\tEBSD Camera Retraction requested, please wait for "mapping complete" verification...'
598 )
599 map_status = tbt.MapStatus(external.EBSD_MappingStatus())
600 # first check if mapping is finished properly
601 minutes_to_wait = 5 # TODO set constant
602 timeout = minutes_to_wait * 60 # seconds #TODO
603 cameraokconfirmations = 3 # synchronization issue with EDAX, try to get map completed 3x before continuing
604 waittime = 10 # seconds
605 if map_status == tbt.MapStatus.ACTIVE: 605 ↛ 606line 605 didn't jump to line 606 because the condition on line 605 was never true
606 print("\t\t\tEBSD mapping currently active, waiting for mapping to finish")
607 while True:
608 current_map_status = tbt.MapStatus(external.EBSD_MappingStatus())
609 if current_map_status != tbt.MapStatus.ACTIVE: 609 ↛ 612line 609 didn't jump to line 612 because the condition on line 609 was always true
610 cameraokconfirmations = cameraokconfirmations - 1
611 waittime = 3 # shorten wait time, polling 3x to see if mapping was really completed.
612 time.sleep(waittime)
613 timeout = timeout - waittime
614 if cameraokconfirmations < 1:
615 delay = minutes_to_wait * 60 - timeout
616 print(f"\t\t\t\tEBSD mapping finished. Delay of {delay} seconds")
617 break
618 if timeout < 1: 618 ↛ 619line 618 didn't jump to line 619 because the condition on line 618 was never true
619 warnings.warn(
620 "\t\t\tWarning, EBSD mapping timeout. Trying to continue..."
621 )
622 break
623 CCD_view(microscope=microscope)
624 print("\t\t\tEBSD Camera retracting...")
625 external.EBSD_RetractCamera()
626 time.sleep(1)
627 CCD_pause(microscope=microscope)
628 current_ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus())
629 if current_ebsd_status != tbt.RetractableDeviceState.RETRACTED: 629 ↛ 630line 629 didn't jump to line 630 because the condition on line 629 was never true
630 raise SystemError("Error, EBSD Camera retraction failed, workflow stopped.")
631 print("\t\tEBSD Camera retracted")
632 return True
635def connect_EDS() -> tbt.RetractableDeviceState:
636 """
637 Connect to the EDS system and retrieve the camera status.
639 This function attempts to connect to the EDS system and retrieve the camera status. It raises a ConnectionError if the connection fails.
641 ## Returns
643 tbt.RetractableDeviceState
644 The status of the EDS camera.
646 ## Raises
648 ConnectionError
649 If the EDS control is not connected.
651 """
652 try:
653 status = external.EDS_CameraStatus()
654 except:
655 raise ConnectionError(
656 """EDS control not connected, "Laser Control" from ThermoFisher must be open.
657 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again."""
658 )
659 return tbt.RetractableDeviceState(status)
662def retract_EDS(microscope: tbt.Microscope) -> bool:
663 """
664 Retract the EDS detector from the microscope.
666 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.
668 ## Parameters
670 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the EDS camera.
672 ## Returns
674 - `bool`: True if the EDS camera is successfully retracted.
676 ## Raises
678 - `SystemError`: If the EDS camera is in an error state or if the EDS camera retraction fails.
679 """
680 # print(f"\t\tRetracting EDS detector")
681 connect_EDS()
682 eds_status = tbt.RetractableDeviceState(external.EDS_CameraStatus())
683 if eds_status == tbt.RetractableDeviceState.ERROR: 683 ↛ 684line 683 didn't jump to line 684 because the condition on line 683 was never true
684 raise SystemError(
685 "EDS Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control"
686 )
687 if eds_status != tbt.RetractableDeviceState.RETRACTED:
688 print("\t\t\tEDS Camera retracting...")
689 CCD_view(microscope=microscope)
690 external.EDS_RetractCamera()
691 time.sleep(1)
692 if ( 692 ↛ 696line 692 didn't jump to line 696
693 tbt.RetractableDeviceState(external.EDS_CameraStatus())
694 != tbt.RetractableDeviceState.RETRACTED
695 ):
696 raise SystemError("Error, EDS Camera retraction failed, workflow stopped.")
697 CCD_pause(microscope=microscope)
698 print("\t\tEDS Camera retracted")
699 return True
702def retract_device(microscope: tbt.Microscope, detector: tbt.DetectorType) -> bool:
703 """
704 Retract the specified detector from the microscope.
706 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.
708 ## Parameters
710 - `microscope` (`tbt.Microscope`): The microscope object for which to retract the detector.
711 - `detector` (`tbt.DetectorType`): The type of the detector to retract.
713 ## Returns
715 - `bool`: True if the detector is successfully retracted.
717 ## Raises
719 - `SystemError`: If the detector cannot be retracted.
720 """
721 CCD_view(microscope=microscope)
722 print(f"\t\tRetracting {detector.value} detector")
723 microscope.detector.type.value = detector.value
724 microscope.detector.retract()
725 state = tbt.RetractableDeviceState(microscope.detector.state)
726 if state != tbt.RetractableDeviceState.RETRACTED: 726 ↛ 727line 726 didn't jump to line 727 because the condition on line 726 was never true
727 raise SystemError(
728 f"{detector.value} detector not retracted, current detector state is {state.value}"
729 )
730 print(f"\t\t{detector.value} detector retracted")
731 CCD_pause(microscope=microscope)
733 return True
736def CCD_pause(
737 microscope: tbt.Microscope,
738 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT,
739) -> bool:
740 """
741 Pause the CCD camera, typically used after device or stage movement.
743 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.
745 ## Parameters
747 - `microscope` (`tbt.Microscope`): The microscope object for which to pause the CCD camera.
748 - `quad` (`tbt.ViewQuad, optional`): The quadrant to switch to before pausing the CCD camera (default is tbt.ViewQuad.LOWER_RIGHT).
750 ## Returns
752 - `bool`: True if the CCD camera is successfully paused.
754 ## Warnings
756 - `UserWarning`: If the CCD camera is not installed on the microscope.
757 """
758 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
759 img.set_view(microscope=microscope, quad=quad)
760 try:
761 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA)
762 except:
763 warnings.warn("CCD camera is not installed on this microscope.")
764 else:
765 microscope.imaging.stop_acquisition()
766 finally:
767 microscope.imaging.set_active_view(initial_view.value)
769 return True
772def CCD_view(
773 microscope: tbt.Microscope,
774 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT,
775) -> bool:
776 """
777 Visualize detector or stage movement for the user using the CCD camera.
779 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.
781 ## Parameters
783 - `microscope` (`tbt.Microscope`): The microscope object for which to visualize the movement.
784 - `quad` (`tbt.ViewQuad, optional`): The quadrant to switch to before visualizing the movement (default is tbt.ViewQuad.LOWER_RIGHT).
786 ## Returns
788 - `bool`: True if the CCD camera is successfully used for visualization.
790 ## Warnings
792 - `UserWarning`: If the CCD camera is not installed on the microscope.
793 """
794 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view())
795 img.set_view(microscope=microscope, quad=quad)
796 try:
797 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA)
798 except:
799 warnings.warn("CCD camera is not installed on this microscope.")
800 else:
801 microscope.imaging.start_acquisition()
802 finally:
803 microscope.imaging.set_active_view(initial_view.value)
805 return True
808def specimen_current(
809 microscope: tbt.Microscope,
810 hfw_mm=Constants.specimen_current_hfw_mm,
811 delay_s=Constants.specimen_current_delay_s,
812) -> float:
813 """
814 Measure the specimen current using the electron beam and return the value in nA.
816 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.
818 ## Parameters
820 - `microscope` (`tbt.Microscope`): The microscope object for which to measure the specimen current.
821 - `hfw_mm` (`float, optional`): The horizontal field width in millimeters (default is Constants.specimen_current_hfw_mm).
822 - `delay_s` (`float, optional`): The delay in seconds before measuring the specimen current (default is Constants.specimen_current_delay_s).
824 ## Returns
826 - `float`: The measured specimen current in nA.
827 """
828 img.set_beam_device(
829 microscope=microscope,
830 device=tbt.Device.ELECTRON_BEAM,
831 )
832 initial_hfw_m = microscope.beams.electron_beam.horizontal_field_width.value
833 initial_detector = tbt.DetectorType(microscope.detector.type.value)
835 # TODO: A safer structure would be a try/except block
836 # try:
837 # img.detector_type(microscope=microscope, detector=tbt.DetectorType.ETD)
838 # img.beam_hfw(
839 # beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
840 # microscope=microscope,
841 # hfw_mm=hfw_mm,
842 # )
843 # microscope.imaging.start_acquisition()
844 # time.sleep(delay_s)
845 # return microscope.state.specimen_current.value * cs.Conversions.A_TO_NA
846 # finally:
847 # microscope.imaging.stop_acquisition()
848 # microscope.beams.electron_beam.horizontal_field_width.value = initial_hfw_m
849 # img.detector_type(microscope=microscope, detector=initial_detector)
850 # img.beam_hfw(
851 # beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
852 # microscope=microscope,
853 # hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM,
854 # )
856 img.detector_type(microscope=microscope, detector=tbt.DetectorType.ETD)
857 img.beam_hfw(
858 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
859 microscope=microscope,
860 hfw_mm=hfw_mm,
861 )
862 microscope.imaging.start_acquisition()
863 time.sleep(delay_s)
864 current_na = microscope.state.specimen_current.value * cs.Conversions.A_TO_NA
865 microscope.imaging.stop_acquisition()
867 # reset detector and hfw
868 microscope.beams.electron_beam.horizontal_field_width.value = initial_hfw_m
869 img.detector_type(microscope=microscope, detector=initial_detector)
870 img.beam_hfw(
871 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()),
872 microscope=microscope,
873 hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM,
874 )
876 return current_na