Coverage for src/pytribeam/image.py: 81%
354 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-10-14 19:37 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-10-14 19:37 +0000
1#!/usr/bin/python3
2"""Microscope imaging configuration and acquisition utilities.
4This module provides the imaging-control functions used throughout `pytribeam`.
5It includes helpers for selecting microscope views and imaging devices,
6configuring electron/ion beam parameters, setting detector state, configuring
7scan settings, and acquiring image frames.
9Most workflows should use `image_operation` or `prepare_imaging` rather than
10calling low-level beam, detector, and scan setters directly.
12## Typical usage
14```python
15from pytribeam import image
17image.image_operation(
18 step=step,
19 image_settings=image_settings,
20 general_settings=general_settings,
21 slice_number=slice_number,
22)
23```
25For lower-level control:
27```python
28from pytribeam import image
29import pytribeam.types as tbt
31image.set_view(microscope, tbt.ViewQuad.UPPER_LEFT)
32image.set_beam_device(microscope, tbt.Device.ELECTRON_BEAM)
33image.prepare_imaging(image_settings)
34```
36## Main entry points
38- `image_operation`: perform a complete image-acquisition operation for one
39 workflow step and slice.
40- `prepare_imaging`: apply beam, detector, and scan settings before acquisition.
41- `collect_single_image`: acquire and save one image frame.
42- `collect_multiple_images`: acquire multiple image frames.
43- `imaging_device`: select the active beam/device and prepare voltage/current.
44- `imaging_detector`: configure and insert the requested detector when needed.
45- `imaging_scan`: apply scan settings other than resolution.
47## Beam configuration
49Beam helper functions configure common electron- and ion-beam properties,
50including voltage, current, dwell time, horizontal field width, working distance,
51scan rotation, scan resolution, full-frame scan mode, and angular corrections.
53The generic beam helpers operate on `tbt.ElectronBeam` and `tbt.IonBeam`
54settings objects and use shared dispatch/utility behavior to access the
55corresponding microscope beam object.
57## Detector configuration
59Detector helper functions select detector type and mode, set contrast and
60brightness, and optionally run automatic contrast/brightness adjustment.
61Insertable detectors are handled through `pytribeam.insertable_devices`.
63## Image acquisition
65The acquisition helpers support both preset and custom scan resolutions. Image
66files are written according to the paths and formats defined by the active
67`tbt.ImageSettings` and workflow settings.
69## Unit conventions
71User-facing imaging settings use explicit field names to indicate units:
73| Quantity | Units |
74| --- | --- |
75| Beam voltage | kilovolts |
76| Beam current | nanoamperes |
77| Dwell time | microseconds |
78| Horizontal field width | millimeters |
79| Working distance | millimeters |
80| Scan rotation | degrees |
81| Contrast/brightness | normalized range from 0 to 1 |
83Values are converted internally as needed before being passed to the microscope
84API.
86> **Warning**
87>
88> Functions in this module can change microscope beam, detector, scan, and image
89> acquisition state. Confirm that the selected beam, detector, field of view,
90> scan conditions, and save paths are appropriate before acquiring images.
92<hr style="height: 12px; background-color: #333; border: none;">
93"""
95__all__ = [
96 "get_available_detector_types",
97 "get_available_detector_modes",
98 "get_available_insertable_detector_states",
99 "get_available_insertable_detectors",
100 "beam_angular_correction",
101 "beam_current",
102 "beam_dwell_time",
103 "beam_hfw",
104 "beam_ready",
105 "beam_scan_full_frame",
106 "beam_scan_resolution",
107 "beam_scan_rotation",
108 "beam_voltage",
109 "beam_working_distance",
110 "collect_multiple_images",
111 "collect_single_image",
112 "detector_auto_cb",
113 "detector_brightness",
114 "detector_cb",
115 "detector_contrast",
116 "detector_mode",
117 "detector_type",
118 "grab_custom_resolution_frame",
119 "grab_preset_resolution_frame",
120 "image_operation",
121 "imaging_detector",
122 "imaging_device",
123 "imaging_scan",
124 "prepare_imaging",
125 "set_beam_device",
126 "set_view",
127]
129# Default python modules
130# from functools import singledispatch
131from pathlib import Path
132import time
133import warnings
134from typing import List, Optional
135import math
137# Local scripts
138import pytribeam.constants as cs
139import pytribeam.insertable_devices as devices
140import pytribeam.types as tbt
141import pytribeam.utilities as ut
144def get_available_detector_types(
145 microscope: tbt.Microscope, device: Optional[tbt.Device] = None
146) -> List[tbt.DetectorType]:
147 """
148 Get the list of available detector types for a given device
150 ## Parameters
152 - `microscope` (`Microscope`): The microscope object from which to retrieve the application files.
153 - `device` (`Device`): The device to check.
155 ## Returns
157 - `List[DetectorType]`: A sorted list of detector types available on the microscope.
158 """
159 # Get the original device
160 original_device = tbt.Device(microscope.imaging.get_active_device())
162 # Parse device
163 if device is None:
164 device = original_device
166 # Set device
167 set_beam_device(microscope, device)
169 # Get the original detector type and mode for the target device
170 original_detector_type = tbt.DetectorType(microscope.detector.type.value)
171 original_detector_mode = tbt.DetectorMode(microscope.detector.mode.value)
173 # Loop over the modes that autoscript provides and make sure they can actually be set (it lies)
174 available_types = []
175 for dt in microscope.detector.type.available_values:
176 dt = tbt.DetectorType(dt)
177 try:
178 # make requested detector the active detector
179 detector_type(microscope, dt)
180 except:
181 pass
182 else:
183 available_types.append(dt)
185 # Clean up
186 detector_type(microscope, original_detector_type)
187 detector_mode(microscope, original_detector_mode)
188 set_beam_device(microscope, original_device)
190 return available_types
193def get_available_detector_modes(
194 microscope: tbt.Microscope,
195 device: Optional[tbt.Device] = None,
196 detector: Optional[tbt.DetectorType] = None,
197):
198 """
199 Get the list of available detector modes for a given detector type
201 ## Parameters
203 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files.
204 - `device` (`Device`): The device to check.
205 - `detector_type` (`tbt.DetectorType`): The detector type to check modes for. If None, returns the modes for the current device
207 ## Returns
209 - `List[str]`: A sorted list of detector types available on the microscope.
210 """
211 # Get the original device
212 original_device = tbt.Device(microscope.imaging.get_active_device())
214 # Parse device
215 if device is None:
216 device = original_device
218 # Set device
219 set_beam_device(microscope, device)
221 # Get the original detector type and mode for the target device
222 original_detector_type = tbt.DetectorType(microscope.detector.type.value)
223 original_detector_mode = tbt.DetectorMode(microscope.detector.mode.value)
225 # Parse the detector type
226 if detector is None:
227 detector = original_detector_type
229 # Set the detector to the requested value
230 if original_detector_type != detector:
231 try:
232 # make requested detector the active detector
233 detector_type(microscope, detector)
234 except:
235 warnings.warn(
236 f"""Warning. Invalid detector type of "{detector.value}" for currently selected device
237 of "{tbt.Device(microscope.imaging.get_active_device()).value}" or detector not found on this system.
238 No detector modes will be returned."""
239 )
240 # Clean up
241 detector_type(microscope, original_detector_type)
242 detector_mode(microscope, original_detector_mode)
243 set_beam_device(microscope, original_device)
244 return
246 # Get the available values of detector mode for this type
247 available_modes = [
248 tbt.DetectorMode(dm) for dm in microscope.detector.mode.available_values
249 ]
251 # Clean up
252 detector_type(microscope, original_detector_type)
253 detector_mode(microscope, original_detector_mode)
254 set_beam_device(microscope, original_device)
256 return available_modes
259def get_available_insertable_detector_states(
260 microscope: tbt.Microscope,
261) -> List[tbt.DetectorType]:
262 """
263 Get the state of all insertable detectors available on the microscope
265 ## Parameters
267 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files.
269 ## Returns
271 - `List[DetectorType]`: A sorted list of insertable detector types available on the microscope.
272 """
273 # Get the original device
274 original_device = tbt.Device(microscope.imaging.get_active_device())
275 device = tbt.Device.ELECTRON_BEAM
276 set_beam_device(microscope, device)
278 # Get the original detector type and mode for the target device
279 original_detector_type = tbt.DetectorType(microscope.detector.type.value)
280 original_detector_mode = tbt.DetectorMode(microscope.detector.mode.value)
282 insertable_detector_states = []
283 for dt in get_available_detector_types(microscope, device):
284 if devices.detector_insertable(microscope=microscope, detector=dt):
285 state = devices.detector_state(microscope=microscope, detector=dt)
286 insertable_detector_states.append((dt, state))
288 # Clean up
289 detector_type(microscope, original_detector_type)
290 detector_mode(microscope, original_detector_mode)
291 set_beam_device(microscope, original_device)
293 return insertable_detector_states
296def get_available_insertable_detectors(
297 microscope: tbt.Microscope,
298) -> List[tbt.DetectorType]:
299 """
300 Get the list of available detector types that are insertable
302 ## Parameters
304 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files.
306 ## Returns
308 - `List[DetectorType]`: A sorted list of insertable detector types available on the microscope.
309 """
310 # Get the original device
311 original_device = tbt.Device(microscope.imaging.get_active_device())
312 device = tbt.Device.ELECTRON_BEAM
313 set_beam_device(microscope, device)
315 # Get the original detector type and mode for the target device
316 original_detector_type = tbt.DetectorType(microscope.detector.type.value)
317 original_detector_mode = tbt.DetectorMode(microscope.detector.mode.value)
319 insertable_detectors = []
320 for dt in get_available_detector_types(microscope, device):
321 if devices.detector_insertable(microscope=microscope, detector=dt):
322 insertable_detectors.append(dt)
324 # Clean up
325 detector_type(microscope, original_detector_type)
326 detector_mode(microscope, original_detector_mode)
327 set_beam_device(microscope, original_device)
329 return insertable_detectors
332######################
335def beam_angular_correction(
336 microscope: tbt.Microscope,
337 dynamic_focus: bool,
338 tilt_correction: bool,
339 # correction_angle_deg: float = None,
340 delay_s: float = 0.5,
341) -> bool:
342 """
343 Uses auto mode to set tilt correction and dynamic focus.
345 This function configures the electron beam's angular correction mode to automatic,
346 sets the scan rotation to zero, and enables or disables dynamic focus and tilt correction
347 based on the provided parameters.
349 ## Parameters
351 - `microscope` (`tbt.Microscope`): The microscope object to configure.
352 - `dynamic_focus` (`bool`): If True, dynamic focus will be turned on. If False, dynamic focus will be turned off.
353 - `tilt_correction` (`bool`): If True, tilt correction will be turned on. If False, tilt correction will be turned off.
354 - `delay_s` (`float, optional`): The delay in seconds to wait after turning dynamic focus or tilt correction on or off (default is 0.5).
356 ## Returns
358 - `bool`: True if the configuration is successful, False otherwise.
360 ## Raises
362 - `SystemError`: If unable to turn dynamic focus or tilt correction on or off.
364 ## Examples
366 >>> import pytribeam.types as tbt
367 >>> microscope = tbt.Microscope()
368 >>> microscope.connect("localhost")
369 Client connecting to [localhost:7520]...
370 Client connected to [localhost:7520]
371 >>> success = beam_angular_correction(microscope, dynamic_focus=True, tilt_correction=False)
372 >>> print(success)
373 True"""
374 angular_correction = microscope.beams.electron_beam.angular_correction
375 angular_correction.mode = tbt.AngularCorrectionMode.AUTOMATIC
376 # scan rotation must be zero for auto mode
377 microscope.beams.electron_beam.scanning.rotation.value = 0.0
379 # manual adjustment not implemented yet, only works at zero scan rotation
380 # if correction_angle_deg is not None:
381 # angular_correction.mode = tbt.AngularCorrectionMode.MANUAL
382 if dynamic_focus:
383 angular_correction.dynamic_focus.turn_on()
384 time.sleep(delay_s)
385 if not angular_correction.dynamic_focus.is_on: 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true
386 raise SystemError("Unable to turn dynamic focus on.")
387 if (not dynamic_focus) or (dynamic_focus is None):
388 angular_correction.dynamic_focus.turn_off()
389 time.sleep(delay_s)
390 if angular_correction.dynamic_focus.is_on: 390 ↛ 391line 390 didn't jump to line 391 because the condition on line 390 was never true
391 raise SystemError("Unable to turn dynamic focus off.")
393 if tilt_correction: 393 ↛ 394line 393 didn't jump to line 394 because the condition on line 393 was never true
394 angular_correction.tilt_correction.turn_on()
395 time.sleep(delay_s)
396 if not angular_correction.tilt_correction.is_on:
397 raise SystemError("Unable to turn tilt correction on.")
398 if (not tilt_correction) or (tilt_correction is None): 398 ↛ exitline 398 didn't return from function 'beam_angular_correction' because the condition on line 398 was always true
399 angular_correction.tilt_correction.turn_off()
400 time.sleep(delay_s)
401 if angular_correction.tilt_correction.is_on: 401 ↛ 402line 401 didn't jump to line 402 because the condition on line 401 was never true
402 raise SystemError("Unable to turn tilt correction off.")
405def beam_current(
406 beam: tbt.Beam,
407 microscope: tbt.Microscope,
408 current_na: float,
409 current_tol_na: float,
410 delay_s: float = 5.0,
411) -> bool:
412 """
413 Sets the current for the selected beam type, with inputs in units of nanoamps.
415 This function sets the beam current for the specified beam type on the microscope.
416 If the current difference exceeds the tolerance, it adjusts the beam current and
417 waits for the specified delay.
419 ## Parameters
421 - `beam` (`tbt.Beam`): The beam type to configure.
422 - `microscope` (`tbt.Microscope`): The microscope object to configure.
423 - `current_na` (`float`): The desired beam current in nanoamps.
424 - `current_tol_na` (`float`): The tolerance for the beam current in nanoamps.
425 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the beam current (default is 5.0).
427 ## Returns
429 - `bool`: True if the beam current is set successfully, False otherwise.
431 ## Raises
433 - `ValueError`: If the beam current cannot be adjusted within the specified tolerance.
435 ## Examples
437 >>> import pytribeam.types as tbt
438 >>> microscope = tbt.Microscope()
439 >>> microscope.connect("localhost")
440 Client connecting to [localhost:7520]...
441 Client connected to [localhost:7520]
442 >>> beam = tbt.ElectronBeam(settings=None)
443 >>> success = beam_current(beam, microscope, current_na=1.0, current_tol_na=0.1)
444 >>> print(success)
445 True"""
446 selected_beam = ut.beam_type(beam, microscope)
448 exisiting_current_a = selected_beam.beam_current.value # amps
449 delta_current_na = abs(
450 exisiting_current_a * cs.Conversions.A_TO_NA - current_na
451 ) # nanoamps
452 if delta_current_na > current_tol_na:
453 warnings.warn(
454 "Requested beam current is not the current setting, imaging conditions may be non-ideal."
455 )
456 print("Adjusting beam current...")
457 selected_beam.beam_current.value = current_na * cs.Conversions.NA_TO_A
458 time.sleep(delay_s)
460 new_current_na = selected_beam.beam_current.value * cs.Conversions.A_TO_NA
461 current_diff_na = abs(new_current_na - current_na)
462 if current_diff_na > current_tol_na: 462 ↛ 463line 462 didn't jump to line 463 because the condition on line 462 was never true
463 raise ValueError(
464 f"""Could not correctly adjust beam voltage,
465 requested {current_na} nA, current beam current is
466 {new_current_na} nA"""
467 )
469 return True
472def beam_dwell_time(
473 beam: tbt.Beam,
474 microscope: tbt.Microscope,
475 dwell_us: float,
476 delay_s: float = 0.1,
477) -> bool:
478 """
479 Sets the dwell time for the selected beam, with inputs in units of microseconds.
481 This function sets the dwell time for the specified beam type on the microscope.
482 It converts the dwell time from microseconds to seconds, sets the dwell time, and
483 waits for the specified delay.
485 ## Parameters
487 - `beam` (`tbt.Beam`): The beam type to configure.
488 - `microscope` (`tbt.Microscope`): The microscope object to configure.
489 - `dwell_us` (`float`): The desired dwell time in microseconds.
490 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the dwell time (default is 0.1).
492 ## Returns
494 - `bool`: True if the dwell time is set successfully, False otherwise.
496 ## Raises
498 - `ValueError`: If the dwell time cannot be adjusted correctly.
500 ## Examples
502 >>> import pytribeam.types as tbt
503 >>> microscope = tbt.Microscope()
504 >>> microscope.connect("localhost")
505 Client connecting to [localhost:7520]...
506 Client connected to [localhost:7520]
507 >>> beam = tbt.ElectronBeam(settings=None)
508 >>> success = beam_dwell_time(beam, microscope, dwell_us=10.0)
509 >>> print(success)
510 True"""
511 selected_beam = ut.beam_type(beam, microscope)
512 dwell_s = dwell_us * cs.Conversions.US_TO_S
513 selected_beam.scanning.dwell_time.value = dwell_s
514 time.sleep(delay_s)
515 if not math.isclose( 515 ↛ 520line 515 didn't jump to line 520 because the condition on line 515 was never true
516 selected_beam.scanning.dwell_time.value,
517 dwell_s,
518 rel_tol=cs.Constants.beam_dwell_tol_ratio,
519 ):
520 raise ValueError(
521 f"""Could not correctly adjust dwell time,
522 requested {dwell_s} seconds, current dwell time is
523 {selected_beam.scanning.dwell_time.value} seconds"""
524 )
526 return True
529def beam_hfw(
530 beam: tbt.Beam,
531 microscope: tbt.Microscope,
532 hfw_mm: float,
533 delay_s: float = 0.1,
534 hfw_tol_mm: float = 1e-6, # 1 nm of tolerance
535) -> bool:
536 """
537 Sets the horizontal field width for the selected beam, with inputs in units of millimeters.
539 This function sets the horizontal field width (HFW) for the specified beam type on the microscope.
540 It converts the HFW from millimeters to meters, sets the HFW, and waits for the specified delay.
541 This should be done after adjusting the working distance.
543 ## Parameters
545 - `beam` (`tbt.Beam`): The beam type to configure.
546 - `microscope` (`tbt.Microscope`): The microscope object to configure.
547 - `hfw_mm` (`float`): The desired horizontal field width in millimeters.
548 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the HFW (default is 0.1).
550 ## Returns
552 - `bool`: True if the HFW is set successfully, False otherwise.
554 ## Raises
556 - `ValueError`: If the HFW cannot be adjusted correctly.
558 ## Examples
560 >>> import pytribeam.types as tbt
561 >>> microscope = tbt.Microscope()
562 >>> microscope.connect("localhost")
563 Client connecting to [localhost:7520]...
564 Client connected to [localhost:7520]
565 >>> beam = tbt.ElectronBeam(settings=None)
566 >>> success = beam_hfw(beam, microscope, hfw_mm=1.0)
567 >>> print(success)
568 True"""
569 selected_beam = ut.beam_type(beam, microscope)
570 hfw_m = hfw_mm * cs.Conversions.MM_TO_M
571 selected_beam.horizontal_field_width.value = hfw_m
572 time.sleep(delay_s)
573 if not math.isclose( 573 ↛ 579line 573 didn't jump to line 579 because the condition on line 573 was never true
574 selected_beam.horizontal_field_width.value,
575 hfw_m,
576 abs_tol=hfw_tol_mm
577 * cs.Conversions.MM_TO_M, # convert to meters for autoscript calls
578 ):
579 raise ValueError(
580 f"""Could not correctly adjust horizontal field width,
581 requested {hfw_mm} millimeters, current field with is
582 {selected_beam.horizontal_field_width.value * cs.Conversions.M_TO_MM} millimeters"""
583 )
585 return True
588def beam_ready(
589 beam: tbt.Beam,
590 microscope: tbt.Microscope,
591 delay_s: float = 5.0,
592 attempts: int = 2,
593) -> bool:
594 """
595 Checks if the beam is on or blanked, and tries to turn it on and unblank it if possible.
597 This function checks the vacuum state, ensures the beam is on, and unblanks the beam if it is blanked.
598 It makes multiple attempts to turn on and unblank the beam, waiting for the specified delay between attempts.
600 ## Parameters
602 - `beam` (`tbt.Beam`): The beam type to check.
603 - `microscope` (`tbt.Microscope`): The microscope object to check.
604 - `delay_s` (`float, optional`): The delay in seconds to wait between attempts (default is 5.0).
605 - `attempts` (`int, optional`): The number of attempts to turn on and unblank the beam (default is 2).
607 ## Returns
609 - `bool`: True if the beam is ready (on and unblanked), False otherwise.
611 ## Raises
613 - `ValueError`: If the vacuum is not pumped. If the beam cannot be turned on after the specified number of attempts. If the beam cannot be unblanked after the specified number of attempts.
615 ## Examples
617 >>> import pytribeam.types as tbt
618 >>> microscope = tbt.Microscope()
619 >>> microscope.connect("localhost")
620 Client connecting to [localhost:7520]...
621 Client connected to [localhost:7520]
622 >>> beam = tbt.ElectronBeam(settings=None)
623 >>> success = beam_ready(beam, microscope)
624 >>> print(success)
625 True"""
626 selected_beam = ut.beam_type(beam, microscope)
628 # check vaccum
629 vacuum = microscope.vacuum.chamber_state
630 if vacuum != tbt.VacuumState.PUMPED.value: 630 ↛ 631line 630 didn't jump to line 631 because the condition on line 630 was never true
631 raise ValueError(f'Vacuum is not pumped, current state is "{vacuum}"')
633 # check beam on
634 count: int = 0
635 while count < attempts:
636 beam_on = selected_beam.is_on
637 if not beam_on:
638 selected_beam.turn_on()
639 time.sleep(delay_s)
640 count += 1
641 if not beam_on: 641 ↛ 642line 641 didn't jump to line 642 because the condition on line 641 was never true
642 raise ValueError(
643 f"Unable to turn on {beam.type} beam after {attempts} attempts."
644 )
646 # check beam blank
647 count: int = 0
648 while count < attempts:
649 beam_blank = selected_beam.is_blanked
650 if beam_blank:
651 selected_beam.unblank()
652 time.sleep(delay_s)
653 count += 1
654 if beam_blank: 654 ↛ 655line 654 didn't jump to line 655 because the condition on line 654 was never true
655 raise ValueError(
656 f"Unable to unblank {beam.type} beam after {attempts} attempts."
657 )
659 return True
662def beam_scan_full_frame(
663 beam: tbt.Beam,
664 microscope: tbt.Microscope,
665) -> bool:
666 """
667 Set beam scan mode to full frame.
669 This function sets the scanning mode of the specified beam to full frame on the microscope.
671 ## Parameters
673 - `beam` (`tbt.Beam`): The beam type to configure.
674 - `microscope` (`tbt.Microscope`): The microscope object to configure.
676 ## Returns
678 - `bool`: True if the scan mode is set to full frame successfully, False otherwise.
680 ## Raises
682 - `SystemError`: If unable to set the scan mode to full frame.
684 ## Examples
686 >>> import pytribeam.types as tbt
687 >>> microscope = tbt.Microscope()
688 >>> microscope.connect("localhost")
689 Client connecting to [localhost:7520]...
690 Client connected to [localhost:7520]
691 >>> beam = tbt.ElectronBeam(settings=None)
692 >>> success = beam_scan_full_frame(beam, microscope)
693 >>> print(success)
694 True"""
695 selected_beam = ut.beam_type(beam, microscope)
696 selected_beam.scanning.mode.set_full_frame()
697 scan_mode = selected_beam.scanning.mode.value
698 if not scan_mode == tbt.ScanMode.FULL_FRAME.value: 698 ↛ 699line 698 didn't jump to line 699 because the condition on line 698 was never true
699 raise SystemError(
700 f"Unable to set imaging to full frame. Current scan mode is {scan_mode}."
701 )
702 return True
705def beam_scan_resolution(
706 beam: tbt.Beam,
707 microscope: tbt.Microscope,
708 resolution: tbt.Resolution,
709 delay_s: float = 0.1,
710) -> bool:
711 """
712 Sets the scan resolution for the selected beam, with inputs in units of preset resolutions.
714 This function sets the scan resolution for the specified beam type on the microscope.
715 It only works for preset resolutions and waits for the specified delay after setting the resolution.
717 ## Parameters
719 - `beam` (`tbt.Beam`): The beam type to configure.
720 - `microscope` (`tbt.Microscope`): The microscope object to configure.
721 - `resolution` (`tbt.Resolution`): The desired scan resolution (must be a preset resolution).
722 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the resolution (default is 0.1).
724 ## Returns
726 - `bool`: True if the scan resolution is set successfully, False otherwise.
728 ## Raises
730 - `ValueError`: If a custom resolution is requested or if the resolution cannot be adjusted correctly.
732 ## Examples
734 >>> import pytribeam.types as tbt
735 >>> import pytribeam.utility as ut
736 >>> microscope = tbt.Microscope()
737 >>> microscope.connect("localhost")
738 Client connecting to [localhost:7520]...
739 Client connected to [localhost:7520]
740 >>> beam = tbt.ElectronBeam(settings=None)
741 >>> resolution = tbt.PresetResolution.HIGH
742 >>> success = beam_scan_resolution(beam, microscope, resolution)
743 >>> print(success)
744 True"""
745 if not isinstance(resolution, tbt.PresetResolution):
746 raise ValueError(
747 f"Requested a custom resolution of {resolution.value}. Only preset resolutions allowed."
748 )
750 selected_beam = ut.beam_type(beam, microscope)
751 selected_beam.scanning.resolution.value = resolution.value
752 time.sleep(delay_s)
753 if selected_beam.scanning.resolution.value != resolution.value: 753 ↛ 754line 753 didn't jump to line 754 because the condition on line 753 was never true
754 raise ValueError(
755 f"""Could not correctly adjust scan resolution,
756 requested {resolution}, current resolution is
757 {selected_beam.scanning.resolution.value}"""
758 )
760 return True
763def beam_scan_rotation(
764 beam: tbt.Beam,
765 microscope: tbt.Microscope,
766 rotation_deg: float,
767 delay_s: float = 0.1,
768) -> bool:
769 """
770 Sets the scan rotation for the selected beam, with inputs in units of degrees.
772 This function sets the scan rotation for the specified beam type on the microscope.
773 It converts the rotation from degrees to radians, sets the scan rotation, and waits for the specified delay.
775 ## Parameters
777 - `beam` (`tbt.Beam`): The beam type to configure.
778 - `microscope` (`tbt.Microscope`): The microscope object to configure.
779 - `rotation_deg` (`float`): The desired scan rotation in degrees.
780 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the scan rotation (default is 0.1).
782 ## Returns
784 - `bool`: True if the scan rotation is set successfully, False otherwise.
786 ## Raises
788 - `ValueError`: If the scan rotation cannot be adjusted correctly.
790 ## Examples
792 >>> import pytribeam.types as tbt
793 >>> microscope = tbt.Microscope()
794 >>> microscope.connect("localhost")
795 Client connecting to [localhost:7520]...
796 Client connected to [localhost:7520]
797 >>> beam = tbt.ElectronBeam(settings=None)
798 >>> success = beam_scan_rotation(beam, microscope, rotation_deg=45.0)
799 >>> print(success)
800 True"""
801 selected_beam = ut.beam_type(beam, microscope)
802 rotation_rad = rotation_deg * cs.Conversions.DEG_TO_RAD
803 selected_beam.scanning.rotation.value = rotation_rad
804 time.sleep(delay_s)
805 if selected_beam.scanning.rotation.value != rotation_rad: 805 ↛ 806line 805 didn't jump to line 806 because the condition on line 805 was never true
806 raise ValueError(
807 f"""Could not correctly adjust scan rotation,
808 requested {rotation_rad} radians, current scan rotation is
809 {selected_beam.scanning.rotation.value} radians"""
810 )
812 return True
815def beam_voltage(
816 beam: tbt.Beam,
817 microscope: tbt.Microscope,
818 voltage_kv: float,
819 voltage_tol_kv: float,
820 delay_s: float = 5.0,
821) -> bool:
822 """
823 Sets the voltage for a given beam type, with inputs in units of kilovolts.
825 This function sets the beam voltage for the specified beam type on the microscope.
826 If the voltage difference exceeds the tolerance, it adjusts the beam voltage and waits for the specified delay.
828 ## Parameters
830 - `beam` (`tbt.Beam`): The beam type to configure.
831 - `microscope` (`tbt.Microscope`): The microscope object to configure.
832 - `voltage_kv` (`float`): The desired beam voltage in kilovolts.
833 - `voltage_tol_kv` (`float`): The tolerance for the beam voltage in kilovolts.
834 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the beam voltage (default is 5.0).
836 ## Returns
838 - `bool`: True if the beam voltage is set successfully, False otherwise.
840 ## Raises
842 - `ValueError`: If the beam voltage cannot be adjusted within the specified tolerance. If the beam is not controllable.
844 ## Examples
846 >>> import pytribeam.types as tbt
847 >>> microscope = tbt.Microscope()
848 >>> microscope.connect("localhost")
849 Client connecting to [localhost:7520]...
850 Client connected to [localhost:7520]
851 >>> beam = tbt.ElectronBeam(settings=None)
852 >>> success = beam_voltage(beam, microscope, voltage_kv=15.0, voltage_tol_kv=0.5)
853 >>> print(success)
854 True"""
855 selected_beam = ut.beam_type(beam, microscope)
857 exisiting_voltage_v = selected_beam.high_voltage.value # volts
858 delta_voltage_kv = abs(
859 exisiting_voltage_v * cs.Conversions.V_TO_KV - voltage_kv
860 ) # kilovolts
861 if delta_voltage_kv > voltage_tol_kv:
862 warnings.warn(
863 "Requested beam current is not the current setting, imaging conditions may be non-ideal."
864 )
865 beam_controllable = selected_beam.high_voltage.is_controllable
867 if not beam_controllable: 867 ↛ 868line 867 didn't jump to line 868 because the condition on line 867 was never true
868 raise ValueError(
869 f"Unable to modify beam voltage, beam is not currently controllable."
870 )
871 print("Adjusting beam voltage...")
872 selected_beam.high_voltage.value = voltage_kv * cs.Conversions.KV_TO_V
873 time.sleep(delay_s)
874 new_voltage_kv = selected_beam.high_voltage.value * cs.Conversions.V_TO_KV
875 voltage_diff_kv = abs(new_voltage_kv - voltage_kv)
876 if voltage_diff_kv > voltage_tol_kv: 876 ↛ 877line 876 didn't jump to line 877 because the condition on line 876 was never true
877 raise ValueError(
878 f"""Could not correctly adjust beam voltage,
879 requested {voltage_kv} kV, current beam voltage is
880 {new_voltage_kv} kV"""
881 )
883 return True
886def beam_working_distance(
887 beam: tbt.Beam,
888 microscope: tbt.Microscope,
889 wd_mm: float,
890 delay_s: float = 0.1,
891) -> bool:
892 """
893 Sets the working distance for the selected beam, with inputs in units of millimeters.
895 This function sets the working distance (WD) for the specified beam type on the microscope.
896 It converts the WD from millimeters to meters, sets the WD, and waits for the specified delay.
897 This should be done before adjusting the horizontal field width.
899 ## Parameters
901 - `beam` (`tbt.Beam`): The beam type to configure.
902 - `microscope` (`tbt.Microscope`): The microscope object to configure.
903 - `wd_mm` (`float`): The desired working distance in millimeters.
904 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the working distance (default is 0.1).
906 ## Returns
908 - `bool`: True if the working distance is set successfully, False otherwise.
910 ## Raises
912 - `ValueError`: If the working distance cannot be adjusted correctly.
914 ## Examples
916 >>> import pytribeam.types as tbt
917 >>> microscope = tbt.Microscope()
918 >>> microscope.connect("localhost")
919 Client connecting to [localhost:7520]...
920 Client connected to [localhost:7520]
921 >>> beam = tbt.ElectronBeam(settings=None)
922 >>> success = beam_working_dist(beam, microscope, wd_mm=5.0)
923 >>> print(success)
924 True"""
925 selected_beam = ut.beam_type(beam, microscope)
926 wd_m = wd_mm * cs.Conversions.MM_TO_M
927 selected_beam.working_distance.value = wd_m
928 time.sleep(delay_s)
929 if selected_beam.working_distance.value != wd_m: 929 ↛ 930line 929 didn't jump to line 930 because the condition on line 929 was never true
930 raise ValueError(
931 f"""Could not correctly adjust working distance,
932 requested {wd_m} meters, current working distance is
933 {selected_beam.working_distance.value} meters"""
934 )
936 return True
939def detector_auto_cb(
940 microscope: tbt.Microscope,
941 beam: tbt.Beam,
942 settings: tbt.ScanArea,
943 delay_s: float = 0.1,
944) -> bool:
945 """
946 Detector auto contrast brightness. Currently only reduced scan area option is supported.
948 This function sets the scanning mode to reduced area, runs the auto contrast-brightness function,
949 and then sets the scanning mode back to full frame.
951 ## Parameters
953 - `microscope` (`tbt.Microscope`): The microscope object to configure.
954 - `beam` (`tbt.Beam`): The beam type to configure.
955 - `settings` (`tbt.ScanArea`): The scan area settings for the reduced area.
956 - `delay_s` (`float, optional`): The delay in seconds to wait after each operation (default is 0.1).
958 ## Returns
960 - `bool`: True if the auto contrast-brightness is completed successfully, False otherwise.
962 ## Raises
964 - `SystemError`: If unable to set the scan mode to reduced area or full frame.
966 ## Examples
968 >>> import pytribeam.types as tbt
969 >>> microscope = tbt.Microscope()
970 >>> microscope.connect("localhost")
971 Client connecting to [localhost:7520]...
972 Client connected to [localhost:7520]
973 >>> beam = tbt.ElectronBeam(settings=None)
974 >>> auto_cb_settings = tbt.ScanArea(left=0.1, top=0.1, width=0.8, height=0.8)
975 >>> success = detector_auto_cb(microscope, beam, auto_cb_settings)
976 >>> print(success)
977 True"""
978 selected_beam = ut.beam_type(beam, microscope)
979 selected_beam.scanning.mode.set_reduced_area(
980 left=settings.left,
981 top=settings.top,
982 width=settings.width,
983 height=settings.height,
984 )
985 scan_mode = selected_beam.scanning.mode.value
986 if not scan_mode == tbt.ScanMode.REDUCED_AREA.value: 986 ↛ 987line 986 didn't jump to line 987 because the condition on line 986 was never true
987 raise SystemError(
988 f"Unable to set imaging to reduced area before auto contrast-brightness. Current scan mode is {scan_mode}."
989 )
990 microscope.auto_functions.run_auto_cb()
991 time.sleep(delay_s)
992 selected_beam.scanning.mode.set_full_frame()
993 time.sleep(delay_s)
995 new_scan_mode = selected_beam.scanning.mode.value
996 if new_scan_mode != tbt.ScanMode.FULL_FRAME.value: 996 ↛ 997line 996 didn't jump to line 997 because the condition on line 996 was never true
997 raise SystemError(
998 f"Unable to set imaging back to full frame after auto contrast-brightness. Current scan mode is {new_scan_mode}."
999 )
1000 return True
1003def detector_brightness(
1004 microscope: tbt.Microscope,
1005 brightness: float,
1006 delay_s: float = 0.1,
1007) -> bool:
1008 """
1009 Sets the detector brightness with input from 0 to 1.
1011 This function sets the brightness for the detector on the microscope and waits for the specified delay.
1013 ## Parameters
1015 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1016 - `brightness` (`float`): The desired brightness value (from 0 to 1).
1017 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the brightness (default is 0.1).
1019 ## Returns
1021 - `bool`: True if the brightness is set successfully, False otherwise.
1023 ## Raises
1025 - `ValueError`: If the brightness cannot be adjusted correctly.
1027 ## Examples
1029 >>> import pytribeam.types as tbt
1030 >>> microscope = tbt.Microscope()
1031 >>> microscope.connect("localhost")
1032 Client connecting to [localhost:7520]...
1033 Client connected to [localhost:7520]
1034 >>> success = detector_brightness(microscope, brightness=0.5)
1035 >>> print(success)
1036 True"""
1037 microscope.detector.brightness.value = brightness
1038 current_detector = microscope.detector.type.value
1039 time.sleep(delay_s)
1040 if not math.isclose( 1040 ↛ 1045line 1040 didn't jump to line 1045 because the condition on line 1040 was never true
1041 microscope.detector.brightness.value,
1042 brightness,
1043 abs_tol=cs.Constants.contrast_brightness_tolerance,
1044 ):
1045 raise ValueError(
1046 f"""Could not correctly adjust detector brightness,
1047 requested {brightness} on {current_detector} detector,
1048 current brightness is {microscope.detector.brightness.value}"""
1049 )
1050 return True
1053def detector_contrast(
1054 microscope: tbt.Microscope,
1055 contrast: float,
1056 delay_s: float = 0.1,
1057) -> bool:
1058 """
1059 Sets the detector contrast with input from 0 to 1.
1061 This function sets the contrast for the detector on the microscope and waits for the specified delay.
1063 ## Parameters
1065 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1066 - `contrast` (`float`): The desired contrast value (from 0 to 1).
1067 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the contrast (default is 0.1).
1069 ## Returns
1071 - `bool`: True if the contrast is set successfully, False otherwise.
1073 ## Raises
1075 - `ValueError`: If the contrast cannot be adjusted correctly.
1077 ## Examples
1079 >>> import pytribeam.types as tbt
1080 >>> microscope = tbt.Microscope()
1081 >>> microscope.connect("localhost")
1082 Client connecting to [localhost:7520]...
1083 Client connected to [localhost:7520]
1084 >>> success = detector_contrast(microscope, contrast=0.5)
1085 >>> print(success)
1086 True"""
1087 microscope.detector.contrast.value = contrast
1088 current_detector = microscope.detector.type.value
1089 time.sleep(delay_s)
1090 if not math.isclose( 1090 ↛ 1095line 1090 didn't jump to line 1095 because the condition on line 1090 was never true
1091 microscope.detector.contrast.value,
1092 contrast,
1093 abs_tol=cs.Constants.contrast_brightness_tolerance,
1094 ):
1095 raise ValueError(
1096 f"""Could not correctly adjust detector contrast,
1097 requested {contrast} on {current_detector} detector,
1098 current contrast is {microscope.detector.contrast.value}"""
1099 )
1100 return True
1103def detector_cb(
1104 microscope: tbt.Microscope,
1105 detector_settings: tbt.Detector,
1106 beam: tbt.Beam,
1107) -> bool:
1108 """
1109 Sets detector contrast and brightness.
1111 This function sets the contrast and brightness for the detector on the microscope.
1112 It also runs the auto contrast-brightness function if specified in the detector settings.
1113 Supports initial settings of contrast and brightness with fixed values before auto adjustment.
1115 ## Parameters
1117 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1118 - `detector_settings` (`tbt.Detector`): The detector settings, including contrast, brightness, and auto contrast-brightness settings.
1119 - `beam` (`tbt.Beam`): The beam type to configure.
1121 ## Returns
1123 - `bool`: True if the contrast and brightness are set successfully, False otherwise.
1125 ## Examples
1127 >>> import pytribeam.types as tbt
1128 >>> microscope = tbt.Microscope()
1129 >>> microscope.connect("localhost")
1130 Client connecting to [localhost:7520]...
1131 Client connected to [localhost:7520]
1132 >>> beam = tbt.ElectronBeam(settings=None)
1133 >>> detector_settings = tbt.Detector(contrast=None, brightness=None, auto_cb_settings=tbt.ScanArea(left=0.1, top=0.1, width=0.8, height=0.8))
1134 >>> success = detector_cb(microscope, detector_settings, beam)
1135 >>> print(success)
1136 True
1138 >>> detector_settings = tbt.Detector(contrast=0.2, brightness=0.3, auto_cb_settings=None)
1139 >>> success = detector_cb(microscope, detector_settings, beam)
1140 >>> print(success)
1141 True
1142 >>> beam_brightness = microscope.detector.brightness.value
1143 >>> print(beam_brightness)
1144 0.3
1145 >>> beam_contrast = microscope.detector.contrast.value
1146 >>> print(beam_contrast)
1147 0.2"""
1148 ### cannot ensure detector is the active one, will overwrite mode settings, so following line is not used
1149 # microscope.detector.type.value = detector_settings.type.value
1150 contrast = detector_settings.contrast
1151 brightness = detector_settings.brightness
1152 if contrast is not None:
1153 detector_contrast(microscope=microscope, contrast=contrast)
1154 if brightness is not None:
1155 detector_brightness(microscope=microscope, brightness=brightness)
1157 null_scan = tbt.ScanArea(left=None, top=None, width=None, height=None)
1158 if not null_scan == detector_settings.auto_cb_settings:
1159 detector_auto_cb(
1160 microscope=microscope,
1161 settings=detector_settings.auto_cb_settings,
1162 beam=beam,
1163 )
1164 return True
1167def detector_mode(
1168 microscope: tbt.Microscope,
1169 detector_mode: tbt.DetectorMode,
1170 delay_s: float = 0.1,
1171) -> bool:
1172 """
1173 Sets the detector mode.
1175 This function sets the mode for the detector on the microscope and waits for the specified delay.
1177 ## Parameters
1179 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1180 - `detector_mode` (`tbt.DetectorMode`): The desired detector mode.
1181 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the detector mode (default is 0.1).
1183 ## Returns
1185 - `bool`: True if the detector mode is set successfully, False otherwise.
1187 ## Raises
1189 - `ValueError`: If the detector mode cannot be set correctly.
1191 ## Examples
1193 >>> import pytribeam.types as tbt
1194 >>> microscope = tbt.Microscope()
1195 >>> microscope.connect("localhost")
1196 Client connecting to [localhost:7520]...
1197 Client connected to [localhost:7520]
1198 >>> mode = tbt.DetectorMode.SECONDARY_ELECTRONS
1199 >>> success = detector_mode(microscope, mode)
1200 >>> print(success)
1201 True"""
1202 microscope.detector.mode.value = detector_mode.value
1203 time.sleep(delay_s)
1204 if microscope.detector.mode.value != detector_mode.value: 1204 ↛ 1205line 1204 didn't jump to line 1205 because the condition on line 1204 was never true
1205 raise ValueError(
1206 f"""Could not correctly set detector mode,
1207 requested {detector_mode}, current mode is
1208 {microscope.detector.mode.value}"""
1209 )
1210 return True
1213def detector_type(
1214 microscope: tbt.Microscope,
1215 detector: tbt.DetectorType,
1216 delay_s: float = 0.1,
1217) -> bool:
1218 """
1219 Sets the detector type.
1221 This function sets the type for the detector on the microscope and waits for the specified delay.
1223 ## Parameters
1225 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1226 - `detector` (`tbt.DetectorType`): The desired detector type.
1227 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the detector type (default is 0.1).
1229 ## Returns
1231 - `bool`: True if the detector type is set successfully, False otherwise.
1233 ## Raises
1235 - `ValueError`: If the detector type cannot be set correctly.
1237 ## Examples
1239 >>> import pytribeam.types as tbt
1240 >>> microscope = tbt.Microscope()
1241 >>> microscope.connect("localhost")
1242 Client connecting to [localhost:7520]...
1243 Client connected to [localhost:7520]
1244 >>> detector = tbt.DetectorType.ETD
1245 >>> success = detector_type(microscope, detector)
1246 >>> print(success)
1247 True"""
1248 microscope.detector.type.value = detector.value
1249 time.sleep(delay_s)
1250 if microscope.detector.type.value != detector.value: 1250 ↛ 1251line 1250 didn't jump to line 1251 because the condition on line 1250 was never true
1251 raise ValueError(
1252 f"""Could not correctly set detector type,
1253 requested {detector}, current detector is
1254 {microscope.detector.type.value}"""
1255 )
1256 return True
1259def grab_custom_resolution_frame(
1260 img_settings: tbt.ImageSettings,
1261 save_path: Path,
1262) -> bool:
1263 """
1264 Method for single frame imaging used with custom resolutions.
1266 This function captures a single frame image using custom resolutions and saves it to the specified path.
1268 ## Parameters
1270 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope and scan resolution.
1271 - `save_path` (`Path`): The path to save the captured image.
1273 ## Returns
1275 - `bool`: True if the image is captured and saved successfully, False otherwise.
1277 ## Examples
1279 >>> import pytribeam.types as tbt
1280 >>> from pathlib import Path
1281 >>> microscope = tbt.Microscope()
1282 >>> microscope.connect("localhost")
1283 Client connecting to [localhost:7520]...
1284 Client connected to [localhost:7520]
1285 >>> img_settings = tbt.ImageSettings(
1286 ... microscope=microscope,
1287 ... beam=tbt.ElectronBeam(settings=None),
1288 ... detector=tbt.Detector(),
1289 ... scan=tbt.Scan(resolution=tbt.Resolution(width=1024, height=768)),
1290 ... bit_depth=tbt.ColorDepth.BITS_8,
1291 ... )
1292 >>> save_path = Path("/path/to/save/image.tif")
1293 >>> success = grab_custom_resolution_frame(img_settings, save_path)
1294 >>> print(success)
1295 True"""
1296 microscope = img_settings.microscope
1297 resolution = img_settings.scan.resolution
1298 microscope.imaging.grab_frame_to_disk(
1299 save_path.absolute().as_posix(),
1300 file_format=tbt.ImageFileFormat.TIFF.value,
1301 settings=tbt.GrabFrameSettings(
1302 resolution=f"{resolution.width}x{resolution.height}"
1303 ),
1304 )
1305 return True
1308def grab_preset_resolution_frame(
1309 img_settings: tbt.ImageSettings,
1310) -> tbt.AdornedImage:
1311 """
1312 Method for single frame imaging used with preset resolutions.
1314 This function captures a single frame image using preset resolutions.
1316 ## Parameters
1318 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, detector, and scan resolution.
1320 ## Returns
1322 - `tbt.AdornedImage`: The captured image.
1324 ## Examples
1326 >>> import pytribeam.types as tbt
1327 >>> microscope = tbt.Microscope()
1328 >>> microscope.connect("localhost")
1329 Client connecting to [localhost:7520]...
1330 Client connected to [localhost:7520]
1331 >>> img_settings = tbt.ImageSettings(
1332 ... microscope=microscope,
1333 ... beam=tbt.ElectronBeam(settings=None),
1334 ... detector=tbt.Detector(),
1335 ... scan=tbt.Scan(resolution=tbt.PresetResolution.PRESET_768X512),
1336 ... bit_depth=tbt.ColorDepth.BITS_8,
1337 ... )
1338 >>> image = grab_preset_resolution_frame(img_settings)
1339 >>> print(image)
1340 AdornedImage(width=768, height=512, bit_depth=8)"""
1341 beam = img_settings.beam
1342 microscope = img_settings.microscope
1343 beam_scan_resolution(
1344 beam=beam,
1345 microscope=microscope,
1346 resolution=img_settings.scan.resolution,
1347 )
1348 return microscope.imaging.grab_frame(
1349 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth)
1350 )
1353def imaging_detector(img_settings: tbt.ImageSettings) -> bool:
1354 """
1355 Prepares the detector and inserts it if applicable.
1357 This function sets the detector type, inserts the detector if necessary, sets the detector mode,
1358 and adjusts the contrast and brightness settings. It is important to set detector mode settings
1359 right before contrast and brightness as any subsequent calls to a detector type can overwrite the mode.
1361 ## Parameters
1363 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, and detector settings.
1365 ## Returns
1367 - `bool`: True if the detector is prepared successfully, False otherwise.
1369 ## Examples
1371 >>> import pytribeam.types as tbt
1372 >>> microscope = tbt.Microscope()
1373 >>> microscope.connect("localhost")
1374 Client connecting to [localhost:7520]...
1375 Client connected to [localhost:7520]
1376 >>> img_settings = tbt.ImageSettings(
1377 ... microscope=microscope,
1378 ... beam=tbt.ElectronBeam(settings=None),
1379 ... detector=tbt.Detector(
1380 ... type=tbt.DetectorType.ETD,
1381 ... mode=tbt.DetectorMode.SECONDARY_ELECTRONS,
1382 ... brightness=0.4,
1383 ... contrast=0.2,
1384 ... ),
1385 ... scan=tbt.Scan(resolution=tbt.PresetResolution.PRESET_768X512),
1386 ... bit_depth=tbt.ColorDepth.BITS_8,
1387 ... )
1388 >>> success = imaging_detector(img_settings)
1389 >>> print(success)
1390 True"""
1391 microscope = img_settings.microscope
1392 detector = img_settings.detector.type
1393 detector_type(
1394 microscope=microscope,
1395 detector=detector,
1396 )
1397 detector_state = devices.detector_state(
1398 microscope=microscope,
1399 detector=detector,
1400 )
1401 if detector_state is not tbt.RetractableDeviceState.STATIONARY:
1402 devices.insert_detector(
1403 microscope=microscope,
1404 detector=detector,
1405 )
1406 detector_mode(
1407 microscope=microscope,
1408 detector_mode=img_settings.detector.mode,
1409 )
1410 detector_cb(
1411 microscope=microscope,
1412 detector_settings=img_settings.detector,
1413 beam=img_settings.beam,
1414 )
1415 return True
1418def imaging_device(
1419 microscope: tbt.Microscope,
1420 beam: tbt.Beam,
1421) -> bool:
1422 """
1423 Prepares the imaging beam, viewing quad, and the beam voltage and current.
1425 This function sets the beam device, ensures the beam is ready, sets the beam voltage and current,
1426 and applies angular correction if the beam type is electron.
1428 ## Parameters
1430 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1431 - `beam` (`tbt.Beam`): The beam type to configure, including its settings.
1433 ## Returns
1435 - `bool`: True if the imaging device is prepared successfully, False otherwise."""
1436 set_beam_device(microscope=microscope, device=beam.device)
1437 beam_ready(beam=beam, microscope=microscope)
1438 beam_voltage(
1439 beam=beam,
1440 microscope=microscope,
1441 voltage_kv=beam.settings.voltage_kv,
1442 voltage_tol_kv=beam.settings.voltage_tol_kv,
1443 )
1444 beam_current(
1445 beam=beam,
1446 microscope=microscope,
1447 current_na=beam.settings.current_na,
1448 current_tol_na=beam.settings.current_tol_na,
1449 )
1450 if beam.type == tbt.BeamType.ELECTRON: 1450 ↛ 1456line 1450 didn't jump to line 1456 because the condition on line 1450 was always true
1451 beam_angular_correction(
1452 microscope=microscope,
1453 dynamic_focus=beam.settings.dynamic_focus,
1454 tilt_correction=beam.settings.tilt_correction,
1455 )
1456 return True
1459def imaging_scan(img_settings: tbt.ImageSettings) -> bool:
1460 """
1461 Sets all scan settings except for the resolution.
1463 This function configures the scan settings for the specified image settings, including
1464 setting the scan mode to full frame, scan rotation, working distance, horizontal field width,
1465 and dwell time.
1467 ## Parameters
1469 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, and scan settings.
1471 ## Returns
1473 - `bool`: True if the scan settings are configured successfully, False otherwise."""
1474 microscope = img_settings.microscope
1475 beam = img_settings.beam
1476 beam_scan_full_frame(
1477 beam=beam,
1478 microscope=microscope,
1479 )
1480 beam_scan_rotation(
1481 beam=beam, microscope=microscope, rotation_deg=img_settings.scan.rotation_deg
1482 )
1483 beam_working_distance(
1484 beam=beam,
1485 microscope=microscope,
1486 wd_mm=img_settings.beam.settings.working_dist_mm,
1487 )
1488 beam_hfw(beam=beam, microscope=microscope, hfw_mm=img_settings.beam.settings.hfw_mm)
1489 beam_dwell_time(
1490 beam=beam, microscope=microscope, dwell_us=img_settings.scan.dwell_time_us
1491 )
1492 return True
1495def prepare_imaging(img_settings: tbt.ImageSettings) -> bool:
1496 """
1497 Prepares various imaging settings.
1499 This function prepares the imaging device, scan settings, and detector settings
1500 based on the specified image settings.
1502 ## Parameters
1504 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1506 ## Returns
1508 - `bool`: True if the imaging settings are prepared successfully, False otherwise."""
1509 imaging_device(microscope=img_settings.microscope, beam=img_settings.beam)
1510 imaging_scan(img_settings=img_settings)
1511 imaging_detector(img_settings=img_settings)
1512 return True
1515def set_view(
1516 microscope: tbt.Microscope,
1517 quad: tbt.ViewQuad,
1518) -> bool:
1519 """
1520 Sets the active view to the specified quad.
1522 This function sets the active imaging view to the specified quad on the microscope.
1524 ## Parameters
1526 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1527 - `quad` (`tbt.ViewQuad`): The imaging view to select: - 1 is upper left - 2 is upper right - 3 is lower left - 4 is lower right
1529 ## Returns
1531 - `bool`: True if the active view is set successfully, False otherwise."""
1532 microscope.imaging.set_active_view(quad.value)
1535def set_beam_device(
1536 microscope: tbt.Microscope,
1537 device: tbt.Device,
1538 delay_s: float = 0.1,
1539) -> bool:
1540 """
1541 Sets the active imaging device.
1543 This function sets the active imaging device on the microscope and waits for the specified delay.
1545 ## Parameters
1547 - `microscope` (`tbt.Microscope`): The microscope object to configure.
1548 - `device` (`tbt.Device`): The desired imaging device.
1549 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the device (default is 0.1).
1551 ## Returns
1553 - `bool`: True if the active device is set successfully, False otherwise.
1555 ## Raises
1557 - `ValueError`: If the active device cannot be set correctly."""
1558 microscope.imaging.set_active_device(device)
1559 time.sleep(delay_s)
1560 curr_device = tbt.Device(microscope.imaging.get_active_device())
1561 if curr_device != device.value: 1561 ↛ 1562line 1561 didn't jump to line 1562 because the condition on line 1561 was never true
1562 raise ValueError(
1563 f"""Could not set active device,
1564 requested device {device.value} but current device is {curr_device}.
1565 Device list:
1566 {tbt.Device.ELECTRON_BEAM.value}: Electron Beam
1567 {tbt.Device.ION_BEAM.value}: Ion Beam
1568 {tbt.Device.CCD_CAMERA.value}: CCD Camera
1569 {tbt.Device.IR_CAMERA.value}: IR Camera
1570 {tbt.Device.NAV_CAM.value}: Nav Cam"""
1571 )
1572 return True
1575#################
1578def collect_single_image(
1579 save_path: Path,
1580 img_settings: tbt.ImageSettings,
1581) -> bool:
1582 """
1583 Collects a single frame image with defined image settings.
1585 This function prepares the imaging settings, sets the view, and captures a single frame image.
1586 It saves the image to the specified path. If a non-preset resolution is requested, the image
1587 will be saved at 8-bit color depth.
1589 ## Parameters
1591 - `save_path` (`Path`): The path to save the captured image.
1592 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1594 ## Returns
1596 - `bool`: True if the image is captured and saved successfully, False otherwise."""
1597 beam = img_settings.beam
1598 microscope = img_settings.microscope
1599 set_view(microscope=microscope, quad=beam.default_view)
1600 prepare_imaging(img_settings=img_settings)
1602 resolution = img_settings.scan.resolution
1603 if isinstance(resolution, tbt.PresetResolution):
1604 img = grab_preset_resolution_frame(img_settings=img_settings)
1605 img.save(str(save_path))
1606 return True
1607 warnings.warn(
1608 f'Warning, non-preset resolution of "{img_settings.scan.resolution}" requested. Image will automatically be saved at 8-bit color depth'
1609 )
1610 if img_settings.bit_depth != tbt.ColorDepth.BITS_8: 1610 ↛ 1611line 1610 didn't jump to line 1611 because the condition on line 1610 was never true
1611 warnings.warn(
1612 f"Warning, non-preset resolution necessitates images be stored at 8-bit color depth, requested bit-depth of {img_settings.bit_depth.value} will be ignored."
1613 )
1614 grab_custom_resolution_frame(
1615 img_settings=img_settings,
1616 save_path=save_path,
1617 )
1618 return True
1621def collect_multiple_images(
1622 multiple_img_settings: List[tbt.ImageSettings], num_frames: int
1623) -> List[tbt.AdornedImage]:
1624 """
1625 Sets up scanning for multiple frames.
1627 This function is best used for collecting multiple segments on a single detector simultaneously.
1628 It is limited to preset resolutions only.
1630 ## Parameters
1632 - `multiple_img_settings` (`List[tbt.ImageSettings]`): The list of image settings for each frame, including the microscope, beam, scan, and detector settings.
1633 - `num_frames` (`int`): The number of frames to collect.
1635 ## Returns
1637 - `List[tbt.AdornedImage]`: The list of captured images.
1639 ## Raises
1641 - `ValueError`: If a non-preset resolution is requested for simultaneous multiple frame imaging.
1643 ## Notes
1645 This method has not yet been tested."""
1646 # TODO test this
1648 # Ensure that an insertable detector is done first so that inserting the detector doesn't interfere with other detectors
1649 # Will need to make sure that only one insertable detector at a time
1650 insertable_detector = [
1651 devices.detector_state(
1652 microscope=_set.microscope,
1653 detector=_set.detector,
1654 )
1655 is not tbt.RetractableDeviceState.STATIONARY
1656 for _set in multiple_img_settings
1657 ]
1658 if sum(insertable_detector) > 1:
1659 raise NotImplementedError(
1660 "Collecting multiple images with more than one insertable detector is not supported."
1661 )
1662 elif sum(insertable_detector) == 1:
1663 start = multiple_img_settings[insertable_detector.index(True)]
1664 multiple_img_settings = [start] + multiple_img_settings
1666 warnings.warn("Method not yet tested")
1667 views = []
1668 for quad in range(1, num_frames + 1):
1669 img_settings = multiple_img_settings[quad - 1]
1670 resolution = img_settings.scan.resolution
1671 if not isinstance(resolution, tbt.PresetResolution):
1672 raise ValueError(
1673 f'Only preset resolutions allowed for simultaneous multiple frame imaging, but resolution of "{resolution.width}x{resolution.height}" was requested.'
1674 )
1676 microscope = img_settings.microscope
1677 beam = img_settings.beam
1678 views.append(quad)
1679 set_view(microscope=microscope, quad=img_settings.beam.default_view)
1680 prepare_imaging(microscope=microscope, beam=beam, img_settings=img_settings)
1681 frames = microscope.imaging.grab_multiple_frames(
1682 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth, views=views)
1683 )
1684 return frames
1687# TODO
1688# def image_method(dict):
1689# """determine imaging method and return associated function"""
1690# pass
1692# TODO
1693# def collect_tiling_image():
1694# for loop
1695# collect_standard_image
1696# #stage move
1699# TODO add more complex imaging behavior, determine method in this function
1700def image_operation(
1701 step: tbt.Step,
1702 image_settings: tbt.ImageSettings,
1703 general_settings: tbt.GeneralSettings,
1704 slice_number: int,
1705) -> bool:
1706 """
1707 Performs an image operation based on the specified settings.
1709 This function collects an image, saves it to the specified directory, and turns off tilt correction and dynamic focus.
1711 ## Parameters
1713 - `step` (`tbt.Step`): The step information, including the name of the step.
1714 - `image_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings.
1715 - `general_settings` (`tbt.GeneralSettings`): The general settings, including the experimental directory.
1716 - `slice_number` (`int`): The slice number for naming the saved image file.
1718 ## Returns
1720 - `bool`: True if the image operation is performed successfully, False otherwise."""
1721 print("\tCollecting image")
1722 # create folder in same directory as experimental directory
1723 image_directory = Path(general_settings.exp_dir).joinpath(step.name)
1724 image_directory.mkdir(parents=True, exist_ok=True)
1726 # TODO
1727 # determine_image_method()
1728 # collect_multiple_images()
1730 # single image process:
1731 save_path = image_directory.joinpath(f"{slice_number:04}.tif")
1732 collect_single_image(save_path=save_path, img_settings=image_settings)
1733 print(f"\tImage saved to {save_path}")
1735 # turn off tilt correction and dynamic focus
1736 beam_angular_correction(
1737 microscope=image_settings.microscope,
1738 dynamic_focus=False,
1739 tilt_correction=False,
1740 )
1742 return True