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

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 "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] 

128 

129# Default python modules 

130# from functools import singledispatch 

131from pathlib import Path 

132import time 

133import warnings 

134from typing import List, Optional 

135import math 

136 

137# Local scripts 

138import pytribeam.constants as cs 

139import pytribeam.insertable_devices as devices 

140import pytribeam.types as tbt 

141import pytribeam.utilities as ut 

142 

143 

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 

149 

150 ## Parameters 

151 

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

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

154 

155 ## Returns 

156 

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

161 

162 # Parse device 

163 if device is None: 

164 device = original_device 

165 

166 # Set device 

167 set_beam_device(microscope, device) 

168 

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) 

172 

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) 

184 

185 # Clean up 

186 detector_type(microscope, original_detector_type) 

187 detector_mode(microscope, original_detector_mode) 

188 set_beam_device(microscope, original_device) 

189 

190 return available_types 

191 

192 

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 

200 

201 ## Parameters 

202 

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 

206 

207 ## Returns 

208 

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

213 

214 # Parse device 

215 if device is None: 

216 device = original_device 

217 

218 # Set device 

219 set_beam_device(microscope, device) 

220 

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) 

224 

225 # Parse the detector type 

226 if detector is None: 

227 detector = original_detector_type 

228 

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 

245 

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 ] 

250 

251 # Clean up 

252 detector_type(microscope, original_detector_type) 

253 detector_mode(microscope, original_detector_mode) 

254 set_beam_device(microscope, original_device) 

255 

256 return available_modes 

257 

258 

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 

264 

265 ## Parameters 

266 

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

268 

269 ## Returns 

270 

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) 

277 

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) 

281 

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

287 

288 # Clean up 

289 detector_type(microscope, original_detector_type) 

290 detector_mode(microscope, original_detector_mode) 

291 set_beam_device(microscope, original_device) 

292 

293 return insertable_detector_states 

294 

295 

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 

301 

302 ## Parameters 

303 

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

305 

306 ## Returns 

307 

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) 

314 

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) 

318 

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) 

323 

324 # Clean up 

325 detector_type(microscope, original_detector_type) 

326 detector_mode(microscope, original_detector_mode) 

327 set_beam_device(microscope, original_device) 

328 

329 return insertable_detectors 

330 

331 

332###################### 

333 

334 

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. 

344 

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. 

348 

349 ## Parameters 

350 

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

355 

356 ## Returns 

357 

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

359 

360 ## Raises 

361 

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

363 

364 ## Examples 

365 

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 

378 

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

392 

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

403 

404 

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. 

414 

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. 

418 

419 ## Parameters 

420 

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

426 

427 ## Returns 

428 

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

430 

431 ## Raises 

432 

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

434 

435 ## Examples 

436 

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) 

447 

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) 

459 

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 ) 

468 

469 return True 

470 

471 

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. 

480 

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. 

484 

485 ## Parameters 

486 

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

491 

492 ## Returns 

493 

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

495 

496 ## Raises 

497 

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

499 

500 ## Examples 

501 

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 ) 

525 

526 return True 

527 

528 

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. 

538 

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. 

542 

543 ## Parameters 

544 

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

549 

550 ## Returns 

551 

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

553 

554 ## Raises 

555 

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

557 

558 ## Examples 

559 

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 ) 

584 

585 return True 

586 

587 

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. 

596 

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. 

599 

600 ## Parameters 

601 

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

606 

607 ## Returns 

608 

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

610 

611 ## Raises 

612 

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. 

614 

615 ## Examples 

616 

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) 

627 

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}"') 

632 

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 ) 

645 

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 ) 

658 

659 return True 

660 

661 

662def beam_scan_full_frame( 

663 beam: tbt.Beam, 

664 microscope: tbt.Microscope, 

665) -> bool: 

666 """ 

667 Set beam scan mode to full frame. 

668 

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

670 

671 ## Parameters 

672 

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

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

675 

676 ## Returns 

677 

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

679 

680 ## Raises 

681 

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

683 

684 ## Examples 

685 

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 

703 

704 

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. 

713 

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. 

716 

717 ## Parameters 

718 

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

723 

724 ## Returns 

725 

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

727 

728 ## Raises 

729 

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

731 

732 ## Examples 

733 

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 ) 

749 

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 ) 

759 

760 return True 

761 

762 

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. 

771 

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. 

774 

775 ## Parameters 

776 

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

781 

782 ## Returns 

783 

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

785 

786 ## Raises 

787 

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

789 

790 ## Examples 

791 

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 ) 

811 

812 return True 

813 

814 

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. 

824 

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. 

827 

828 ## Parameters 

829 

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

835 

836 ## Returns 

837 

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

839 

840 ## Raises 

841 

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

843 

844 ## Examples 

845 

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) 

856 

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 

866 

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 ) 

882 

883 return True 

884 

885 

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. 

894 

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. 

898 

899 ## Parameters 

900 

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

905 

906 ## Returns 

907 

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

909 

910 ## Raises 

911 

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

913 

914 ## Examples 

915 

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 ) 

935 

936 return True 

937 

938 

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. 

947 

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. 

950 

951 ## Parameters 

952 

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

957 

958 ## Returns 

959 

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

961 

962 ## Raises 

963 

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

965 

966 ## Examples 

967 

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) 

994 

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 

1001 

1002 

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. 

1010 

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

1012 

1013 ## Parameters 

1014 

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

1018 

1019 ## Returns 

1020 

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

1022 

1023 ## Raises 

1024 

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

1026 

1027 ## Examples 

1028 

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 

1051 

1052 

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. 

1060 

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

1062 

1063 ## Parameters 

1064 

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

1068 

1069 ## Returns 

1070 

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

1072 

1073 ## Raises 

1074 

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

1076 

1077 ## Examples 

1078 

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 

1101 

1102 

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. 

1110 

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. 

1114 

1115 ## Parameters 

1116 

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. 

1120 

1121 ## Returns 

1122 

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

1124 

1125 ## Examples 

1126 

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 

1137 

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) 

1156 

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 

1165 

1166 

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. 

1174 

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

1176 

1177 ## Parameters 

1178 

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

1182 

1183 ## Returns 

1184 

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

1186 

1187 ## Raises 

1188 

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

1190 

1191 ## Examples 

1192 

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 

1211 

1212 

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. 

1220 

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

1222 

1223 ## Parameters 

1224 

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

1228 

1229 ## Returns 

1230 

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

1232 

1233 ## Raises 

1234 

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

1236 

1237 ## Examples 

1238 

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 

1257 

1258 

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. 

1265 

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

1267 

1268 ## Parameters 

1269 

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. 

1272 

1273 ## Returns 

1274 

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

1276 

1277 ## Examples 

1278 

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 

1306 

1307 

1308def grab_preset_resolution_frame( 

1309 img_settings: tbt.ImageSettings, 

1310) -> tbt.AdornedImage: 

1311 """ 

1312 Method for single frame imaging used with preset resolutions. 

1313 

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

1315 

1316 ## Parameters 

1317 

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

1319 

1320 ## Returns 

1321 

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

1323 

1324 ## Examples 

1325 

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 ) 

1351 

1352 

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

1354 """ 

1355 Prepares the detector and inserts it if applicable. 

1356 

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. 

1360 

1361 ## Parameters 

1362 

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

1364 

1365 ## Returns 

1366 

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

1368 

1369 ## Examples 

1370 

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 

1416 

1417 

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. 

1424 

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. 

1427 

1428 ## Parameters 

1429 

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

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

1432 

1433 ## Returns 

1434 

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 

1457 

1458 

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

1460 """ 

1461 Sets all scan settings except for the resolution. 

1462 

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. 

1466 

1467 ## Parameters 

1468 

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

1470 

1471 ## Returns 

1472 

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 

1493 

1494 

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

1496 """ 

1497 Prepares various imaging settings. 

1498 

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

1500 based on the specified image settings. 

1501 

1502 ## Parameters 

1503 

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

1505 

1506 ## Returns 

1507 

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 

1513 

1514 

1515def set_view( 

1516 microscope: tbt.Microscope, 

1517 quad: tbt.ViewQuad, 

1518) -> bool: 

1519 """ 

1520 Sets the active view to the specified quad. 

1521 

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

1523 

1524 ## Parameters 

1525 

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 

1528 

1529 ## Returns 

1530 

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

1532 microscope.imaging.set_active_view(quad.value) 

1533 

1534 

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. 

1542 

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

1544 

1545 ## Parameters 

1546 

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

1550 

1551 ## Returns 

1552 

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

1554 

1555 ## Raises 

1556 

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 

1573 

1574 

1575################# 

1576 

1577 

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. 

1584 

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. 

1588 

1589 ## Parameters 

1590 

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. 

1593 

1594 ## Returns 

1595 

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) 

1601 

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 

1619 

1620 

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. 

1626 

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

1628 It is limited to preset resolutions only. 

1629 

1630 ## Parameters 

1631 

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. 

1634 

1635 ## Returns 

1636 

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

1638 

1639 ## Raises 

1640 

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

1642 

1643 ## Notes 

1644 

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

1646 # TODO test this 

1647 

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 

1665 

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 ) 

1675 

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 

1685 

1686 

1687# TODO 

1688# def image_method(dict): 

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

1690# pass 

1691 

1692# TODO 

1693# def collect_tiling_image(): 

1694# for loop 

1695# collect_standard_image 

1696# #stage move 

1697 

1698 

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. 

1708 

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

1710 

1711 ## Parameters 

1712 

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. 

1717 

1718 ## Returns 

1719 

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) 

1725 

1726 # TODO 

1727 # determine_image_method() 

1728 # collect_multiple_images() 

1729 

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}") 

1734 

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 ) 

1741 

1742 return True