Coverage for src\pytribeam\insertable_devices.py: 88%

256 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2025-03-04 17:41 -0800

1#!/usr/bin/python3 

2 

3# Default python modules 

4# from functools import singledispatch 

5import os 

6from pathlib import Path 

7import time 

8import warnings 

9import math 

10from typing import NamedTuple, List, Tuple 

11from types import ModuleType 

12 

13# Autoscript included modules 

14import numpy as np 

15from matplotlib import pyplot as plt 

16 

17# 3rd party module 

18 

19# Local scripts 

20import pytribeam.utilities as ut 

21import pytribeam.constants as cs 

22from pytribeam.constants import Constants 

23import pytribeam.image as img 

24 

25try: 

26 from pytribeam.laser import tfs_laser as external 

27except: 

28 pass 

29import pytribeam.types as tbt 

30 

31 

32def detector_insertable( 

33 microscope: tbt.Microscope, 

34 detector: tbt.DetectorType, 

35) -> bool: 

36 """Determines whether or not the built-in microscope detector is insertable and returns it state""" 

37 # check if the detector is being read by Autoscript 

38 try: 

39 # make requested detector the active detector 

40 microscope.detector.type.value = detector.value 

41 except: 

42 warnings.warn( 

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

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

45 Detector will be assumed to not be insertable.""" 

46 ) 

47 return False 

48 

49 # check if it is insertable 

50 try: 

51 microscope.detector.state 

52 return True 

53 except Exception: 

54 return False 

55 

56 

57def detector_state( 

58 microscope: tbt.Microscope, 

59 detector: tbt.DetectorType, 

60) -> tbt.RetractableDeviceState: 

61 """Determine state of detector, only valid if detector is insertable""" 

62 # check if the detector is being read by Autoscriptdevice_access(microscope) 

63 if not detector_insertable( 

64 microscope=microscope, 

65 detector=detector, 

66 ): 

67 return None 

68 return tbt.RetractableDeviceState(microscope.detector.state) 

69 

70 

71def detectors_will_collide( 

72 microscope: tbt.Microscope, 

73 detector_to_insert: tbt.DetectorType, 

74) -> bool: 

75 """Determines if collision may occur""" 

76 device_retracted = tbt.RetractableDeviceState.RETRACTED.value 

77 for detector_combo in Constants.detector_collisions: 

78 if detector_to_insert in detector_combo: 

79 for detector in detector_combo: 

80 if detector == detector_to_insert: 

81 continue 

82 if detector == tbt.DetectorType.EDS: 

83 if external.EDS_CameraStatus() != device_retracted: 

84 return True 

85 elif detector == tbt.DetectorType.EBSD: 

86 if external.EBSD_CameraStatus() != device_retracted: 

87 return True 

88 else: 

89 state = detector_state(microscope=microscope, detector=detector) 

90 if state.value != device_retracted: 

91 return True 

92 return False 

93 

94 

95def device_access(microscope: tbt.Microscope) -> tbt.ViewQuad: 

96 """Switches to upper-left quad and assign electron beam as active device, 

97 which is the only device with access to insertable devices like the CBS/ABS detector 

98 Other devices, like ion-beam, CCD or Nav-Cam, do not have CBS/ABS access 

99 """ 

100 img.set_view( 

101 microscope=microscope, 

102 quad=tbt.ViewQuad.UPPER_LEFT, 

103 ) 

104 img.set_beam_device( 

105 microscope=microscope, 

106 device=tbt.Device.ELECTRON_BEAM, 

107 ) 

108 return True 

109 

110 

111def insert_EBSD( 

112 microscope: tbt.Microscope, 

113) -> bool: 

114 connect_EBSD() 

115 if detectors_will_collide( 

116 microscope=microscope, 

117 detector_to_insert=tbt.DetectorType.EBSD, 

118 ): 

119 raise SystemError( 

120 f"""Error. Cannot insert EBSD which may collide with another detector. 

121 Disallowed detector combinations are: {Constants.detector_collisions}""" 

122 ) 

123 ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus()) 

124 map_status = tbt.MapStatus(external.EBSD_MappingStatus()) 

125 if ebsd_cam_status == tbt.RetractableDeviceState.ERROR: 

126 raise SystemError("Error, EDS Camera in error state, workflow stopped.") 

127 if map_status != tbt.MapStatus.IDLE: 

128 raise SystemError( 

129 f'Error, EBSD mapping not in "{tbt.MapStatus.IDLE.value}" state.' 

130 ) 

131 if ebsd_cam_status != tbt.RetractableDeviceState.INSERTED: 

132 print("\tInserting EBSD Camera...") 

133 # TODO change to constants 

134 minutes_to_wait = 3 

135 timeout = minutes_to_wait * 60 # seconds 

136 waittime = 4 # seconds 

137 CCD_view(microscope=microscope) 

138 # Oxford Inst requires 2 inserts 

139 while True: 

140 external.EBSD_InsertCamera() # inserted state 

141 if ( 

142 external.EBSD_CameraStatus() 

143 == tbt.RetractableDeviceState.INSERTED.value 

144 ): 

145 break 

146 time.sleep(waittime) 

147 timeout = timeout - waittime 

148 if timeout < 1: 

149 warnings.warn("Warning: EBSD insert timeout. Trying to continue...") 

150 break 

151 CCD_pause(microscope=microscope) 

152 

153 new_ebsd_cam_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus()) 

154 if new_ebsd_cam_status == tbt.RetractableDeviceState.INSERTED: 

155 print("\tEBSD Camera inserted") 

156 return True 

157 raise SystemError( 

158 f'EBSDS Camera is not inserted, currently in "{new_ebsd_cam_status}" state' 

159 ) 

160 

161 

162def insert_EDS( 

163 microscope: tbt.Microscope, 

164) -> bool: 

165 connect_EDS() 

166 if detectors_will_collide( 

167 microscope=microscope, 

168 detector_to_insert=tbt.DetectorType.EDS, 

169 ): 

170 raise SystemError( 

171 f"""Error. Cannot insert EDS while CBS not in "Retracted" state.  

172 CBS detector currently in "{detector_state(microscope=microscope, detector=tbt.DetectorType.CBS).value}" state.""" 

173 ) 

174 eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

175 map_status = tbt.MapStatus(external.EDS_MappingStatus()) 

176 if eds_cam_status == tbt.RetractableDeviceState.ERROR: 

177 raise SystemError("Error, EDS Camera in error state, workflow stopped.") 

178 if map_status != tbt.MapStatus.IDLE: 

179 raise SystemError( 

180 f'Error, EDS mapping not in "{tbt.MapStatus.IDLE.value}" state.' 

181 ) 

182 if eds_cam_status != tbt.RetractableDeviceState.INSERTED: 

183 print("\tInserting EDS Camera...") 

184 CCD_view(microscope=microscope) 

185 external.EDS_InsertCamera() 

186 CCD_pause(microscope=microscope) 

187 

188 new_eds_cam_status = tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

189 if new_eds_cam_status == tbt.RetractableDeviceState.INSERTED: 

190 print("\tEDS Camera inserted") 

191 return True 

192 raise SystemError( 

193 f'EDS Camera is not inserted, currently in "{new_eds_cam_status}" state' 

194 ) 

195 

196 

197def insert_detector( 

198 microscope: tbt.Microscope, 

199 detector: tbt.DetectorType, 

200 time_delay_s: float = 0.5, 

201) -> bool: 

202 """inserts the selected detector""" 

203 # ensure detector is the active one 

204 microscope.detector.type.value = detector.value 

205 # confirm detector is insertable 

206 try: 

207 state = microscope.detector.state 

208 except: 

209 raise ValueError(f"{detector.value} detector is not insertable.") 

210 if state == tbt.RetractableDeviceState.RETRACTED.value: 

211 if detectors_will_collide( 

212 microscope=microscope, 

213 detector_to_insert=detector, 

214 ): 

215 raise SystemError( 

216 f"""Error. Cannot insert {detector.value} which may collide with another detector. 

217 Disallowed detector combinations are: {Constants.detector_collisions}""" 

218 ) 

219 

220 print(f"\tInserting {detector.value} detector...") 

221 CCD_view(microscope=microscope) 

222 microscope.detector.insert() 

223 time.sleep(time_delay_s) 

224 CCD_pause(microscope=microscope) 

225 if microscope.detector.state == tbt.RetractableDeviceState.INSERTED.value: 

226 print(f"\t\t{detector.value} detector inserted.") 

227 return True 

228 elif state == tbt.RetractableDeviceState.INSERTED.value: 

229 print(f"\t{detector.value} detector is already inserted.") 

230 return True 

231 raise SystemError( 

232 f'Cannot insert {detector.value} detector, current detector state is "{state}".' 

233 ) 

234 

235 

236def retract_all_devices( 

237 microscope: tbt.Microscope, 

238 enable_EBSD: bool, 

239 enable_EDS: bool, 

240) -> bool: 

241 # TODO come up with better system for enable_EBSD_EDS 

242 """Retracts all insertable devices. First microscope detectors, 

243 then EBSD/EDS detectors if integrated. Must overwrite EBSD/EDS option to ignore 

244 these detectors 

245 

246 microscope: microscope object for accessing autoscript API 

247 """ 

248 print("\tRetracting devices, do not interact with xTUI during this process...") 

249 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view()) 

250 device_access(microscope) 

251 

252 for detector in microscope.detector.type.available_values: 

253 detector = tbt.DetectorType(detector) # overwrite 

254 state = detector_state( 

255 microscope=microscope, 

256 detector=detector, 

257 ) 

258 if (state is not None) and (state != tbt.RetractableDeviceState.RETRACTED): 

259 retract_device( 

260 microscope=microscope, 

261 detector=detector, 

262 ) 

263 

264 # EBSD/EDS detectors: 

265 try: 

266 external 

267 except NameError: 

268 pass 

269 print("\t\tLaser API not imported, EBSD and EDS detectors are unavailable") 

270 else: 

271 if enable_EBSD: 

272 retract_EBSD(microscope=microscope) 

273 if enable_EDS: 

274 retract_EDS(microscope=microscope) 

275 # else: 

276 # warnings.warn( 

277 # "\t\tWarning: EBSD and EDS device control API is available but not being used." 

278 # ) 

279 

280 # reset initial settings: 

281 img.set_view( 

282 microscope=microscope, 

283 quad=initial_view, 

284 ) 

285 print("\t\tAll available and enabled devices retracted.") 

286 return True 

287 

288 

289def connect_EBSD() -> tbt.RetractableDeviceState: 

290 try: 

291 status = external.EBSD_CameraStatus() 

292 except: 

293 raise ConnectionError( 

294 """EBSD control not connected, "Laser Control" from ThermoFisher must be open.  

295 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again.""" 

296 ) 

297 return tbt.RetractableDeviceState(status) 

298 

299 

300def retract_EBSD(microscope: tbt.Microscope) -> bool: 

301 # print(f"\t\tRetracting EBSD detector") 

302 connect_EBSD() 

303 ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus()) 

304 if ebsd_status == tbt.RetractableDeviceState.ERROR: 

305 raise SystemError( 

306 "EBSD Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control" 

307 ) 

308 if ebsd_status != tbt.RetractableDeviceState.RETRACTED: 

309 print( 

310 '\t\t\tEBSD Camera Retraction requested, please wait for "mapping complete" verification...' 

311 ) 

312 map_status = tbt.MapStatus(external.EBSD_MappingStatus()) 

313 # first check if mapping is finished properly 

314 minutes_to_wait = 5 # TODO set constant 

315 timeout = minutes_to_wait * 60 # seconds #TODO 

316 cameraokconfirmations = 3 # synchronization issue with EDAX, try to get map completed 3x before continuing 

317 waittime = 10 # seconds 

318 if map_status == tbt.MapStatus.ACTIVE: 

319 print("\t\t\tEBSD mapping currently active, waiting for mapping to finish") 

320 while True: 

321 current_map_status = tbt.MapStatus(external.EBSD_MappingStatus()) 

322 if current_map_status != tbt.MapStatus.ACTIVE: 

323 cameraokconfirmations = cameraokconfirmations - 1 

324 waittime = 3 # shorten wait time, polling 3x to see if mapping was really completed. 

325 time.sleep(waittime) 

326 timeout = timeout - waittime 

327 if cameraokconfirmations < 1: 

328 delay = minutes_to_wait * 60 - timeout 

329 print(f"\t\t\t\tEBSD mapping finished. Delay of {delay} seconds") 

330 break 

331 if timeout < 1: 

332 warnings.warn( 

333 "\t\t\tWarning, EBSD mapping timeout. Trying to continue..." 

334 ) 

335 break 

336 CCD_view(microscope=microscope) 

337 print("\t\t\tEBSD Camera retracting...") 

338 external.EBSD_RetractCamera() 

339 time.sleep(1) 

340 CCD_pause(microscope=microscope) 

341 current_ebsd_status = tbt.RetractableDeviceState(external.EBSD_CameraStatus()) 

342 if current_ebsd_status != tbt.RetractableDeviceState.RETRACTED: 

343 raise SystemError("Error, EBSD Camera retraction failed, workflow stopped.") 

344 print("\t\tEBSD Camera retracted") 

345 return True 

346 

347 

348def connect_EDS() -> tbt.RetractableDeviceState: 

349 try: 

350 status = external.EDS_CameraStatus() 

351 except: 

352 raise ConnectionError( 

353 """EDS control not connected, "Laser Control" from ThermoFisher must be open.  

354 Try closing Laser Control, restarting EBSD/EDS software, then opening Laser Control again.""" 

355 ) 

356 return tbt.RetractableDeviceState(status) 

357 

358 

359def retract_EDS(microscope: tbt.Microscope) -> bool: 

360 """Retract EDS dector""" 

361 # print(f"\t\tRetracting EDS detector") 

362 connect_EDS() 

363 eds_status = tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

364 if eds_status == tbt.RetractableDeviceState.ERROR: 

365 raise SystemError( 

366 "EDS Camera in error state, workflow stopped. Check EBSD/EDS software or restart laser control" 

367 ) 

368 if eds_status != tbt.RetractableDeviceState.RETRACTED: 

369 print("\t\t\tEDS Camera retracting...") 

370 CCD_view(microscope=microscope) 

371 external.EDS_RetractCamera() 

372 time.sleep(1) 

373 if ( 

374 tbt.RetractableDeviceState(external.EDS_CameraStatus()) 

375 != tbt.RetractableDeviceState.RETRACTED 

376 ): 

377 raise SystemError("Error, EDS Camera retraction failed, workflow stopped.") 

378 CCD_pause(microscope=microscope) 

379 print("\t\tEDS Camera retracted") 

380 return True 

381 

382 

383def retract_device(microscope: tbt.Microscope, detector: tbt.DetectorType) -> bool: 

384 CCD_view(microscope=microscope) 

385 print(f"\t\tRetracting {detector.value} detector") 

386 microscope.detector.type.value = detector.value 

387 microscope.detector.retract() 

388 state = tbt.RetractableDeviceState(microscope.detector.state) 

389 if state != tbt.RetractableDeviceState.RETRACTED: 

390 raise SystemError( 

391 f"{detector.value} detector not retracted, current detector state is {detector_state.value}" 

392 ) 

393 print(f"\t\t{detector.value} detector retracted") 

394 CCD_pause(microscope=microscope) 

395 

396 return True 

397 

398 

399def CCD_pause( 

400 microscope: tbt.Microscope, 

401 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT, 

402) -> bool: 

403 """Pauses CCD, typically used after device or stage movement""" 

404 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view()) 

405 img.set_view(microscope=microscope, quad=quad) 

406 try: 

407 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA) 

408 except: 

409 warnings.warn("CCD camera is not installed on this microscope.") 

410 else: 

411 microscope.imaging.stop_acquisition() 

412 finally: 

413 microscope.imaging.set_active_view(initial_view.value) 

414 return True 

415 

416 

417def CCD_view( 

418 microscope: tbt.Microscope, 

419 quad: tbt.ViewQuad = tbt.ViewQuad.LOWER_RIGHT, 

420) -> bool: 

421 """visualizes detector or stage movement for the user using the CCD camera""" 

422 initial_view = tbt.ViewQuad(microscope.imaging.get_active_view()) 

423 img.set_view(microscope=microscope, quad=quad) 

424 try: 

425 img.set_beam_device(microscope=microscope, device=tbt.Device.CCD_CAMERA) 

426 except: 

427 warnings.warn("CCD camera is not installed on this microscope.") 

428 else: 

429 microscope.imaging.start_acquisition() 

430 finally: 

431 microscope.imaging.set_active_view(initial_view.value) 

432 return True 

433 

434 

435def ebsd_operation( 

436 step: tbt.Step, 

437 general_settings: tbt.GeneralSettings, 

438 slice_number: int, 

439) -> bool: 

440 pass 

441 image_settings = step.operation_settings.image 

442 

443 

444def specimen_current( 

445 microscope: tbt.Microscope, 

446 hfw_mm=Constants.specimen_current_hfw_mm, 

447 delay_s=Constants.specimen_current_delay_s, 

448) -> float: 

449 """Measures specimen current using the electron beam, return in nA""" 

450 img.set_beam_device( 

451 microscope=microscope, 

452 device=tbt.Device.ELECTRON_BEAM, 

453 ) 

454 initial_hfw_m = microscope.beams.electron_beam.horizontal_field_width.value 

455 initial_detector = tbt.DetectorType(microscope.detector.type.value) 

456 

457 img.detector_type(microscope=microscope, detector=tbt.DetectorType.ETD) 

458 img.beam_hfw( 

459 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()), 

460 microscope=microscope, 

461 hfw_mm=hfw_mm, 

462 ) 

463 microscope.imaging.start_acquisition() 

464 time.sleep(delay_s) 

465 current_na = microscope.state.specimen_current.value * cs.Conversions.A_TO_NA 

466 microscope.imaging.stop_acquisition() 

467 

468 # reset detector and hfw 

469 microscope.beams.electron_beam.horizontal_field_width.value = initial_hfw_m 

470 img.detector_type(microscope=microscope, detector=initial_detector) 

471 img.beam_hfw( 

472 beam=tbt.ElectronBeam(settings=tbt.BeamSettings()), 

473 microscope=microscope, 

474 hfw_mm=initial_hfw_m * cs.Conversions.M_TO_MM, 

475 ) 

476 

477 return current_na