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

283 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2026-09-09 18:27 +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 "beam_angular_correction", 

97 "beam_current", 

98 "beam_dwell_time", 

99 "beam_hfw", 

100 "beam_ready", 

101 "beam_scan_full_frame", 

102 "beam_scan_resolution", 

103 "beam_scan_rotation", 

104 "beam_voltage", 

105 "beam_working_distance", 

106 "collect_multiple_images", 

107 "collect_single_image", 

108 "detector_auto_cb", 

109 "detector_brightness", 

110 "detector_cb", 

111 "detector_contrast", 

112 "detector_mode", 

113 "detector_type", 

114 "grab_custom_resolution_frame", 

115 "grab_preset_resolution_frame", 

116 "image_operation", 

117 "imaging_detector", 

118 "imaging_device", 

119 "imaging_scan", 

120 "prepare_imaging", 

121 "set_beam_device", 

122 "set_view", 

123] 

124 

125# Default python modules 

126# from functools import singledispatch 

127from pathlib import Path 

128import time 

129import warnings 

130from typing import List 

131import math 

132 

133# Local scripts 

134import pytribeam.constants as cs 

135import pytribeam.insertable_devices as devices 

136import pytribeam.types as tbt 

137import pytribeam.utilities as ut 

138 

139 

140def beam_angular_correction( 

141 microscope: tbt.Microscope, 

142 dynamic_focus: bool, 

143 tilt_correction: bool, 

144 # correction_angle_deg: float = None, 

145 delay_s: float = 0.5, 

146) -> bool: 

147 """ 

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

149 

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

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

152 based on the provided parameters. 

153 

154 ## Parameters 

155 

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

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

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

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

160 

161 ## Returns 

162 

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

164 

165 ## Raises 

166 

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

168 

169 ## Examples 

170 

171 >>> import pytribeam.types as tbt 

172 >>> microscope = tbt.Microscope() 

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

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

175 Client connected to [localhost:7520] 

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

177 >>> print(success) 

178 True""" 

179 angular_correction = microscope.beams.electron_beam.angular_correction 

180 angular_correction.mode = tbt.AngularCorrectionMode.AUTOMATIC 

181 # scan rotation must be zero for auto mode 

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

183 

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

185 # if correction_angle_deg is not None: 

186 # angular_correction.mode = tbt.AngularCorrectionMode.MANUAL 

187 if dynamic_focus: 

188 angular_correction.dynamic_focus.turn_on() 

189 time.sleep(delay_s) 

190 if not angular_correction.dynamic_focus.is_on: 190 ↛ 192line 190 didn't jump to line 192 because the condition on line 190 was always true

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

192 if (not dynamic_focus) or (dynamic_focus is None): 192 ↛ 198line 192 didn't jump to line 198 because the condition on line 192 was always true

193 angular_correction.dynamic_focus.turn_off() 

194 time.sleep(delay_s) 

195 if angular_correction.dynamic_focus.is_on: 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true

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

197 

198 if tilt_correction: 198 ↛ 199line 198 didn't jump to line 199 because the condition on line 198 was never true

199 angular_correction.tilt_correction.turn_on() 

200 time.sleep(delay_s) 

201 if not angular_correction.tilt_correction.is_on: 201 ↛ 203line 201 didn't jump to line 203 because the condition on line 201 was always true

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

203 if (not tilt_correction) or (tilt_correction is None): 203 ↛ exitline 203 didn't return from function 'beam_angular_correction' because the condition on line 203 was always true

204 angular_correction.tilt_correction.turn_off() 

205 time.sleep(delay_s) 

206 if angular_correction.tilt_correction.is_on: 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true

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

208 

209 

210def beam_current( 

211 beam: tbt.Beam, 

212 microscope: tbt.Microscope, 

213 current_na: float, 

214 current_tol_na: float, 

215 delay_s: float = 5.0, 

216) -> bool: 

217 """ 

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

219 

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

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

222 waits for the specified delay. 

223 

224 ## Parameters 

225 

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

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

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

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

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

231 

232 ## Returns 

233 

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

235 

236 ## Raises 

237 

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

239 

240 ## Examples 

241 

242 >>> import pytribeam.types as tbt 

243 >>> microscope = tbt.Microscope() 

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

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

246 Client connected to [localhost:7520] 

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

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

249 >>> print(success) 

250 True""" 

251 selected_beam = ut.beam_type(beam, microscope) 

252 

253 exisiting_current_a = selected_beam.beam_current.value # amps 

254 delta_current_na = abs( 

255 exisiting_current_a * cs.Conversions.A_TO_NA - current_na 

256 ) # nanoamps 

257 if delta_current_na > current_tol_na: 

258 warnings.warn( 

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

260 ) 

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

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

263 time.sleep(delay_s) 

264 

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

266 current_diff_na = abs(new_current_na - current_na) 

267 if current_diff_na > current_tol_na: 267 ↛ 268line 267 didn't jump to line 268 because the condition on line 267 was never true

268 raise ValueError( 

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

270 requested {current_na} nA, current beam current is 

271 {new_current_na} nA""" 

272 ) 

273 

274 return True 

275 

276 

277def beam_dwell_time( 

278 beam: tbt.Beam, 

279 microscope: tbt.Microscope, 

280 dwell_us: float, 

281 delay_s: float = 0.1, 

282) -> bool: 

283 """ 

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

285 

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

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

288 waits for the specified delay. 

289 

290 ## Parameters 

291 

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

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

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

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

296 

297 ## Returns 

298 

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

300 

301 ## Raises 

302 

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

304 

305 ## Examples 

306 

307 >>> import pytribeam.types as tbt 

308 >>> microscope = tbt.Microscope() 

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

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

311 Client connected to [localhost:7520] 

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

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

314 >>> print(success) 

315 True""" 

316 selected_beam = ut.beam_type(beam, microscope) 

317 dwell_s = dwell_us * cs.Conversions.US_TO_S 

318 selected_beam.scanning.dwell_time.value = dwell_s 

319 time.sleep(delay_s) 

320 if not math.isclose( 320 ↛ 325line 320 didn't jump to line 325 because the condition on line 320 was never true

321 selected_beam.scanning.dwell_time.value, 

322 dwell_s, 

323 rel_tol=cs.Constants.beam_dwell_tol_ratio, 

324 ): 

325 raise ValueError( 

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

327 requested {dwell_s} seconds, current dwell time is 

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

329 ) 

330 

331 return True 

332 

333 

334def beam_hfw( 

335 beam: tbt.Beam, 

336 microscope: tbt.Microscope, 

337 hfw_mm: float, 

338 delay_s: float = 0.1, 

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

340) -> bool: 

341 """ 

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

343 

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

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

346 This should be done after adjusting the working distance. 

347 

348 ## Parameters 

349 

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

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

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

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

354 

355 ## Returns 

356 

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

358 

359 ## Raises 

360 

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

362 

363 ## Examples 

364 

365 >>> import pytribeam.types as tbt 

366 >>> microscope = tbt.Microscope() 

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

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

369 Client connected to [localhost:7520] 

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

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

372 >>> print(success) 

373 True""" 

374 selected_beam = ut.beam_type(beam, microscope) 

375 hfw_m = hfw_mm * cs.Conversions.MM_TO_M 

376 selected_beam.horizontal_field_width.value = hfw_m 

377 time.sleep(delay_s) 

378 if not math.isclose( 378 ↛ 384line 378 didn't jump to line 384 because the condition on line 378 was never true

379 selected_beam.horizontal_field_width.value, 

380 hfw_m, 

381 abs_tol=hfw_tol_mm 

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

383 ): 

384 raise ValueError( 

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

386 requested {hfw_mm} millimeters, current field with is 

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

388 ) 

389 

390 return True 

391 

392 

393def beam_ready( 

394 beam: tbt.Beam, 

395 microscope: tbt.Microscope, 

396 delay_s: float = 5.0, 

397 attempts: int = 2, 

398) -> bool: 

399 """ 

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

401 

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

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

404 

405 ## Parameters 

406 

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

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

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

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

411 

412 ## Returns 

413 

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

415 

416 ## Raises 

417 

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

419 

420 ## Examples 

421 

422 >>> import pytribeam.types as tbt 

423 >>> microscope = tbt.Microscope() 

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

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

426 Client connected to [localhost:7520] 

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

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

429 >>> print(success) 

430 True""" 

431 selected_beam = ut.beam_type(beam, microscope) 

432 

433 # check vaccum 

434 vacuum = microscope.vacuum.chamber_state 

435 if vacuum != tbt.VacuumState.PUMPED.value: 435 ↛ 436line 435 didn't jump to line 436 because the condition on line 435 was never true

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

437 

438 # check beam on 

439 count: int = 0 

440 while count < attempts: 

441 beam_on = selected_beam.is_on 

442 if not beam_on: 

443 selected_beam.turn_on() 

444 time.sleep(delay_s) 

445 count += 1 

446 if not beam_on: 

447 raise ValueError( 

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

449 ) 

450 

451 # check beam blank 

452 count: int = 0 

453 while count < attempts: 

454 beam_blank = selected_beam.is_blanked 

455 if beam_blank: 

456 selected_beam.unblank() 

457 time.sleep(delay_s) 

458 count += 1 

459 if beam_blank: 

460 raise ValueError( 

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

462 ) 

463 

464 return True 

465 

466 

467def beam_scan_full_frame( 

468 beam: tbt.Beam, 

469 microscope: tbt.Microscope, 

470) -> bool: 

471 """ 

472 Set beam scan mode to full frame. 

473 

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

475 

476 ## Parameters 

477 

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

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

480 

481 ## Returns 

482 

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

484 

485 ## Raises 

486 

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

488 

489 ## Examples 

490 

491 >>> import pytribeam.types as tbt 

492 >>> microscope = tbt.Microscope() 

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

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

495 Client connected to [localhost:7520] 

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

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

498 >>> print(success) 

499 True""" 

500 selected_beam = ut.beam_type(beam, microscope) 

501 selected_beam.scanning.mode.set_full_frame() 

502 scan_mode = selected_beam.scanning.mode.value 

503 if not scan_mode == tbt.ScanMode.FULL_FRAME.value: 503 ↛ 504line 503 didn't jump to line 504 because the condition on line 503 was never true

504 raise SystemError( 

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

506 ) 

507 return True 

508 

509 

510def beam_scan_resolution( 

511 beam: tbt.Beam, 

512 microscope: tbt.Microscope, 

513 resolution: tbt.Resolution, 

514 delay_s: float = 0.1, 

515) -> bool: 

516 """ 

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

518 

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

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

521 

522 ## Parameters 

523 

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

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

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

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

528 

529 ## Returns 

530 

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

532 

533 ## Raises 

534 

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

536 

537 ## Examples 

538 

539 >>> import pytribeam.types as tbt 

540 >>> import pytribeam.utility as ut 

541 >>> microscope = tbt.Microscope() 

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

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

544 Client connected to [localhost:7520] 

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

546 >>> resolution = tbt.PresetResolution.HIGH 

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

548 >>> print(success) 

549 True""" 

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

551 raise ValueError( 

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

553 ) 

554 

555 selected_beam = ut.beam_type(beam, microscope) 

556 selected_beam.scanning.resolution.value = resolution.value 

557 time.sleep(delay_s) 

558 if selected_beam.scanning.resolution.value != resolution.value: 558 ↛ 559line 558 didn't jump to line 559 because the condition on line 558 was never true

559 raise ValueError( 

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

561 requested {resolution}, current resolution is 

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

563 ) 

564 

565 return True 

566 

567 

568def beam_scan_rotation( 

569 beam: tbt.Beam, 

570 microscope: tbt.Microscope, 

571 rotation_deg: float, 

572 delay_s: float = 0.1, 

573) -> bool: 

574 """ 

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

576 

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

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

579 

580 ## Parameters 

581 

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

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

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

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

586 

587 ## Returns 

588 

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

590 

591 ## Raises 

592 

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

594 

595 ## Examples 

596 

597 >>> import pytribeam.types as tbt 

598 >>> microscope = tbt.Microscope() 

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

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

601 Client connected to [localhost:7520] 

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

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

604 >>> print(success) 

605 True""" 

606 selected_beam = ut.beam_type(beam, microscope) 

607 rotation_rad = rotation_deg * cs.Conversions.DEG_TO_RAD 

608 selected_beam.scanning.rotation.value = rotation_rad 

609 time.sleep(delay_s) 

610 if selected_beam.scanning.rotation.value != rotation_rad: 610 ↛ 611line 610 didn't jump to line 611 because the condition on line 610 was never true

611 raise ValueError( 

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

613 requested {rotation_rad} radians, current scan rotation is 

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

615 ) 

616 

617 return True 

618 

619 

620def beam_voltage( 

621 beam: tbt.Beam, 

622 microscope: tbt.Microscope, 

623 voltage_kv: float, 

624 voltage_tol_kv: float, 

625 delay_s: float = 5.0, 

626) -> bool: 

627 """ 

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

629 

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

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

632 

633 ## Parameters 

634 

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

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

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

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

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

640 

641 ## Returns 

642 

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

644 

645 ## Raises 

646 

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

648 

649 ## Examples 

650 

651 >>> import pytribeam.types as tbt 

652 >>> microscope = tbt.Microscope() 

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

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

655 Client connected to [localhost:7520] 

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

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

658 >>> print(success) 

659 True""" 

660 selected_beam = ut.beam_type(beam, microscope) 

661 

662 exisiting_voltage_v = selected_beam.high_voltage.value # volts 

663 delta_voltage_kv = abs( 

664 exisiting_voltage_v * cs.Conversions.V_TO_KV - voltage_kv 

665 ) # kilovolts 

666 if delta_voltage_kv > voltage_tol_kv: 

667 warnings.warn( 

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

669 ) 

670 beam_controllable = selected_beam.high_voltage.is_controllable 

671 

672 if not beam_controllable: 672 ↛ 673line 672 didn't jump to line 673 because the condition on line 672 was never true

673 raise ValueError( 

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

675 ) 

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

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

678 time.sleep(delay_s) 

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

680 voltage_diff_kv = abs(new_voltage_kv - voltage_kv) 

681 if voltage_diff_kv > voltage_tol_kv: 681 ↛ 682line 681 didn't jump to line 682 because the condition on line 681 was never true

682 raise ValueError( 

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

684 requested {voltage_kv} kV, current beam voltage is 

685 {new_voltage_kv} kV""" 

686 ) 

687 

688 return True 

689 

690 

691def beam_working_distance( 

692 beam: tbt.Beam, 

693 microscope: tbt.Microscope, 

694 wd_mm: float, 

695 delay_s: float = 0.1, 

696) -> bool: 

697 """ 

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

699 

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

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

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

703 

704 ## Parameters 

705 

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

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

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

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

710 

711 ## Returns 

712 

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

714 

715 ## Raises 

716 

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

718 

719 ## Examples 

720 

721 >>> import pytribeam.types as tbt 

722 >>> microscope = tbt.Microscope() 

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

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

725 Client connected to [localhost:7520] 

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

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

728 >>> print(success) 

729 True""" 

730 selected_beam = ut.beam_type(beam, microscope) 

731 wd_m = wd_mm * cs.Conversions.MM_TO_M 

732 selected_beam.working_distance.value = wd_m 

733 time.sleep(delay_s) 

734 if selected_beam.working_distance.value != wd_m: 734 ↛ 735line 734 didn't jump to line 735 because the condition on line 734 was never true

735 raise ValueError( 

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

737 requested {wd_m} meters, current working distance is 

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

739 ) 

740 

741 return True 

742 

743 

744def detector_auto_cb( 

745 microscope: tbt.Microscope, 

746 beam: tbt.Beam, 

747 settings: tbt.ScanArea, 

748 delay_s: float = 0.1, 

749) -> bool: 

750 """ 

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

752 

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

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

755 

756 ## Parameters 

757 

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

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

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

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

762 

763 ## Returns 

764 

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

766 

767 ## Raises 

768 

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

770 

771 ## Examples 

772 

773 >>> import pytribeam.types as tbt 

774 >>> microscope = tbt.Microscope() 

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

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

777 Client connected to [localhost:7520] 

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

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

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

781 >>> print(success) 

782 True""" 

783 selected_beam = ut.beam_type(beam, microscope) 

784 selected_beam.scanning.mode.set_reduced_area( 

785 left=settings.left, 

786 top=settings.top, 

787 width=settings.width, 

788 height=settings.height, 

789 ) 

790 scan_mode = selected_beam.scanning.mode.value 

791 if not scan_mode == tbt.ScanMode.REDUCED_AREA.value: 791 ↛ 792line 791 didn't jump to line 792 because the condition on line 791 was never true

792 raise SystemError( 

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

794 ) 

795 microscope.auto_functions.run_auto_cb() 

796 time.sleep(delay_s) 

797 selected_beam.scanning.mode.set_full_frame() 

798 time.sleep(delay_s) 

799 

800 new_scan_mode = selected_beam.scanning.mode.value 

801 if new_scan_mode != tbt.ScanMode.FULL_FRAME.value: 801 ↛ 802line 801 didn't jump to line 802 because the condition on line 801 was never true

802 raise SystemError( 

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

804 ) 

805 return True 

806 

807 

808def detector_brightness( 

809 microscope: tbt.Microscope, 

810 brightness: float, 

811 delay_s: float = 0.1, 

812) -> bool: 

813 """ 

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

815 

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

817 

818 ## Parameters 

819 

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

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

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

823 

824 ## Returns 

825 

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

827 

828 ## Raises 

829 

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

831 

832 ## Examples 

833 

834 >>> import pytribeam.types as tbt 

835 >>> microscope = tbt.Microscope() 

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

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

838 Client connected to [localhost:7520] 

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

840 >>> print(success) 

841 True""" 

842 microscope.detector.brightness.value = brightness 

843 current_detector = microscope.detector.type.value 

844 time.sleep(delay_s) 

845 if not math.isclose( 

846 microscope.detector.brightness.value, 

847 brightness, 

848 abs_tol=cs.Constants.contrast_brightness_tolerance, 

849 ): 

850 raise ValueError( 

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

852 requested {brightness} on {current_detector} detector,  

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

854 ) 

855 return True 

856 

857 

858def detector_contrast( 

859 microscope: tbt.Microscope, 

860 contrast: float, 

861 delay_s: float = 0.1, 

862) -> bool: 

863 """ 

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

865 

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

867 

868 ## Parameters 

869 

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

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

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

873 

874 ## Returns 

875 

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

877 

878 ## Raises 

879 

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

881 

882 ## Examples 

883 

884 >>> import pytribeam.types as tbt 

885 >>> microscope = tbt.Microscope() 

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

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

888 Client connected to [localhost:7520] 

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

890 >>> print(success) 

891 True""" 

892 microscope.detector.contrast.value = contrast 

893 current_detector = microscope.detector.type.value 

894 time.sleep(delay_s) 

895 if not math.isclose( 

896 microscope.detector.contrast.value, 

897 contrast, 

898 abs_tol=cs.Constants.contrast_brightness_tolerance, 

899 ): 

900 raise ValueError( 

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

902 requested {contrast} on {current_detector} detector,  

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

904 ) 

905 return True 

906 

907 

908def detector_cb( 

909 microscope: tbt.Microscope, 

910 detector_settings: tbt.Detector, 

911 beam: tbt.Beam, 

912) -> bool: 

913 """ 

914 Sets detector contrast and brightness. 

915 

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

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

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

919 

920 ## Parameters 

921 

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

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

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

925 

926 ## Returns 

927 

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

929 

930 ## Examples 

931 

932 >>> import pytribeam.types as tbt 

933 >>> microscope = tbt.Microscope() 

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

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

936 Client connected to [localhost:7520] 

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

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

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

940 >>> print(success) 

941 True 

942 

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

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

945 >>> print(success) 

946 True 

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

948 >>> print(beam_brightness) 

949 0.3 

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

951 >>> print(beam_contrast) 

952 0.2""" 

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

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

955 contrast = detector_settings.contrast 

956 brightness = detector_settings.brightness 

957 if contrast is not None: 

958 detector_contrast(microscope=microscope, contrast=contrast) 

959 if brightness is not None: 

960 detector_brightness(microscope=microscope, brightness=brightness) 

961 

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

963 if not null_scan == detector_settings.auto_cb_settings: 

964 detector_auto_cb( 

965 microscope=microscope, 

966 settings=detector_settings.auto_cb_settings, 

967 beam=beam, 

968 ) 

969 return True 

970 

971 

972def detector_mode( 

973 microscope: tbt.Microscope, 

974 detector_mode: tbt.DetectorMode, 

975 delay_s: float = 0.1, 

976) -> bool: 

977 """ 

978 Sets the detector mode. 

979 

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

981 

982 ## Parameters 

983 

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

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

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

987 

988 ## Returns 

989 

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

991 

992 ## Raises 

993 

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

995 

996 ## Examples 

997 

998 >>> import pytribeam.types as tbt 

999 >>> microscope = tbt.Microscope() 

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

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

1002 Client connected to [localhost:7520] 

1003 >>> mode = tbt.DetectorMode.SECONDARY_ELECTRONS 

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

1005 >>> print(success) 

1006 True""" 

1007 microscope.detector.mode.value = detector_mode.value 

1008 time.sleep(delay_s) 

1009 if microscope.detector.mode.value != detector_mode.value: 1009 ↛ 1010line 1009 didn't jump to line 1010 because the condition on line 1009 was never true

1010 raise ValueError( 

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

1012 requested {detector_mode}, current mode is 

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

1014 ) 

1015 return True 

1016 

1017 

1018def detector_type( 

1019 microscope: tbt.Microscope, 

1020 detector: tbt.DetectorType, 

1021 delay_s: float = 0.1, 

1022) -> bool: 

1023 """ 

1024 Sets the detector type. 

1025 

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

1027 

1028 ## Parameters 

1029 

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

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

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

1033 

1034 ## Returns 

1035 

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

1037 

1038 ## Raises 

1039 

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

1041 

1042 ## Examples 

1043 

1044 >>> import pytribeam.types as tbt 

1045 >>> microscope = tbt.Microscope() 

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

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

1048 Client connected to [localhost:7520] 

1049 >>> detector = tbt.DetectorType.ETD 

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

1051 >>> print(success) 

1052 True""" 

1053 microscope.detector.type.value = detector.value 

1054 time.sleep(delay_s) 

1055 if microscope.detector.type.value != detector.value: 1055 ↛ 1056line 1055 didn't jump to line 1056 because the condition on line 1055 was never true

1056 raise ValueError( 

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

1058 requested {detector}, current detector is 

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

1060 ) 

1061 return True 

1062 

1063 

1064def grab_custom_resolution_frame( 

1065 img_settings: tbt.ImageSettings, 

1066 save_path: Path, 

1067) -> bool: 

1068 """ 

1069 Method for single frame imaging used with custom resolutions. 

1070 

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

1072 

1073 ## Parameters 

1074 

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

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

1077 

1078 ## Returns 

1079 

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

1081 

1082 ## Examples 

1083 

1084 >>> import pytribeam.types as tbt 

1085 >>> from pathlib import Path 

1086 >>> microscope = tbt.Microscope() 

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

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

1089 Client connected to [localhost:7520] 

1090 >>> img_settings = tbt.ImageSettings( 

1091 ... microscope=microscope, 

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

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

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

1095 ... bit_depth=tbt.ColorDepth.BITS_8, 

1096 ... ) 

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

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

1099 >>> print(success) 

1100 True""" 

1101 microscope = img_settings.microscope 

1102 resolution = img_settings.scan.resolution 

1103 microscope.imaging.grab_frame_to_disk( 

1104 save_path.absolute().as_posix(), 

1105 file_format=tbt.ImageFileFormat.TIFF.value, 

1106 settings=tbt.GrabFrameSettings( 

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

1108 ), 

1109 ) 

1110 return True 

1111 

1112 

1113def grab_preset_resolution_frame( 

1114 img_settings: tbt.ImageSettings, 

1115) -> tbt.AdornedImage: 

1116 """ 

1117 Method for single frame imaging used with preset resolutions. 

1118 

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

1120 

1121 ## Parameters 

1122 

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

1124 

1125 ## Returns 

1126 

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

1128 

1129 ## Examples 

1130 

1131 >>> import pytribeam.types as tbt 

1132 >>> microscope = tbt.Microscope() 

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

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

1135 Client connected to [localhost:7520] 

1136 >>> img_settings = tbt.ImageSettings( 

1137 ... microscope=microscope, 

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

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

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

1141 ... bit_depth=tbt.ColorDepth.BITS_8, 

1142 ... ) 

1143 >>> image = grab_preset_resolution_frame(img_settings) 

1144 >>> print(image) 

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

1146 beam = img_settings.beam 

1147 microscope = img_settings.microscope 

1148 beam_scan_resolution( 

1149 beam=beam, 

1150 microscope=microscope, 

1151 resolution=img_settings.scan.resolution, 

1152 ) 

1153 return microscope.imaging.grab_frame( 

1154 tbt.GrabFrameSettings(bit_depth=img_settings.bit_depth) 

1155 ) 

1156 

1157 

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

1159 """ 

1160 Prepares the detector and inserts it if applicable. 

1161 

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

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

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

1165 

1166 ## Parameters 

1167 

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

1169 

1170 ## Returns 

1171 

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

1173 

1174 ## Examples 

1175 

1176 >>> import pytribeam.types as tbt 

1177 >>> microscope = tbt.Microscope() 

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

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

1180 Client connected to [localhost:7520] 

1181 >>> img_settings = tbt.ImageSettings( 

1182 ... microscope=microscope, 

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

1184 ... detector=tbt.Detector( 

1185 ... type=tbt.DetectorType.ETD, 

1186 ... mode=tbt.DetectorMode.SECONDARY_ELECTRONS, 

1187 ... brightness=0.4, 

1188 ... contrast=0.2, 

1189 ... ), 

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

1191 ... bit_depth=tbt.ColorDepth.BITS_8, 

1192 ... ) 

1193 >>> success = imaging_detector(img_settings) 

1194 >>> print(success) 

1195 True""" 

1196 microscope = img_settings.microscope 

1197 detector = img_settings.detector.type 

1198 detector_type( 

1199 microscope=microscope, 

1200 detector=detector, 

1201 ) 

1202 detector_state = devices.detector_state( 

1203 microscope=microscope, 

1204 detector=detector, 

1205 ) 

1206 if detector_state is not tbt.RetractableDeviceState.STATIONARY: 1206 ↛ 1207line 1206 didn't jump to line 1207 because the condition on line 1206 was never true

1207 devices.insert_detector( 

1208 microscope=microscope, 

1209 detector=detector, 

1210 ) 

1211 detector_mode( 

1212 microscope=microscope, 

1213 detector_mode=img_settings.detector.mode, 

1214 ) 

1215 detector_cb( 

1216 microscope=microscope, 

1217 detector_settings=img_settings.detector, 

1218 beam=img_settings.beam, 

1219 ) 

1220 return True 

1221 

1222 

1223def imaging_device( 

1224 microscope: tbt.Microscope, 

1225 beam: tbt.Beam, 

1226) -> bool: 

1227 """ 

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

1229 

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

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

1232 

1233 ## Parameters 

1234 

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

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

1237 

1238 ## Returns 

1239 

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

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

1242 beam_ready(beam=beam, microscope=microscope) 

1243 beam_voltage( 

1244 beam=beam, 

1245 microscope=microscope, 

1246 voltage_kv=beam.settings.voltage_kv, 

1247 voltage_tol_kv=beam.settings.voltage_tol_kv, 

1248 ) 

1249 beam_current( 

1250 beam=beam, 

1251 microscope=microscope, 

1252 current_na=beam.settings.current_na, 

1253 current_tol_na=beam.settings.current_tol_na, 

1254 ) 

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

1256 beam_angular_correction( 

1257 microscope=microscope, 

1258 dynamic_focus=beam.settings.dynamic_focus, 

1259 tilt_correction=beam.settings.tilt_correction, 

1260 ) 

1261 return True 

1262 

1263 

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

1265 """ 

1266 Sets all scan settings except for the resolution. 

1267 

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

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

1270 and dwell time. 

1271 

1272 ## Parameters 

1273 

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

1275 

1276 ## Returns 

1277 

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

1279 microscope = img_settings.microscope 

1280 beam = img_settings.beam 

1281 beam_scan_full_frame( 

1282 beam=beam, 

1283 microscope=microscope, 

1284 ) 

1285 beam_scan_rotation( 

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

1287 ) 

1288 beam_working_distance( 

1289 beam=beam, 

1290 microscope=microscope, 

1291 wd_mm=img_settings.beam.settings.working_dist_mm, 

1292 ) 

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

1294 beam_dwell_time( 

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

1296 ) 

1297 return True 

1298 

1299 

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

1301 """ 

1302 Prepares various imaging settings. 

1303 

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

1305 based on the specified image settings. 

1306 

1307 ## Parameters 

1308 

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

1310 

1311 ## Returns 

1312 

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

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

1315 imaging_scan(img_settings=img_settings) 

1316 imaging_detector(img_settings=img_settings) 

1317 return True 

1318 

1319 

1320def set_view( 

1321 microscope: tbt.Microscope, 

1322 quad: tbt.ViewQuad, 

1323) -> bool: 

1324 """ 

1325 Sets the active view to the specified quad. 

1326 

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

1328 

1329 ## Parameters 

1330 

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

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

1333 

1334 ## Returns 

1335 

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

1337 microscope.imaging.set_active_view(quad.value) 

1338 

1339 

1340def set_beam_device( 

1341 microscope: tbt.Microscope, 

1342 device: tbt.Device, 

1343 delay_s: float = 0.1, 

1344) -> bool: 

1345 """ 

1346 Sets the active imaging device. 

1347 

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

1349 

1350 ## Parameters 

1351 

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

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

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

1355 

1356 ## Returns 

1357 

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

1359 

1360 ## Raises 

1361 

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

1363 microscope.imaging.set_active_device(device) 

1364 time.sleep(delay_s) 

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

1366 if curr_device != device.value: 1366 ↛ 1367line 1366 didn't jump to line 1367 because the condition on line 1366 was never true

1367 raise ValueError( 

1368 f"""Could not set active device, 

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

1370 Device list: 

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

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

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

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

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

1376 ) 

1377 return True 

1378 

1379 

1380################# 

1381 

1382 

1383def collect_single_image( 

1384 save_path: Path, 

1385 img_settings: tbt.ImageSettings, 

1386) -> bool: 

1387 """ 

1388 Collects a single frame image with defined image settings. 

1389 

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

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

1392 will be saved at 8-bit color depth. 

1393 

1394 ## Parameters 

1395 

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

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

1398 

1399 ## Returns 

1400 

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

1402 beam = img_settings.beam 

1403 microscope = img_settings.microscope 

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

1405 prepare_imaging(img_settings=img_settings) 

1406 

1407 resolution = img_settings.scan.resolution 

1408 if isinstance(resolution, tbt.PresetResolution): 

1409 img = grab_preset_resolution_frame(img_settings=img_settings) 

1410 img.save(str(save_path)) 

1411 return True 

1412 warnings.warn( 

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

1414 ) 

1415 if img_settings.bit_depth != tbt.ColorDepth.BITS_8: 1415 ↛ 1416line 1415 didn't jump to line 1416 because the condition on line 1415 was never true

1416 warnings.warn( 

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

1418 ) 

1419 grab_custom_resolution_frame( 

1420 img_settings=img_settings, 

1421 save_path=save_path, 

1422 ) 

1423 return True 

1424 

1425 

1426def collect_multiple_images( 

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

1428) -> List[tbt.AdornedImage]: 

1429 """ 

1430 Sets up scanning for multiple frames. 

1431 

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

1433 It is limited to preset resolutions only. 

1434 

1435 ## Parameters 

1436 

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

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

1439 

1440 ## Returns 

1441 

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

1443 

1444 ## Raises 

1445 

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

1447 

1448 ## Notes 

1449 

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

1451 # TODO test this 

1452 

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

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

1455 insertable_detector = [ 

1456 devices.detector_state( 

1457 microscope=_set.microscope, 

1458 detector=_set.detector, 

1459 ) 

1460 is not tbt.RetractableDeviceState.STATIONARY 

1461 for _set in multiple_img_settings 

1462 ] 

1463 if sum(insertable_detector) > 1: 

1464 raise NotImplementedError( 

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

1466 ) 

1467 elif sum(insertable_detector) == 1: 

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

1469 multiple_img_settings = [start] + multiple_img_settings 

1470 

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

1472 views = [] 

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

1474 img_settings = multiple_img_settings[quad - 1] 

1475 resolution = img_settings.scan.resolution 

1476 if not isinstance(resolution, tbt.PresetResolution): 1476 ↛ 1481line 1476 didn't jump to line 1481 because the condition on line 1476 was always true

1477 raise ValueError( 

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

1479 ) 

1480 

1481 microscope = img_settings.microscope 

1482 beam = img_settings.beam 

1483 views.append(quad) 

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

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

1486 frames = microscope.imaging.grab_multiple_frames( 

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

1488 ) 

1489 return frames 

1490 

1491 

1492# TODO 

1493# def image_method(dict): 

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

1495# pass 

1496 

1497# TODO 

1498# def collect_tiling_image(): 

1499# for loop 

1500# collect_standard_image 

1501# #stage move 

1502 

1503 

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

1505def image_operation( 

1506 step: tbt.Step, 

1507 image_settings: tbt.ImageSettings, 

1508 general_settings: tbt.GeneralSettings, 

1509 slice_number: int, 

1510) -> bool: 

1511 """ 

1512 Performs an image operation based on the specified settings. 

1513 

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

1515 

1516 ## Parameters 

1517 

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

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

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

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

1522 

1523 ## Returns 

1524 

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

1526 print("\tCollecting image") 

1527 # create folder in same directory as experimental directory 

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

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

1530 

1531 # TODO 

1532 # determine_image_method() 

1533 # collect_multiple_images() 

1534 

1535 # single image process: 

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

1537 collect_single_image(save_path=save_path, img_settings=image_settings) 

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

1539 

1540 # turn off tilt correction and dynamic focus 

1541 beam_angular_correction( 

1542 microscope=image_settings.microscope, 

1543 dynamic_focus=False, 

1544 tilt_correction=False, 

1545 ) 

1546 

1547 return True