Coverage for src/pytribeam/image.py: 19%

349 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2026-10-23 20:42 +0000

1#!/usr/bin/python3 

2"""Microscope imaging configuration and acquisition utilities. 

3 

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. 

8 

9Most workflows should use `image_operation` or `prepare_imaging` rather than 

10calling low-level beam, detector, and scan setters directly. 

11 

12## Typical usage 

13 

14```python 

15from pytribeam import image 

16 

17image.image_operation( 

18 step=step, 

19 image_settings=image_settings, 

20 general_settings=general_settings, 

21 slice_number=slice_number, 

22) 

23``` 

24 

25For lower-level control: 

26 

27```python 

28from pytribeam import image 

29import pytribeam.types as tbt 

30 

31image.set_view(microscope, tbt.ViewQuad.UPPER_LEFT) 

32image.set_beam_device(microscope, tbt.Device.ELECTRON_BEAM) 

33image.prepare_imaging(image_settings) 

34``` 

35 

36## Main entry points 

37 

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. 

46 

47## Beam configuration 

48 

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. 

52 

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. 

56 

57## Detector configuration 

58 

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`. 

62 

63## Image acquisition 

64 

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. 

68 

69## Unit conventions 

70 

71User-facing imaging settings use explicit field names to indicate units: 

72 

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 | 

82 

83Values are converted internally as needed before being passed to the microscope 

84API. 

85 

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. 

91 

92<hr style="height: 12px; background-color: #333; border: none;"> 

93""" 

94 

95__all__ = [ 

96 "RestoreError", 

97 "detector_settings_restored", 

98 "get_available_detector_types", 

99 "get_available_detector_modes", 

100 "get_available_insertable_detector_states", 

101 "get_available_insertable_detectors", 

102 "beam_angular_correction", 

103 "beam_current", 

104 "beam_dwell_time", 

105 "beam_hfw", 

106 "beam_ready", 

107 "beam_scan_full_frame", 

108 "beam_scan_resolution", 

109 "beam_scan_rotation", 

110 "beam_voltage", 

111 "beam_working_distance", 

112 "collect_multiple_images", 

113 "collect_single_image", 

114 "detector_auto_cb", 

115 "detector_brightness", 

116 "detector_cb", 

117 "detector_contrast", 

118 "detector_mode", 

119 "detector_type", 

120 "grab_custom_resolution_frame", 

121 "grab_preset_resolution_frame", 

122 "image_operation", 

123 "imaging_detector", 

124 "imaging_device", 

125 "imaging_scan", 

126 "prepare_imaging", 

127 "set_beam_device", 

128 "set_view", 

129] 

130 

131# Default python modules 

132# from functools import singledispatch 

133import contextlib 

134from pathlib import Path 

135import time 

136import warnings 

137from typing import Iterator, List, Optional 

138import math 

139 

140# Local scripts 

141import pytribeam.constants as cs 

142import pytribeam.insertable_devices as devices 

143import pytribeam.types as tbt 

144import pytribeam.utilities as ut 

145 

146 

147class RestoreError(RuntimeError): 

148 """The device, detector type, or detector mode could not be restored after a 

149 temporary change, so the microscope UI may not be as it was.""" 

150 

151 

152@contextlib.contextmanager 

153def detector_settings_restored( 

154 microscope: tbt.Microscope, 

155 device: Optional[tbt.Device] = None, 

156) -> Iterator[tbt.DetectorType]: 

157 """ 

158 Temporarily switch the active device, and restore the device, detector type, 

159 and detector mode afterwards, however the block exits. 

160 

161 Used by the functions that probe which detectors and modes are available, 

162 which change the active detector while they search. The detector type and 

163 mode are read after switching to *device*, since detector settings belong 

164 to the device, and restored on it before switching back. 

165 

166 ## Parameters 

167 

168 - `microscope` (`tbt.Microscope`): The microscope object to use. 

169 - `device` (`tbt.Device, optional`): The device to switch to while probing. If None, the active device is used. 

170 

171 ## Yields 

172 

173 - `tbt.DetectorType`: The detector type that was active on *device* before the block. 

174 

175 ## Raises 

176 

177 - `RestoreError`: If any setting could not be restored. Every setting is still attempted. If the block itself raised, that error propagates unchanged and the restore failure is reported as a warning, so neither is lost. 

178 """ 

179 original_device = tbt.Device(microscope.imaging.get_active_device()) 

180 if device is None: 180 ↛ 182line 180 didn't jump to line 182 because the condition on line 180 was always true

181 device = original_device 

182 set_beam_device(microscope, device) 

183 original_detector_type = tbt.DetectorType(microscope.detector.type.value) 

184 original_detector_mode = tbt.DetectorMode(microscope.detector.mode.value) 

185 block_raised = True 

186 try: 

187 yield original_detector_type 

188 block_raised = False 

189 finally: 

190 steps = ( 190 ↛ exit,   190 ↛ 2012 missed branches: 1) line 190 didn't jump to the function exit, 2) line 190 didn't jump to line 201 because

191 ( 

192 "detector type", 

193 lambda: detector_type(microscope, original_detector_type), 

194 ), 

195 ( 

196 "detector mode", 

197 lambda: detector_mode(microscope, original_detector_mode), 

198 ), 

199 ("device", lambda: set_beam_device(microscope, original_device)), 

200 ) 

201 failed = [] 

202 for name, step in steps: 

203 try: 

204 step() 

205 except Exception as error: 

206 failed.append(f"{name} ({error})") 

207 if failed: 

208 message = "Could not restore the " + ", ".join(failed) + "." 

209 if block_raised: 

210 warnings.warn(message) # the block's own error is propagating 

211 else: 

212 raise RestoreError(message) 

213 

214 

215def get_available_detector_types( 

216 microscope: tbt.Microscope, device: Optional[tbt.Device] = None 

217) -> List[tbt.DetectorType]: 

218 """ 

219 Get the list of available detector types for a given device 

220 

221 ## Parameters 

222 

223 - `microscope` (`Microscope`): The microscope object from which to retrieve the application files. 

224 - `device` (`Device`): The device to check. 

225 

226 ## Returns 

227 

228 - `List[DetectorType]`: A sorted list of detector types available on the microscope. 

229 

230 The active device, detector type, and detector mode are restored afterwards, 

231 even if probing fails (see `detector_settings_restored`). 

232 

233 ## Raises 

234 

235 - `RestoreError`: If a setting could not be restored afterwards. 

236 """ 

237 with detector_settings_restored(microscope, device): 

238 # Make sure each detector type AutoScript lists can actually be set (it lies) 

239 available_types = [] 

240 for dt in microscope.detector.type.available_values: 

241 dt = tbt.DetectorType(dt) 

242 try: 

243 # make requested detector the active detector 

244 detector_type(microscope, dt) 

245 except: 

246 pass 

247 else: 

248 available_types.append(dt) 

249 return available_types 

250 

251 

252def get_available_detector_modes( 

253 microscope: tbt.Microscope, 

254 device: Optional[tbt.Device] = None, 

255 detector: Optional[tbt.DetectorType] = None, 

256): 

257 """ 

258 Get the list of available detector modes for a given detector type 

259 

260 ## Parameters 

261 

262 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files. 

263 - `device` (`Device`): The device to check. 

264 - `detector_type` (`tbt.DetectorType`): The detector type to check modes for. If None, returns the modes for the current device 

265 

266 ## Returns 

267 

268 - `List[DetectorMode]`: The detector modes available for the detector, or None (with a warning) if the detector cannot be selected on this device. 

269 

270 The active device, detector type, and detector mode are restored afterwards, 

271 even if probing fails (see `detector_settings_restored`). 

272 

273 ## Raises 

274 

275 - `RestoreError`: If a setting could not be restored afterwards. 

276 """ 

277 with detector_settings_restored(microscope, device) as original_detector_type: 

278 if detector is None: 

279 detector = original_detector_type 

280 

281 # Set the detector to the requested value 

282 if original_detector_type != detector: 282 ↛ 295line 282 didn't jump to line 295 because the condition on line 282 was always true

283 try: 

284 # make requested detector the active detector 

285 detector_type(microscope, detector) 

286 except: 

287 warnings.warn( 

288 f"""Warning. Invalid detector type of "{detector.value}" for currently selected device 

289 of "{tbt.Device(microscope.imaging.get_active_device()).value}" or detector not found on this system. 

290 No detector modes will be returned.""" 

291 ) 

292 return None 

293 

294 # Get the available values of detector mode for this type 

295 available_modes = [ 295 ↛ exitline 295 didn't run the list comprehension on line 295

296 tbt.DetectorMode(dm) for dm in microscope.detector.mode.available_values 

297 ] 

298 return available_modes 

299 

300 

301def get_available_insertable_detector_states( 

302 microscope: tbt.Microscope, 

303) -> List[tbt.DetectorType]: 

304 """ 

305 Get the state of all insertable detectors available on the microscope 

306 

307 ## Parameters 

308 

309 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files. 

310 

311 ## Returns 

312 

313 - `List[Tuple[DetectorType, RetractableDeviceState]]`: Each insertable detector and its state. 

314 

315 The active device, detector type, and detector mode are restored afterwards, 

316 even if probing fails (see `detector_settings_restored`). 

317 

318 ## Raises 

319 

320 - `RestoreError`: If a setting could not be restored afterwards. 

321 """ 

322 device = tbt.Device.ELECTRON_BEAM 

323 with detector_settings_restored(microscope, device): 

324 insertable_detector_states = [] 

325 for dt in get_available_detector_types(microscope, device): 325 ↛ 329line 325 didn't jump to line 329 because the loop on line 325 didn't complete

326 if devices.detector_insertable(microscope=microscope, detector=dt): 326 ↛ 325line 326 didn't jump to line 325 because the condition on line 326 was always true

327 state = devices.detector_state(microscope=microscope, detector=dt) 

328 insertable_detector_states.append((dt, state)) 

329 return insertable_detector_states 

330 

331 

332def get_available_insertable_detectors( 

333 microscope: tbt.Microscope, 

334) -> List[tbt.DetectorType]: 

335 """ 

336 Get the list of available detector types that are insertable 

337 

338 ## Parameters 

339 

340 - `microscope` (`tbt.Microscope`): The microscope object from which to retrieve the application files. 

341 

342 ## Returns 

343 

344 - `List[DetectorType]`: A sorted list of insertable detector types available on the microscope. 

345 

346 The active device, detector type, and detector mode are restored afterwards, 

347 even if probing fails (see `detector_settings_restored`). 

348 

349 ## Raises 

350 

351 - `RestoreError`: If a setting could not be restored afterwards. 

352 """ 

353 device = tbt.Device.ELECTRON_BEAM 

354 with detector_settings_restored(microscope, device): 

355 insertable_detectors = [] 

356 for dt in get_available_detector_types(microscope, device): 

357 if devices.detector_insertable(microscope=microscope, detector=dt): 

358 insertable_detectors.append(dt) 

359 return insertable_detectors 

360 

361 

362def beam_angular_correction( 

363 microscope: tbt.Microscope, 

364 dynamic_focus: bool, 

365 tilt_correction: bool, 

366 # correction_angle_deg: float = None, 

367 delay_s: float = 0.5, 

368) -> bool: 

369 """ 

370 Uses auto mode to set tilt correction and dynamic focus. 

371 

372 This function configures the electron beam's angular correction mode to automatic, 

373 sets the scan rotation to zero, and enables or disables dynamic focus and tilt correction 

374 based on the provided parameters. 

375 

376 ## Parameters 

377 

378 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

379 - `dynamic_focus` (`bool`): If True, dynamic focus will be turned on. If False, dynamic focus will be turned off. 

380 - `tilt_correction` (`bool`): If True, tilt correction will be turned on. If False, tilt correction will be turned off. 

381 - `delay_s` (`float, optional`): The delay in seconds to wait after turning dynamic focus or tilt correction on or off (default is 0.5). 

382 

383 ## Returns 

384 

385 - `bool`: True if the configuration is successful, False otherwise. 

386 

387 ## Raises 

388 

389 - `SystemError`: If unable to turn dynamic focus or tilt correction on or off. 

390 

391 ## Examples 

392 

393 >>> import pytribeam.types as tbt 

394 >>> microscope = tbt.Microscope() 

395 >>> microscope.connect("localhost") 

396 Client connecting to [localhost:7520]... 

397 Client connected to [localhost:7520] 

398 >>> success = beam_angular_correction(microscope, dynamic_focus=True, tilt_correction=False) 

399 >>> print(success) 

400 True""" 

401 angular_correction = microscope.beams.electron_beam.angular_correction 

402 angular_correction.mode = tbt.AngularCorrectionMode.AUTOMATIC 

403 # scan rotation must be zero for auto mode 

404 microscope.beams.electron_beam.scanning.rotation.value = 0.0 

405 

406 # manual adjustment not implemented yet, only works at zero scan rotation 

407 # if correction_angle_deg is not None: 

408 # angular_correction.mode = tbt.AngularCorrectionMode.MANUAL 

409 if dynamic_focus: 409 ↛ 414line 409 didn't jump to line 414 because the condition on line 409 was always true

410 angular_correction.dynamic_focus.turn_on() 

411 time.sleep(delay_s) 

412 if not angular_correction.dynamic_focus.is_on: 

413 raise SystemError("Unable to turn dynamic focus on.") 

414 if (not dynamic_focus) or (dynamic_focus is None): 

415 angular_correction.dynamic_focus.turn_off() 

416 time.sleep(delay_s) 

417 if angular_correction.dynamic_focus.is_on: 

418 raise SystemError("Unable to turn dynamic focus off.") 

419 

420 if tilt_correction: 

421 angular_correction.tilt_correction.turn_on() 

422 time.sleep(delay_s) 

423 if not angular_correction.tilt_correction.is_on: 

424 raise SystemError("Unable to turn tilt correction on.") 

425 if (not tilt_correction) or (tilt_correction is None): 

426 angular_correction.tilt_correction.turn_off() 

427 time.sleep(delay_s) 

428 if angular_correction.tilt_correction.is_on: 

429 raise SystemError("Unable to turn tilt correction off.") 

430 

431 

432def beam_current( 

433 beam: tbt.Beam, 

434 microscope: tbt.Microscope, 

435 current_na: float, 

436 current_tol_na: float, 

437 delay_s: float = 5.0, 

438) -> bool: 

439 """ 

440 Sets the current for the selected beam type, with inputs in units of nanoamps. 

441 

442 This function sets the beam current for the specified beam type on the microscope. 

443 If the current difference exceeds the tolerance, it adjusts the beam current and 

444 waits for the specified delay. 

445 

446 ## Parameters 

447 

448 - `beam` (`tbt.Beam`): The beam type to configure. 

449 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

450 - `current_na` (`float`): The desired beam current in nanoamps. 

451 - `current_tol_na` (`float`): The tolerance for the beam current in nanoamps. 

452 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the beam current (default is 5.0). 

453 

454 ## Returns 

455 

456 - `bool`: True if the beam current is set successfully, False otherwise. 

457 

458 ## Raises 

459 

460 - `ValueError`: If the beam current cannot be adjusted within the specified tolerance. 

461 

462 ## Examples 

463 

464 >>> import pytribeam.types as tbt 

465 >>> microscope = tbt.Microscope() 

466 >>> microscope.connect("localhost") 

467 Client connecting to [localhost:7520]... 

468 Client connected to [localhost:7520] 

469 >>> beam = tbt.ElectronBeam(settings=None) 

470 >>> success = beam_current(beam, microscope, current_na=1.0, current_tol_na=0.1) 

471 >>> print(success) 

472 True""" 

473 selected_beam = ut.beam_type(beam, microscope) 

474 

475 exisiting_current_a = selected_beam.beam_current.value # amps 

476 delta_current_na = abs( 

477 exisiting_current_a * cs.Conversions.A_TO_NA - current_na 

478 ) # nanoamps 

479 if delta_current_na > current_tol_na: 

480 warnings.warn( 

481 "Requested beam current is not the current setting, imaging conditions may be non-ideal." 

482 ) 

483 print("Adjusting beam current...") 

484 selected_beam.beam_current.value = current_na * cs.Conversions.NA_TO_A 

485 time.sleep(delay_s) 

486 

487 new_current_na = selected_beam.beam_current.value * cs.Conversions.A_TO_NA 

488 current_diff_na = abs(new_current_na - current_na) 

489 if current_diff_na > current_tol_na: 

490 raise ValueError( 

491 f"""Could not correctly adjust beam voltage, 

492 requested {current_na} nA, current beam current is 

493 {new_current_na} nA""" 

494 ) 

495 

496 return True 

497 

498 

499def beam_dwell_time( 

500 beam: tbt.Beam, 

501 microscope: tbt.Microscope, 

502 dwell_us: float, 

503 delay_s: float = 0.1, 

504) -> bool: 

505 """ 

506 Sets the dwell time for the selected beam, with inputs in units of microseconds. 

507 

508 This function sets the dwell time for the specified beam type on the microscope. 

509 It converts the dwell time from microseconds to seconds, sets the dwell time, and 

510 waits for the specified delay. 

511 

512 ## Parameters 

513 

514 - `beam` (`tbt.Beam`): The beam type to configure. 

515 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

516 - `dwell_us` (`float`): The desired dwell time in microseconds. 

517 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the dwell time (default is 0.1). 

518 

519 ## Returns 

520 

521 - `bool`: True if the dwell time is set successfully, False otherwise. 

522 

523 ## Raises 

524 

525 - `ValueError`: If the dwell time cannot be adjusted correctly. 

526 

527 ## Examples 

528 

529 >>> import pytribeam.types as tbt 

530 >>> microscope = tbt.Microscope() 

531 >>> microscope.connect("localhost") 

532 Client connecting to [localhost:7520]... 

533 Client connected to [localhost:7520] 

534 >>> beam = tbt.ElectronBeam(settings=None) 

535 >>> success = beam_dwell_time(beam, microscope, dwell_us=10.0) 

536 >>> print(success) 

537 True""" 

538 selected_beam = ut.beam_type(beam, microscope) 

539 dwell_s = dwell_us * cs.Conversions.US_TO_S 

540 selected_beam.scanning.dwell_time.value = dwell_s 

541 time.sleep(delay_s) 

542 if not math.isclose( 

543 selected_beam.scanning.dwell_time.value, 

544 dwell_s, 

545 rel_tol=cs.Constants.beam_dwell_tol_ratio, 

546 ): 

547 raise ValueError( 

548 f"""Could not correctly adjust dwell time, 

549 requested {dwell_s} seconds, current dwell time is 

550 {selected_beam.scanning.dwell_time.value} seconds""" 

551 ) 

552 

553 return True 

554 

555 

556def beam_hfw( 

557 beam: tbt.Beam, 

558 microscope: tbt.Microscope, 

559 hfw_mm: float, 

560 delay_s: float = 0.1, 

561 hfw_tol_mm: float = 1e-6, # 1 nm of tolerance 

562) -> bool: 

563 """ 

564 Sets the horizontal field width for the selected beam, with inputs in units of millimeters. 

565 

566 This function sets the horizontal field width (HFW) for the specified beam type on the microscope. 

567 It converts the HFW from millimeters to meters, sets the HFW, and waits for the specified delay. 

568 This should be done after adjusting the working distance. 

569 

570 ## Parameters 

571 

572 - `beam` (`tbt.Beam`): The beam type to configure. 

573 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

574 - `hfw_mm` (`float`): The desired horizontal field width in millimeters. 

575 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the HFW (default is 0.1). 

576 

577 ## Returns 

578 

579 - `bool`: True if the HFW is set successfully, False otherwise. 

580 

581 ## Raises 

582 

583 - `ValueError`: If the HFW cannot be adjusted correctly. 

584 

585 ## Examples 

586 

587 >>> import pytribeam.types as tbt 

588 >>> microscope = tbt.Microscope() 

589 >>> microscope.connect("localhost") 

590 Client connecting to [localhost:7520]... 

591 Client connected to [localhost:7520] 

592 >>> beam = tbt.ElectronBeam(settings=None) 

593 >>> success = beam_hfw(beam, microscope, hfw_mm=1.0) 

594 >>> print(success) 

595 True""" 

596 selected_beam = ut.beam_type(beam, microscope) 

597 hfw_m = hfw_mm * cs.Conversions.MM_TO_M 

598 selected_beam.horizontal_field_width.value = hfw_m 

599 time.sleep(delay_s) 

600 if not math.isclose( 

601 selected_beam.horizontal_field_width.value, 

602 hfw_m, 

603 abs_tol=hfw_tol_mm 

604 * cs.Conversions.MM_TO_M, # convert to meters for autoscript calls 

605 ): 

606 raise ValueError( 

607 f"""Could not correctly adjust horizontal field width, 

608 requested {hfw_mm} millimeters, current field with is 

609 {selected_beam.horizontal_field_width.value * cs.Conversions.M_TO_MM} millimeters""" 

610 ) 

611 

612 return True 

613 

614 

615def beam_ready( 

616 beam: tbt.Beam, 

617 microscope: tbt.Microscope, 

618 delay_s: float = 5.0, 

619 attempts: int = 2, 

620) -> bool: 

621 """ 

622 Checks if the beam is on or blanked, and tries to turn it on and unblank it if possible. 

623 

624 This function checks the vacuum state, ensures the beam is on, and unblanks the beam if it is blanked. 

625 It makes multiple attempts to turn on and unblank the beam, waiting for the specified delay between attempts. 

626 

627 ## Parameters 

628 

629 - `beam` (`tbt.Beam`): The beam type to check. 

630 - `microscope` (`tbt.Microscope`): The microscope object to check. 

631 - `delay_s` (`float, optional`): The delay in seconds to wait between attempts (default is 5.0). 

632 - `attempts` (`int, optional`): The number of attempts to turn on and unblank the beam (default is 2). 

633 

634 ## Returns 

635 

636 - `bool`: True if the beam is ready (on and unblanked), False otherwise. 

637 

638 ## Raises 

639 

640 - `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. 

641 

642 ## Examples 

643 

644 >>> import pytribeam.types as tbt 

645 >>> microscope = tbt.Microscope() 

646 >>> microscope.connect("localhost") 

647 Client connecting to [localhost:7520]... 

648 Client connected to [localhost:7520] 

649 >>> beam = tbt.ElectronBeam(settings=None) 

650 >>> success = beam_ready(beam, microscope) 

651 >>> print(success) 

652 True""" 

653 selected_beam = ut.beam_type(beam, microscope) 

654 

655 # check vaccum 

656 vacuum = microscope.vacuum.chamber_state 

657 if vacuum != tbt.VacuumState.PUMPED.value: 

658 raise ValueError(f'Vacuum is not pumped, current state is "{vacuum}"') 

659 

660 # check beam on 

661 count: int = 0 

662 while count < attempts: 662 ↛ 663,   662 ↛ 6682 missed branches: 1) line 662 didn't jump to line 663 because the condition on line 662 was never true, 2) line 662 didn't jump to line 668 because the condition on line 662 was always true

663 beam_on = selected_beam.is_on 

664 if not beam_on: 664 ↛ 667line 664 didn't jump to line 667 because the condition on line 664 was always true

665 selected_beam.turn_on() 

666 time.sleep(delay_s) 

667 count += 1 

668 if not beam_on: 

669 raise ValueError( 

670 f"Unable to turn on {beam.type} beam after {attempts} attempts." 

671 ) 

672 

673 # check beam blank 

674 count: int = 0 

675 while count < attempts: 

676 beam_blank = selected_beam.is_blanked 

677 if beam_blank: 

678 selected_beam.unblank() 

679 time.sleep(delay_s) 

680 count += 1 

681 if beam_blank: 

682 raise ValueError( 

683 f"Unable to unblank {beam.type} beam after {attempts} attempts." 

684 ) 

685 

686 return True 

687 

688 

689def beam_scan_full_frame( 

690 beam: tbt.Beam, 

691 microscope: tbt.Microscope, 

692) -> bool: 

693 """ 

694 Set beam scan mode to full frame. 

695 

696 This function sets the scanning mode of the specified beam to full frame on the microscope. 

697 

698 ## Parameters 

699 

700 - `beam` (`tbt.Beam`): The beam type to configure. 

701 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

702 

703 ## Returns 

704 

705 - `bool`: True if the scan mode is set to full frame successfully, False otherwise. 

706 

707 ## Raises 

708 

709 - `SystemError`: If unable to set the scan mode to full frame. 

710 

711 ## Examples 

712 

713 >>> import pytribeam.types as tbt 

714 >>> microscope = tbt.Microscope() 

715 >>> microscope.connect("localhost") 

716 Client connecting to [localhost:7520]... 

717 Client connected to [localhost:7520] 

718 >>> beam = tbt.ElectronBeam(settings=None) 

719 >>> success = beam_scan_full_frame(beam, microscope) 

720 >>> print(success) 

721 True""" 

722 selected_beam = ut.beam_type(beam, microscope) 

723 selected_beam.scanning.mode.set_full_frame() 

724 scan_mode = selected_beam.scanning.mode.value 

725 if not scan_mode == tbt.ScanMode.FULL_FRAME.value: 

726 raise SystemError( 

727 f"Unable to set imaging to full frame. Current scan mode is {scan_mode}." 

728 ) 

729 return True 

730 

731 

732def beam_scan_resolution( 

733 beam: tbt.Beam, 

734 microscope: tbt.Microscope, 

735 resolution: tbt.Resolution, 

736 delay_s: float = 0.1, 

737) -> bool: 

738 """ 

739 Sets the scan resolution for the selected beam, with inputs in units of preset resolutions. 

740 

741 This function sets the scan resolution for the specified beam type on the microscope. 

742 It only works for preset resolutions and waits for the specified delay after setting the resolution. 

743 

744 ## Parameters 

745 

746 - `beam` (`tbt.Beam`): The beam type to configure. 

747 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

748 - `resolution` (`tbt.Resolution`): The desired scan resolution (must be a preset resolution). 

749 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the resolution (default is 0.1). 

750 

751 ## Returns 

752 

753 - `bool`: True if the scan resolution is set successfully, False otherwise. 

754 

755 ## Raises 

756 

757 - `ValueError`: If a custom resolution is requested or if the resolution cannot be adjusted correctly. 

758 

759 ## Examples 

760 

761 >>> import pytribeam.types as tbt 

762 >>> import pytribeam.utility as ut 

763 >>> microscope = tbt.Microscope() 

764 >>> microscope.connect("localhost") 

765 Client connecting to [localhost:7520]... 

766 Client connected to [localhost:7520] 

767 >>> beam = tbt.ElectronBeam(settings=None) 

768 >>> resolution = tbt.PresetResolution.HIGH 

769 >>> success = beam_scan_resolution(beam, microscope, resolution) 

770 >>> print(success) 

771 True""" 

772 if not isinstance(resolution, tbt.PresetResolution): 

773 raise ValueError( 

774 f"Requested a custom resolution of {resolution.value}. Only preset resolutions allowed." 

775 ) 

776 

777 selected_beam = ut.beam_type(beam, microscope) 

778 selected_beam.scanning.resolution.value = resolution.value 

779 time.sleep(delay_s) 

780 if selected_beam.scanning.resolution.value != resolution.value: 

781 raise ValueError( 

782 f"""Could not correctly adjust scan resolution, 

783 requested {resolution}, current resolution is 

784 {selected_beam.scanning.resolution.value}""" 

785 ) 

786 

787 return True 

788 

789 

790def beam_scan_rotation( 

791 beam: tbt.Beam, 

792 microscope: tbt.Microscope, 

793 rotation_deg: float, 

794 delay_s: float = 0.1, 

795) -> bool: 

796 """ 

797 Sets the scan rotation for the selected beam, with inputs in units of degrees. 

798 

799 This function sets the scan rotation for the specified beam type on the microscope. 

800 It converts the rotation from degrees to radians, sets the scan rotation, and waits for the specified delay. 

801 

802 ## Parameters 

803 

804 - `beam` (`tbt.Beam`): The beam type to configure. 

805 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

806 - `rotation_deg` (`float`): The desired scan rotation in degrees. 

807 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the scan rotation (default is 0.1). 

808 

809 ## Returns 

810 

811 - `bool`: True if the scan rotation is set successfully, False otherwise. 

812 

813 ## Raises 

814 

815 - `ValueError`: If the scan rotation cannot be adjusted correctly. 

816 

817 ## Examples 

818 

819 >>> import pytribeam.types as tbt 

820 >>> microscope = tbt.Microscope() 

821 >>> microscope.connect("localhost") 

822 Client connecting to [localhost:7520]... 

823 Client connected to [localhost:7520] 

824 >>> beam = tbt.ElectronBeam(settings=None) 

825 >>> success = beam_scan_rotation(beam, microscope, rotation_deg=45.0) 

826 >>> print(success) 

827 True""" 

828 selected_beam = ut.beam_type(beam, microscope) 

829 rotation_rad = rotation_deg * cs.Conversions.DEG_TO_RAD 

830 selected_beam.scanning.rotation.value = rotation_rad 

831 time.sleep(delay_s) 

832 if selected_beam.scanning.rotation.value != rotation_rad: 

833 raise ValueError( 

834 f"""Could not correctly adjust scan rotation, 

835 requested {rotation_rad} radians, current scan rotation is 

836 {selected_beam.scanning.rotation.value} radians""" 

837 ) 

838 

839 return True 

840 

841 

842def beam_voltage( 

843 beam: tbt.Beam, 

844 microscope: tbt.Microscope, 

845 voltage_kv: float, 

846 voltage_tol_kv: float, 

847 delay_s: float = 5.0, 

848) -> bool: 

849 """ 

850 Sets the voltage for a given beam type, with inputs in units of kilovolts. 

851 

852 This function sets the beam voltage for the specified beam type on the microscope. 

853 If the voltage difference exceeds the tolerance, it adjusts the beam voltage and waits for the specified delay. 

854 

855 ## Parameters 

856 

857 - `beam` (`tbt.Beam`): The beam type to configure. 

858 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

859 - `voltage_kv` (`float`): The desired beam voltage in kilovolts. 

860 - `voltage_tol_kv` (`float`): The tolerance for the beam voltage in kilovolts. 

861 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the beam voltage (default is 5.0). 

862 

863 ## Returns 

864 

865 - `bool`: True if the beam voltage is set successfully, False otherwise. 

866 

867 ## Raises 

868 

869 - `ValueError`: If the beam voltage cannot be adjusted within the specified tolerance. If the beam is not controllable. 

870 

871 ## Examples 

872 

873 >>> import pytribeam.types as tbt 

874 >>> microscope = tbt.Microscope() 

875 >>> microscope.connect("localhost") 

876 Client connecting to [localhost:7520]... 

877 Client connected to [localhost:7520] 

878 >>> beam = tbt.ElectronBeam(settings=None) 

879 >>> success = beam_voltage(beam, microscope, voltage_kv=15.0, voltage_tol_kv=0.5) 

880 >>> print(success) 

881 True""" 

882 selected_beam = ut.beam_type(beam, microscope) 

883 

884 exisiting_voltage_v = selected_beam.high_voltage.value # volts 

885 delta_voltage_kv = abs( 

886 exisiting_voltage_v * cs.Conversions.V_TO_KV - voltage_kv 

887 ) # kilovolts 

888 if delta_voltage_kv > voltage_tol_kv: 888 ↛ 910line 888 didn't jump to line 910 because the condition on line 888 was always true

889 warnings.warn( 

890 "Requested beam current is not the current setting, imaging conditions may be non-ideal." 

891 ) 

892 beam_controllable = selected_beam.high_voltage.is_controllable 

893 

894 if not beam_controllable: 

895 raise ValueError( 

896 f"Unable to modify beam voltage, beam is not currently controllable." 

897 ) 

898 print("Adjusting beam voltage...") 

899 selected_beam.high_voltage.value = voltage_kv * cs.Conversions.KV_TO_V 

900 time.sleep(delay_s) 

901 new_voltage_kv = selected_beam.high_voltage.value * cs.Conversions.V_TO_KV 

902 voltage_diff_kv = abs(new_voltage_kv - voltage_kv) 

903 if voltage_diff_kv > voltage_tol_kv: 

904 raise ValueError( 

905 f"""Could not correctly adjust beam voltage, 

906 requested {voltage_kv} kV, current beam voltage is 

907 {new_voltage_kv} kV""" 

908 ) 

909 

910 return True 

911 

912 

913def beam_working_distance( 

914 beam: tbt.Beam, 

915 microscope: tbt.Microscope, 

916 wd_mm: float, 

917 delay_s: float = 0.1, 

918) -> bool: 

919 """ 

920 Sets the working distance for the selected beam, with inputs in units of millimeters. 

921 

922 This function sets the working distance (WD) for the specified beam type on the microscope. 

923 It converts the WD from millimeters to meters, sets the WD, and waits for the specified delay. 

924 This should be done before adjusting the horizontal field width. 

925 

926 ## Parameters 

927 

928 - `beam` (`tbt.Beam`): The beam type to configure. 

929 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

930 - `wd_mm` (`float`): The desired working distance in millimeters. 

931 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the working distance (default is 0.1). 

932 

933 ## Returns 

934 

935 - `bool`: True if the working distance is set successfully, False otherwise. 

936 

937 ## Raises 

938 

939 - `ValueError`: If the working distance cannot be adjusted correctly. 

940 

941 ## Examples 

942 

943 >>> import pytribeam.types as tbt 

944 >>> microscope = tbt.Microscope() 

945 >>> microscope.connect("localhost") 

946 Client connecting to [localhost:7520]... 

947 Client connected to [localhost:7520] 

948 >>> beam = tbt.ElectronBeam(settings=None) 

949 >>> success = beam_working_dist(beam, microscope, wd_mm=5.0) 

950 >>> print(success) 

951 True""" 

952 selected_beam = ut.beam_type(beam, microscope) 

953 wd_m = wd_mm * cs.Conversions.MM_TO_M 

954 selected_beam.working_distance.value = wd_m 

955 time.sleep(delay_s) 

956 if selected_beam.working_distance.value != wd_m: 

957 raise ValueError( 

958 f"""Could not correctly adjust working distance, 

959 requested {wd_m} meters, current working distance is 

960 {selected_beam.working_distance.value} meters""" 

961 ) 

962 

963 return True 

964 

965 

966def detector_auto_cb( 

967 microscope: tbt.Microscope, 

968 beam: tbt.Beam, 

969 settings: tbt.ScanArea, 

970 delay_s: float = 0.1, 

971) -> bool: 

972 """ 

973 Detector auto contrast brightness. Currently only reduced scan area option is supported. 

974 

975 This function sets the scanning mode to reduced area, runs the auto contrast-brightness function, 

976 and then sets the scanning mode back to full frame. 

977 

978 ## Parameters 

979 

980 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

981 - `beam` (`tbt.Beam`): The beam type to configure. 

982 - `settings` (`tbt.ScanArea`): The scan area settings for the reduced area. 

983 - `delay_s` (`float, optional`): The delay in seconds to wait after each operation (default is 0.1). 

984 

985 ## Returns 

986 

987 - `bool`: True if the auto contrast-brightness is completed successfully, False otherwise. 

988 

989 ## Raises 

990 

991 - `SystemError`: If unable to set the scan mode to reduced area or full frame. 

992 

993 ## Examples 

994 

995 >>> import pytribeam.types as tbt 

996 >>> microscope = tbt.Microscope() 

997 >>> microscope.connect("localhost") 

998 Client connecting to [localhost:7520]... 

999 Client connected to [localhost:7520] 

1000 >>> beam = tbt.ElectronBeam(settings=None) 

1001 >>> auto_cb_settings = tbt.ScanArea(left=0.1, top=0.1, width=0.8, height=0.8) 

1002 >>> success = detector_auto_cb(microscope, beam, auto_cb_settings) 

1003 >>> print(success) 

1004 True""" 

1005 selected_beam = ut.beam_type(beam, microscope) 

1006 selected_beam.scanning.mode.set_reduced_area( 

1007 left=settings.left, 

1008 top=settings.top, 

1009 width=settings.width, 

1010 height=settings.height, 

1011 ) 

1012 scan_mode = selected_beam.scanning.mode.value 

1013 if not scan_mode == tbt.ScanMode.REDUCED_AREA.value: 

1014 raise SystemError( 

1015 f"Unable to set imaging to reduced area before auto contrast-brightness. Current scan mode is {scan_mode}." 

1016 ) 

1017 microscope.auto_functions.run_auto_cb() 

1018 time.sleep(delay_s) 

1019 selected_beam.scanning.mode.set_full_frame() 

1020 time.sleep(delay_s) 

1021 

1022 new_scan_mode = selected_beam.scanning.mode.value 

1023 if new_scan_mode != tbt.ScanMode.FULL_FRAME.value: 

1024 raise SystemError( 

1025 f"Unable to set imaging back to full frame after auto contrast-brightness. Current scan mode is {new_scan_mode}." 

1026 ) 

1027 return True 

1028 

1029 

1030def detector_brightness( 

1031 microscope: tbt.Microscope, 

1032 brightness: float, 

1033 delay_s: float = 0.1, 

1034) -> bool: 

1035 """ 

1036 Sets the detector brightness with input from 0 to 1. 

1037 

1038 This function sets the brightness for the detector on the microscope and waits for the specified delay. 

1039 

1040 ## Parameters 

1041 

1042 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

1043 - `brightness` (`float`): The desired brightness value (from 0 to 1). 

1044 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the brightness (default is 0.1). 

1045 

1046 ## Returns 

1047 

1048 - `bool`: True if the brightness is set successfully, False otherwise. 

1049 

1050 ## Raises 

1051 

1052 - `ValueError`: If the brightness cannot be adjusted correctly. 

1053 

1054 ## Examples 

1055 

1056 >>> import pytribeam.types as tbt 

1057 >>> microscope = tbt.Microscope() 

1058 >>> microscope.connect("localhost") 

1059 Client connecting to [localhost:7520]... 

1060 Client connected to [localhost:7520] 

1061 >>> success = detector_brightness(microscope, brightness=0.5) 

1062 >>> print(success) 

1063 True""" 

1064 microscope.detector.brightness.value = brightness 

1065 current_detector = microscope.detector.type.value 

1066 time.sleep(delay_s) 

1067 if not math.isclose( 

1068 microscope.detector.brightness.value, 

1069 brightness, 

1070 abs_tol=cs.Constants.contrast_brightness_tolerance, 

1071 ): 

1072 raise ValueError( 

1073 f"""Could not correctly adjust detector brightness, 

1074 requested {brightness} on {current_detector} detector,  

1075 current brightness is {microscope.detector.brightness.value}""" 

1076 ) 

1077 return True 

1078 

1079 

1080def detector_contrast( 

1081 microscope: tbt.Microscope, 

1082 contrast: float, 

1083 delay_s: float = 0.1, 

1084) -> bool: 

1085 """ 

1086 Sets the detector contrast with input from 0 to 1. 

1087 

1088 This function sets the contrast for the detector on the microscope and waits for the specified delay. 

1089 

1090 ## Parameters 

1091 

1092 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

1093 - `contrast` (`float`): The desired contrast value (from 0 to 1). 

1094 - `delay_s` (`float, optional`): The delay in seconds to wait after adjusting the contrast (default is 0.1). 

1095 

1096 ## Returns 

1097 

1098 - `bool`: True if the contrast is set successfully, False otherwise. 

1099 

1100 ## Raises 

1101 

1102 - `ValueError`: If the contrast cannot be adjusted correctly. 

1103 

1104 ## Examples 

1105 

1106 >>> import pytribeam.types as tbt 

1107 >>> microscope = tbt.Microscope() 

1108 >>> microscope.connect("localhost") 

1109 Client connecting to [localhost:7520]... 

1110 Client connected to [localhost:7520] 

1111 >>> success = detector_contrast(microscope, contrast=0.5) 

1112 >>> print(success) 

1113 True""" 

1114 microscope.detector.contrast.value = contrast 

1115 current_detector = microscope.detector.type.value 

1116 time.sleep(delay_s) 

1117 if not math.isclose( 

1118 microscope.detector.contrast.value, 

1119 contrast, 

1120 abs_tol=cs.Constants.contrast_brightness_tolerance, 

1121 ): 

1122 raise ValueError( 

1123 f"""Could not correctly adjust detector contrast, 

1124 requested {contrast} on {current_detector} detector,  

1125 current contrast is {microscope.detector.contrast.value}""" 

1126 ) 

1127 return True 

1128 

1129 

1130def detector_cb( 

1131 microscope: tbt.Microscope, 

1132 detector_settings: tbt.Detector, 

1133 beam: tbt.Beam, 

1134) -> bool: 

1135 """ 

1136 Sets detector contrast and brightness. 

1137 

1138 This function sets the contrast and brightness for the detector on the microscope. 

1139 It also runs the auto contrast-brightness function if specified in the detector settings. 

1140 Supports initial settings of contrast and brightness with fixed values before auto adjustment. 

1141 

1142 ## Parameters 

1143 

1144 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

1145 - `detector_settings` (`tbt.Detector`): The detector settings, including contrast, brightness, and auto contrast-brightness settings. 

1146 - `beam` (`tbt.Beam`): The beam type to configure. 

1147 

1148 ## Returns 

1149 

1150 - `bool`: True if the contrast and brightness are set successfully, False otherwise. 

1151 

1152 ## Examples 

1153 

1154 >>> import pytribeam.types as tbt 

1155 >>> microscope = tbt.Microscope() 

1156 >>> microscope.connect("localhost") 

1157 Client connecting to [localhost:7520]... 

1158 Client connected to [localhost:7520] 

1159 >>> beam = tbt.ElectronBeam(settings=None) 

1160 >>> 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)) 

1161 >>> success = detector_cb(microscope, detector_settings, beam) 

1162 >>> print(success) 

1163 True 

1164 

1165 >>> detector_settings = tbt.Detector(contrast=0.2, brightness=0.3, auto_cb_settings=None) 

1166 >>> success = detector_cb(microscope, detector_settings, beam) 

1167 >>> print(success) 

1168 True 

1169 >>> beam_brightness = microscope.detector.brightness.value 

1170 >>> print(beam_brightness) 

1171 0.3 

1172 >>> beam_contrast = microscope.detector.contrast.value 

1173 >>> print(beam_contrast) 

1174 0.2""" 

1175 ### cannot ensure detector is the active one, will overwrite mode settings, so following line is not used 

1176 # microscope.detector.type.value = detector_settings.type.value 

1177 contrast = detector_settings.contrast 

1178 brightness = detector_settings.brightness 

1179 if contrast is not None: 

1180 detector_contrast(microscope=microscope, contrast=contrast) 

1181 if brightness is not None: 

1182 detector_brightness(microscope=microscope, brightness=brightness) 

1183 

1184 null_scan = tbt.ScanArea(left=None, top=None, width=None, height=None) 

1185 if not null_scan == detector_settings.auto_cb_settings: 

1186 detector_auto_cb( 

1187 microscope=microscope, 

1188 settings=detector_settings.auto_cb_settings, 

1189 beam=beam, 

1190 ) 

1191 return True 

1192 

1193 

1194def detector_mode( 

1195 microscope: tbt.Microscope, 

1196 detector_mode: tbt.DetectorMode, 

1197 delay_s: float = 0.1, 

1198) -> bool: 

1199 """ 

1200 Sets the detector mode. 

1201 

1202 This function sets the mode for the detector on the microscope and waits for the specified delay. 

1203 

1204 ## Parameters 

1205 

1206 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

1207 - `detector_mode` (`tbt.DetectorMode`): The desired detector mode. 

1208 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the detector mode (default is 0.1). 

1209 

1210 ## Returns 

1211 

1212 - `bool`: True if the detector mode is set successfully, False otherwise. 

1213 

1214 ## Raises 

1215 

1216 - `ValueError`: If the detector mode cannot be set correctly. 

1217 

1218 ## Examples 

1219 

1220 >>> import pytribeam.types as tbt 

1221 >>> microscope = tbt.Microscope() 

1222 >>> microscope.connect("localhost") 

1223 Client connecting to [localhost:7520]... 

1224 Client connected to [localhost:7520] 

1225 >>> mode = tbt.DetectorMode.SECONDARY_ELECTRONS 

1226 >>> success = detector_mode(microscope, mode) 

1227 >>> print(success) 

1228 True""" 

1229 microscope.detector.mode.value = detector_mode.value 

1230 time.sleep(delay_s) 

1231 if microscope.detector.mode.value != detector_mode.value: 

1232 raise ValueError( 

1233 f"""Could not correctly set detector mode, 

1234 requested {detector_mode}, current mode is 

1235 {microscope.detector.mode.value}""" 

1236 ) 

1237 return True 

1238 

1239 

1240def detector_type( 

1241 microscope: tbt.Microscope, 

1242 detector: tbt.DetectorType, 

1243 delay_s: float = 0.1, 

1244) -> bool: 

1245 """ 

1246 Sets the detector type. 

1247 

1248 This function sets the type for the detector on the microscope and waits for the specified delay. 

1249 

1250 ## Parameters 

1251 

1252 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

1253 - `detector` (`tbt.DetectorType`): The desired detector type. 

1254 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the detector type (default is 0.1). 

1255 

1256 ## Returns 

1257 

1258 - `bool`: True if the detector type is set successfully, False otherwise. 

1259 

1260 ## Raises 

1261 

1262 - `ValueError`: If the detector type cannot be set correctly. 

1263 

1264 ## Examples 

1265 

1266 >>> import pytribeam.types as tbt 

1267 >>> microscope = tbt.Microscope() 

1268 >>> microscope.connect("localhost") 

1269 Client connecting to [localhost:7520]... 

1270 Client connected to [localhost:7520] 

1271 >>> detector = tbt.DetectorType.ETD 

1272 >>> success = detector_type(microscope, detector) 

1273 >>> print(success) 

1274 True""" 

1275 microscope.detector.type.value = detector.value 

1276 time.sleep(delay_s) 

1277 if microscope.detector.type.value != detector.value: 

1278 raise ValueError( 

1279 f"""Could not correctly set detector type, 

1280 requested {detector}, current detector is 

1281 {microscope.detector.type.value}""" 

1282 ) 

1283 return True 

1284 

1285 

1286def grab_custom_resolution_frame( 

1287 img_settings: tbt.ImageSettings, 

1288 save_path: Path, 

1289) -> bool: 

1290 """ 

1291 Method for single frame imaging used with custom resolutions. 

1292 

1293 This function captures a single frame image using custom resolutions and saves it to the specified path. 

1294 

1295 ## Parameters 

1296 

1297 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope and scan resolution. 

1298 - `save_path` (`Path`): The path to save the captured image. 

1299 

1300 ## Returns 

1301 

1302 - `bool`: True if the image is captured and saved successfully, False otherwise. 

1303 

1304 ## Examples 

1305 

1306 >>> import pytribeam.types as tbt 

1307 >>> from pathlib import Path 

1308 >>> microscope = tbt.Microscope() 

1309 >>> microscope.connect("localhost") 

1310 Client connecting to [localhost:7520]... 

1311 Client connected to [localhost:7520] 

1312 >>> img_settings = tbt.ImageSettings( 

1313 ... microscope=microscope, 

1314 ... beam=tbt.ElectronBeam(settings=None), 

1315 ... detector=tbt.Detector(), 

1316 ... scan=tbt.Scan(resolution=tbt.Resolution(width=1024, height=768)), 

1317 ... bit_depth=tbt.ColorDepth.BITS_8, 

1318 ... ) 

1319 >>> save_path = Path("/path/to/save/image.tif") 

1320 >>> success = grab_custom_resolution_frame(img_settings, save_path) 

1321 >>> print(success) 

1322 True""" 

1323 microscope = img_settings.microscope 

1324 resolution = img_settings.scan.resolution 

1325 microscope.imaging.grab_frame_to_disk( 

1326 save_path.absolute().as_posix(), 

1327 file_format=tbt.ImageFileFormat.TIFF.value, 

1328 settings=tbt.GrabFrameSettings( 

1329 resolution=f"{resolution.width}x{resolution.height}" 

1330 ), 

1331 ) 

1332 return True 

1333 

1334 

1335def grab_preset_resolution_frame( 

1336 img_settings: tbt.ImageSettings, 

1337) -> tbt.AdornedImage: 

1338 """ 

1339 Method for single frame imaging used with preset resolutions. 

1340 

1341 This function captures a single frame image using preset resolutions. 

1342 

1343 ## Parameters 

1344 

1345 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, detector, and scan resolution. 

1346 

1347 ## Returns 

1348 

1349 - `tbt.AdornedImage`: The captured image. 

1350 

1351 ## Examples 

1352 

1353 >>> import pytribeam.types as tbt 

1354 >>> microscope = tbt.Microscope() 

1355 >>> microscope.connect("localhost") 

1356 Client connecting to [localhost:7520]... 

1357 Client connected to [localhost:7520] 

1358 >>> img_settings = tbt.ImageSettings( 

1359 ... microscope=microscope, 

1360 ... beam=tbt.ElectronBeam(settings=None), 

1361 ... detector=tbt.Detector(), 

1362 ... scan=tbt.Scan(resolution=tbt.PresetResolution.PRESET_768X512), 

1363 ... bit_depth=tbt.ColorDepth.BITS_8, 

1364 ... ) 

1365 >>> image = grab_preset_resolution_frame(img_settings) 

1366 >>> print(image) 

1367 AdornedImage(width=768, height=512, bit_depth=8)""" 

1368 beam = img_settings.beam 

1369 microscope = img_settings.microscope 

1370 beam_scan_resolution( 

1371 beam=beam, 

1372 microscope=microscope, 

1373 resolution=img_settings.scan.resolution, 

1374 ) 

1375 return microscope.imaging.grab_frame( 

1376 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth) 

1377 ) 

1378 

1379 

1380def imaging_detector(img_settings: tbt.ImageSettings) -> bool: 

1381 """ 

1382 Prepares the detector and inserts it if applicable. 

1383 

1384 This function sets the detector type, inserts the detector if necessary, sets the detector mode, 

1385 and adjusts the contrast and brightness settings. It is important to set detector mode settings 

1386 right before contrast and brightness as any subsequent calls to a detector type can overwrite the mode. 

1387 

1388 ## Parameters 

1389 

1390 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, and detector settings. 

1391 

1392 ## Returns 

1393 

1394 - `bool`: True if the detector is prepared successfully, False otherwise. 

1395 

1396 ## Examples 

1397 

1398 >>> import pytribeam.types as tbt 

1399 >>> microscope = tbt.Microscope() 

1400 >>> microscope.connect("localhost") 

1401 Client connecting to [localhost:7520]... 

1402 Client connected to [localhost:7520] 

1403 >>> img_settings = tbt.ImageSettings( 

1404 ... microscope=microscope, 

1405 ... beam=tbt.ElectronBeam(settings=None), 

1406 ... detector=tbt.Detector( 

1407 ... type=tbt.DetectorType.ETD, 

1408 ... mode=tbt.DetectorMode.SECONDARY_ELECTRONS, 

1409 ... brightness=0.4, 

1410 ... contrast=0.2, 

1411 ... ), 

1412 ... scan=tbt.Scan(resolution=tbt.PresetResolution.PRESET_768X512), 

1413 ... bit_depth=tbt.ColorDepth.BITS_8, 

1414 ... ) 

1415 >>> success = imaging_detector(img_settings) 

1416 >>> print(success) 

1417 True""" 

1418 microscope = img_settings.microscope 

1419 detector = img_settings.detector.type 

1420 detector_type( 

1421 microscope=microscope, 

1422 detector=detector, 

1423 ) 

1424 detector_state = devices.detector_state( 

1425 microscope=microscope, 

1426 detector=detector, 

1427 ) 

1428 if detector_state is not tbt.RetractableDeviceState.STATIONARY: 

1429 devices.insert_detector( 

1430 microscope=microscope, 

1431 detector=detector, 

1432 ) 

1433 detector_mode( 

1434 microscope=microscope, 

1435 detector_mode=img_settings.detector.mode, 

1436 ) 

1437 detector_cb( 

1438 microscope=microscope, 

1439 detector_settings=img_settings.detector, 

1440 beam=img_settings.beam, 

1441 ) 

1442 return True 

1443 

1444 

1445def imaging_device( 

1446 microscope: tbt.Microscope, 

1447 beam: tbt.Beam, 

1448) -> bool: 

1449 """ 

1450 Prepares the imaging beam, viewing quad, and the beam voltage and current. 

1451 

1452 This function sets the beam device, ensures the beam is ready, sets the beam voltage and current, 

1453 and applies angular correction if the beam type is electron. 

1454 

1455 ## Parameters 

1456 

1457 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

1458 - `beam` (`tbt.Beam`): The beam type to configure, including its settings. 

1459 

1460 ## Returns 

1461 

1462 - `bool`: True if the imaging device is prepared successfully, False otherwise.""" 

1463 set_beam_device(microscope=microscope, device=beam.device) 

1464 beam_ready(beam=beam, microscope=microscope) 

1465 beam_voltage( 

1466 beam=beam, 

1467 microscope=microscope, 

1468 voltage_kv=beam.settings.voltage_kv, 

1469 voltage_tol_kv=beam.settings.voltage_tol_kv, 

1470 ) 

1471 beam_current( 

1472 beam=beam, 

1473 microscope=microscope, 

1474 current_na=beam.settings.current_na, 

1475 current_tol_na=beam.settings.current_tol_na, 

1476 ) 

1477 if beam.type == tbt.BeamType.ELECTRON: 1477 ↛ 1483line 1477 didn't jump to line 1483 because the condition on line 1477 was always true

1478 beam_angular_correction( 

1479 microscope=microscope, 

1480 dynamic_focus=beam.settings.dynamic_focus, 

1481 tilt_correction=beam.settings.tilt_correction, 

1482 ) 

1483 return True 

1484 

1485 

1486def imaging_scan(img_settings: tbt.ImageSettings) -> bool: 

1487 """ 

1488 Sets all scan settings except for the resolution. 

1489 

1490 This function configures the scan settings for the specified image settings, including 

1491 setting the scan mode to full frame, scan rotation, working distance, horizontal field width, 

1492 and dwell time. 

1493 

1494 ## Parameters 

1495 

1496 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, and scan settings. 

1497 

1498 ## Returns 

1499 

1500 - `bool`: True if the scan settings are configured successfully, False otherwise.""" 

1501 microscope = img_settings.microscope 

1502 beam = img_settings.beam 

1503 beam_scan_full_frame( 

1504 beam=beam, 

1505 microscope=microscope, 

1506 ) 

1507 beam_scan_rotation( 

1508 beam=beam, microscope=microscope, rotation_deg=img_settings.scan.rotation_deg 

1509 ) 

1510 beam_working_distance( 

1511 beam=beam, 

1512 microscope=microscope, 

1513 wd_mm=img_settings.beam.settings.working_dist_mm, 

1514 ) 

1515 beam_hfw(beam=beam, microscope=microscope, hfw_mm=img_settings.beam.settings.hfw_mm) 

1516 beam_dwell_time( 

1517 beam=beam, microscope=microscope, dwell_us=img_settings.scan.dwell_time_us 

1518 ) 

1519 return True 

1520 

1521 

1522def prepare_imaging(img_settings: tbt.ImageSettings) -> bool: 

1523 """ 

1524 Prepares various imaging settings. 

1525 

1526 This function prepares the imaging device, scan settings, and detector settings 

1527 based on the specified image settings. 

1528 

1529 ## Parameters 

1530 

1531 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings. 

1532 

1533 ## Returns 

1534 

1535 - `bool`: True if the imaging settings are prepared successfully, False otherwise.""" 

1536 imaging_device(microscope=img_settings.microscope, beam=img_settings.beam) 

1537 imaging_scan(img_settings=img_settings) 

1538 imaging_detector(img_settings=img_settings) 

1539 return True 

1540 

1541 

1542def set_view( 

1543 microscope: tbt.Microscope, 

1544 quad: tbt.ViewQuad, 

1545) -> bool: 

1546 """ 

1547 Sets the active view to the specified quad. 

1548 

1549 This function sets the active imaging view to the specified quad on the microscope. 

1550 

1551 ## Parameters 

1552 

1553 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

1554 - `quad` (`tbt.ViewQuad`): The imaging view to select: - 1 is upper left - 2 is upper right - 3 is lower left - 4 is lower right 

1555 

1556 ## Returns 

1557 

1558 - `bool`: True if the active view is set successfully, False otherwise.""" 

1559 microscope.imaging.set_active_view(quad.value) 

1560 

1561 

1562def set_beam_device( 

1563 microscope: tbt.Microscope, 

1564 device: tbt.Device, 

1565 delay_s: float = 0.1, 

1566) -> bool: 

1567 """ 

1568 Sets the active imaging device. 

1569 

1570 This function sets the active imaging device on the microscope and waits for the specified delay. 

1571 

1572 ## Parameters 

1573 

1574 - `microscope` (`tbt.Microscope`): The microscope object to configure. 

1575 - `device` (`tbt.Device`): The desired imaging device. 

1576 - `delay_s` (`float, optional`): The delay in seconds to wait after setting the device (default is 0.1). 

1577 

1578 ## Returns 

1579 

1580 - `bool`: True if the active device is set successfully, False otherwise. 

1581 

1582 ## Raises 

1583 

1584 - `ValueError`: If the active device cannot be set correctly.""" 

1585 microscope.imaging.set_active_device(device) 

1586 time.sleep(delay_s) 

1587 curr_device = tbt.Device(microscope.imaging.get_active_device()) 

1588 if curr_device != device.value: 

1589 raise ValueError( 

1590 f"""Could not set active device, 

1591 requested device {device.value} but current device is {curr_device}. 

1592 Device list: 

1593 {tbt.Device.ELECTRON_BEAM.value}: Electron Beam 

1594 {tbt.Device.ION_BEAM.value}: Ion Beam 

1595 {tbt.Device.CCD_CAMERA.value}: CCD Camera 

1596 {tbt.Device.IR_CAMERA.value}: IR Camera 

1597 {tbt.Device.NAV_CAM.value}: Nav Cam""" 

1598 ) 

1599 return True 

1600 

1601 

1602################# 

1603 

1604 

1605def collect_single_image( 

1606 save_path: Path, 

1607 img_settings: tbt.ImageSettings, 

1608) -> bool: 

1609 """ 

1610 Collects a single frame image with defined image settings. 

1611 

1612 This function prepares the imaging settings, sets the view, and captures a single frame image. 

1613 It saves the image to the specified path. If a non-preset resolution is requested, the image 

1614 will be saved at 8-bit color depth. 

1615 

1616 ## Parameters 

1617 

1618 - `save_path` (`Path`): The path to save the captured image. 

1619 - `img_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings. 

1620 

1621 ## Returns 

1622 

1623 - `bool`: True if the image is captured and saved successfully, False otherwise.""" 

1624 beam = img_settings.beam 

1625 microscope = img_settings.microscope 

1626 set_view(microscope=microscope, quad=beam.default_view) 

1627 prepare_imaging(img_settings=img_settings) 

1628 

1629 resolution = img_settings.scan.resolution 

1630 if isinstance(resolution, tbt.PresetResolution): 

1631 img = grab_preset_resolution_frame(img_settings=img_settings) 

1632 img.save(str(save_path)) 

1633 return True 

1634 warnings.warn( 

1635 f'Warning, non-preset resolution of "{img_settings.scan.resolution}" requested. Image will automatically be saved at 8-bit color depth' 

1636 ) 

1637 if img_settings.bit_depth != tbt.ColorDepth.BITS_8: 

1638 warnings.warn( 

1639 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." 

1640 ) 

1641 grab_custom_resolution_frame( 

1642 img_settings=img_settings, 

1643 save_path=save_path, 

1644 ) 

1645 return True 

1646 

1647 

1648def collect_multiple_images( 

1649 multiple_img_settings: List[tbt.ImageSettings], num_frames: int 

1650) -> List[tbt.AdornedImage]: 

1651 """ 

1652 Sets up scanning for multiple frames. 

1653 

1654 This function is best used for collecting multiple segments on a single detector simultaneously. 

1655 It is limited to preset resolutions only. 

1656 

1657 ## Parameters 

1658 

1659 - `multiple_img_settings` (`List[tbt.ImageSettings]`): The list of image settings for each frame, including the microscope, beam, scan, and detector settings. 

1660 - `num_frames` (`int`): The number of frames to collect. 

1661 

1662 ## Returns 

1663 

1664 - `List[tbt.AdornedImage]`: The list of captured images. 

1665 

1666 ## Raises 

1667 

1668 - `ValueError`: If a non-preset resolution is requested for simultaneous multiple frame imaging. 

1669 

1670 ## Notes 

1671 

1672 This method has not yet been tested.""" 

1673 # TODO test this 

1674 

1675 # Ensure that an insertable detector is done first so that inserting the detector doesn't interfere with other detectors 

1676 # Will need to make sure that only one insertable detector at a time 

1677 insertable_detector = [ 

1678 devices.detector_state( 

1679 microscope=_set.microscope, 

1680 detector=_set.detector, 

1681 ) 

1682 is not tbt.RetractableDeviceState.STATIONARY 

1683 for _set in multiple_img_settings 

1684 ] 

1685 if sum(insertable_detector) > 1: 

1686 raise NotImplementedError( 

1687 "Collecting multiple images with more than one insertable detector is not supported." 

1688 ) 

1689 elif sum(insertable_detector) == 1: 

1690 start = multiple_img_settings[insertable_detector.index(True)] 

1691 multiple_img_settings = [start] + multiple_img_settings 

1692 

1693 warnings.warn("Method not yet tested") 

1694 views = [] 

1695 for quad in range(1, num_frames + 1): 

1696 img_settings = multiple_img_settings[quad - 1] 

1697 resolution = img_settings.scan.resolution 

1698 if not isinstance(resolution, tbt.PresetResolution): 

1699 raise ValueError( 

1700 f'Only preset resolutions allowed for simultaneous multiple frame imaging, but resolution of "{resolution.width}x{resolution.height}" was requested.' 

1701 ) 

1702 

1703 microscope = img_settings.microscope 

1704 beam = img_settings.beam 

1705 views.append(quad) 

1706 set_view(microscope=microscope, quad=img_settings.beam.default_view) 

1707 prepare_imaging(microscope=microscope, beam=beam, img_settings=img_settings) 

1708 frames = microscope.imaging.grab_multiple_frames( 

1709 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth, views=views) 

1710 ) 

1711 return frames 

1712 

1713 

1714# TODO 

1715# def image_method(dict): 

1716# """determine imaging method and return associated function""" 

1717# pass 

1718 

1719# TODO 

1720# def collect_tiling_image(): 

1721# for loop 

1722# collect_standard_image 

1723# #stage move 

1724 

1725 

1726# TODO add more complex imaging behavior, determine method in this function 

1727def image_operation( 

1728 step: tbt.Step, 

1729 image_settings: tbt.ImageSettings, 

1730 general_settings: tbt.GeneralSettings, 

1731 slice_number: int, 

1732) -> bool: 

1733 """ 

1734 Performs an image operation based on the specified settings. 

1735 

1736 This function collects an image, saves it to the specified directory, and turns off tilt correction and dynamic focus. 

1737 

1738 ## Parameters 

1739 

1740 - `step` (`tbt.Step`): The step information, including the name of the step. 

1741 - `image_settings` (`tbt.ImageSettings`): The image settings, including the microscope, beam, scan, and detector settings. 

1742 - `general_settings` (`tbt.GeneralSettings`): The general settings, including the experimental directory. 

1743 - `slice_number` (`int`): The slice number for naming the saved image file. 

1744 

1745 ## Returns 

1746 

1747 - `bool`: True if the image operation is performed successfully, False otherwise.""" 

1748 print("\tCollecting image") 

1749 # create folder in same directory as experimental directory 

1750 image_directory = Path(general_settings.exp_dir).joinpath(step.name) 

1751 image_directory.mkdir(parents=True, exist_ok=True) 

1752 

1753 # TODO 

1754 # determine_image_method() 

1755 # collect_multiple_images() 

1756 

1757 # single image process: 

1758 save_path = image_directory.joinpath(f"{slice_number:04}.tif") 

1759 collect_single_image(save_path=save_path, img_settings=image_settings) 

1760 print(f"\tImage saved to {save_path}") 

1761 

1762 # turn off tilt correction and dynamic focus 

1763 beam_angular_correction( 

1764 microscope=image_settings.microscope, 

1765 dynamic_focus=False, 

1766 tilt_correction=False, 

1767 ) 

1768 

1769 return True