Coverage for src/pytribeam/fib.py: 0%

111 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2026-06-16 18:30 +0000

1#!/usr/bin/python3 

2""" 

3FIB Module 

4========== 

5 

6This module contains functions for performing various operations related to Focused Ion Beam (FIB) milling, including preparing the microscope for milling, creating patterns, and performing milling operations. 

7 

8Functions 

9--------- 

10application_files(microscope: tbt.Microscope) -> List[str] 

11 Get the list of application files from the current microscope. 

12 

13shutter_control(microscope: tbt.Microscope) -> None 

14 Ensure auto control is set on the e-beam shutter. Manual control is not currently offered. 

15 

16prepare_milling(microscope: tbt.Microscope, application: str, patterning_device: tbt.Device = tbt.Device.ION_BEAM) -> bool 

17 Clear old patterns, assign patterning to ion beam by default, and load the application. 

18 

19create_pattern(geometry, microscope: tbt.Microscope, **kwargs: dict) -> bool 

20 Create a pattern on the microscope based on the provided geometry. 

21 

22create_pattern(geometry: tbt.FIBRectanglePattern, microscope: tbt.Microscope, **kwargs: dict) -> tbt.as_dynamics.RectanglePattern 

23 Create a rectangle pattern on the microscope. 

24 

25create_pattern(geometry: tbt.FIBRegularCrossSection, microscope: tbt.Microscope, **kwargs: dict) -> tbt.as_dynamics.RegularCrossSectionPattern 

26 Create a regular cross-section pattern on the microscope. 

27 

28create_pattern(geometry: tbt.FIBCleaningCrossSection, microscope: tbt.Microscope, **kwargs: dict) -> tbt.as_dynamics.CleaningCrossSectionPattern 

29 Create a cleaning cross-section pattern on the microscope. 

30 

31create_pattern(geometry: tbt.FIBStreamPattern, microscope: tbt.Microscope, **kwargs: dict) -> tbt.StreamPattern 

32 Create a stream pattern on the microscope. 

33 

34image_processing(geometry: tbt.FIBStreamPattern, input_image_path: Path) -> bool 

35 Perform image processing for FIB stream pattern. 

36 

37mill_operation(step: tbt.Step, fib_settings: tbt.FIBSettings, general_settings: tbt.GeneralSettings, slice_number: int) -> bool 

38 Perform a milling operation based on the provided step and settings. 

39""" 

40 

41# Default python modules 

42from functools import singledispatch 

43from pathlib import Path 

44import warnings 

45from typing import List 

46from functools import singledispatch 

47import subprocess 

48 

49# Autoscript included modules 

50from PIL import Image as pil_img 

51import cv2 

52import numpy as np 

53 

54# 3rd party module 

55 

56# Local scripts 

57import pytribeam.constants as cs 

58from pytribeam.constants import Conversions 

59import pytribeam.types as tbt 

60import pytribeam.image as img 

61 

62 

63def application_files(microscope: tbt.Microscope) -> List[str]: 

64 """ 

65 Get the list of application files from the current microscope. 

66 

67 This function retrieves the list of application files available on the current microscope, removes any "None" entries, and sorts the list. 

68 

69 Parameters 

70 ---------- 

71 microscope : tbt.Microscope 

72 The microscope object from which to retrieve the application files. 

73 

74 Returns 

75 ------- 

76 List[str] 

77 A sorted list of application files available on the microscope. 

78 """ 

79 apps = microscope.patterning.list_all_application_files() 

80 

81 # Remove "None" entry 

82 while "None" in apps: 

83 apps.remove("None") 

84 apps.sort(key=str.casefold) 

85 

86 return apps 

87 

88 

89def shutter_control(microscope: tbt.Microscope) -> None: 

90 """ 

91 Ensure auto control is set on the e-beam shutter. Manual control is not currently offered. 

92 

93 This function checks if the e-beam protective shutter is installed and sets its mode to automatic if it is not already set. If the shutter cannot be set to automatic mode, a SystemError is raised. 

94 

95 Parameters 

96 ---------- 

97 microscope : tbt.Microscope 

98 The microscope object for which to control the e-beam shutter. 

99 

100 Raises 

101 ------ 

102 SystemError 

103 If the e-beam shutter is installed but cannot be set to automatic mode. 

104 

105 Warnings 

106 -------- 

107 UserWarning 

108 If the e-beam shutter is not installed or if it is set to automatic mode. 

109 """ 

110 shutter = microscope.beams.electron_beam.protective_shutter 

111 if not shutter.is_installed: 

112 warnings.warn("Protective E-beam shutter not installed on this system.") 

113 return 

114 status = shutter.mode.value 

115 if status != tbt.ProtectiveShutterMode.AUTOMATIC: 

116 shutter.mode.value = tbt.ProtectiveShutterMode.AUTOMATIC 

117 new_status = shutter.mode.value 

118 if new_status != tbt.ProtectiveShutterMode.AUTOMATIC: 

119 raise SystemError( 

120 "E-beam shutter for FIB milling is installed but cannot set control to 'Automatic' mode." 

121 ) 

122 warnings.warn( 

123 "E-beam shutter for FIB milling operations is in auto-mode, which may not insert at certain tilt angles and stage positions. Manual control not available." 

124 ) 

125 return 

126 

127 

128def prepare_milling( 

129 microscope: tbt.Microscope, 

130 application: str, 

131 patterning_device: tbt.Device = tbt.Device.ION_BEAM, 

132) -> bool: 

133 # TODO validation and error checking from TFS 

134 # TODO support e-beam patterning via the mill_beam settings 

135 """ 

136 Clear old patterns, assign patterning to ion beam by default, and load the application. 

137 

138 This function clears old patterns, assigns the patterning device to the specified beam (ion beam by default), and loads the specified application. It validates the patterning device and application file. 

139 

140 Parameters 

141 ---------- 

142 microscope : tbt.Microscope 

143 The microscope object for which to prepare milling. 

144 application : str 

145 The name of the application file to load. 

146 patterning_device : tbt.Device, optional 

147 The device to use for patterning (default is tbt.Device.ION_BEAM). 

148 

149 Returns 

150 ------- 

151 bool 

152 True if the preparation is successful. 

153 

154 Raises 

155 ------ 

156 ValueError 

157 If the patterning device is invalid or if the application file is not found on the system. 

158 """ 

159 valid_devices = [tbt.Device.ELECTRON_BEAM, tbt.Device.ION_BEAM] 

160 if patterning_device not in valid_devices: 

161 raise ValueError( 

162 f"Invalid patterning device of '{patterning_device}' requested, only '{[i for i in valid_devices]}' are valid." 

163 ) 

164 microscope.patterning.clear_patterns() 

165 microscope.patterning.set_default_beam_type(beam_index=patterning_device.value) 

166 if application not in microscope.patterning.list_all_application_files(): 

167 raise ValueError( 

168 f"Invalid application file on this system, there is no patterning application with name: '{application}'." 

169 ) 

170 else: 

171 microscope.patterning.set_default_application_file(application) 

172 

173 return True 

174 

175 

176@singledispatch 

177def create_pattern( 

178 geometry, 

179 microscope: tbt.Microscope, 

180 **kwargs: dict, 

181) -> bool: # FIBRectanglePattern 

182 """ 

183 Create a pattern on the microscope based on the provided geometry. 

184 

185 This function creates a pattern on the microscope based on the provided geometry. It is a generic function that raises a NotImplementedError if no handler is available for the provided geometry type. 

186 

187 Parameters 

188 ---------- 

189 geometry : Any 

190 The geometry of the pattern to create. 

191 microscope : tbt.Microscope 

192 The microscope object on which to create the pattern. 

193 kwargs : dict 

194 Additional keyword arguments. 

195 

196 Returns 

197 ------- 

198 bool 

199 True if the pattern is created successfully. 

200 

201 Raises 

202 ------ 

203 NotImplementedError 

204 If no handler is available for the provided geometry type. 

205 """ 

206 _ = geometry 

207 __ = microscope 

208 ___ = kwargs 

209 raise NotImplementedError(f"No handler for type {type(geometry)}") 

210 

211 

212@create_pattern.register 

213def _( 

214 geometry: tbt.FIBRectanglePattern, 

215 microscope: tbt.Microscope, 

216 **kwargs: dict, 

217) -> tbt.as_dynamics.RectanglePattern: 

218 """ 

219 Create a rectangle pattern on the microscope. 

220 

221 Parameters 

222 ---------- 

223 geometry : tbt.FIBRectanglePattern 

224 The geometry of the rectangle pattern to create. 

225 microscope : tbt.Microscope 

226 The microscope object on which to create the pattern. 

227 kwargs : dict 

228 Additional keyword arguments. 

229 

230 Returns 

231 ------- 

232 tbt.as_dynamics.RectanglePattern 

233 The created rectangle pattern. 

234 """ 

235 pattern = microscope.patterning.create_rectangle( 

236 center_x=geometry.center_um.x * Conversions.UM_TO_M, 

237 center_y=geometry.center_um.y * Conversions.UM_TO_M, 

238 width=geometry.width_um * Conversions.UM_TO_M, 

239 height=geometry.height_um * Conversions.UM_TO_M, 

240 depth=geometry.depth_um * Conversions.UM_TO_M, 

241 ) 

242 pattern.scan_direction = geometry.scan_direction.value 

243 pattern.scan_type = geometry.scan_type.value 

244 

245 return pattern 

246 

247 

248@create_pattern.register 

249def _( 

250 geometry: tbt.FIBRegularCrossSection, 

251 microscope: tbt.Microscope, 

252 **kwargs: dict, 

253) -> tbt.as_dynamics.RegularCrossSectionPattern: 

254 """ 

255 Create a regular cross-section pattern on the microscope. 

256 

257 Parameters 

258 ---------- 

259 geometry : tbt.FIBRegularCrossSection 

260 The geometry of the regular cross-section pattern to create. 

261 microscope : tbt.Microscope 

262 The microscope object on which to create the pattern. 

263 kwargs : dict 

264 Additional keyword arguments. 

265 

266 Returns 

267 ------- 

268 tbt.as_dynamics.RegularCrossSectionPattern 

269 The created regular cross-section pattern. 

270 """ 

271 pattern = microscope.patterning.create_regular_cross_section( 

272 center_x=geometry.center_um.x * Conversions.UM_TO_M, 

273 center_y=geometry.center_um.y * Conversions.UM_TO_M, 

274 width=geometry.width_um * Conversions.UM_TO_M, 

275 height=geometry.height_um * Conversions.UM_TO_M, 

276 depth=geometry.depth_um * Conversions.UM_TO_M, 

277 ) 

278 pattern.scan_direction = geometry.scan_direction.value 

279 pattern.scan_type = geometry.scan_type.value 

280 

281 return pattern 

282 

283 

284@create_pattern.register 

285def _( 

286 geometry: tbt.FIBCleaningCrossSection, 

287 microscope: tbt.Microscope, 

288 **kwargs: dict, 

289) -> tbt.as_dynamics.CleaningCrossSectionPattern: 

290 """ 

291 Create a cleaning cross-section pattern on the microscope. 

292 

293 Parameters 

294 ---------- 

295 geometry : tbt.FIBCleaningCrossSection 

296 The geometry of the cleaning cross-section pattern to create. 

297 microscope : tbt.Microscope 

298 The microscope object on which to create the pattern. 

299 kwargs : dict 

300 Additional keyword arguments. 

301 

302 Returns 

303 ------- 

304 tbt.as_dynamics.CleaningCrossSectionPattern 

305 The created cleaning cross-section pattern. 

306 """ 

307 pattern = microscope.patterning.create_cleaning_cross_section( 

308 center_x=geometry.center_um.x * Conversions.UM_TO_M, 

309 center_y=geometry.center_um.y * Conversions.UM_TO_M, 

310 width=geometry.width_um * Conversions.UM_TO_M, 

311 height=geometry.height_um * Conversions.UM_TO_M, 

312 depth=geometry.depth_um * Conversions.UM_TO_M, 

313 ) 

314 pattern.scan_direction = geometry.scan_direction.value 

315 pattern.scan_type = geometry.scan_type.value 

316 

317 return pattern 

318 

319 

320@create_pattern.register 

321def _( 

322 geometry: tbt.FIBStreamPattern, 

323 microscope: tbt.Microscope, 

324 **kwargs: dict, 

325) -> tbt.StreamPattern: 

326 """ 

327 Create a stream pattern on the microscope. 

328 

329 Parameters 

330 ---------- 

331 geometry : tbt.FIBStreamPattern 

332 The geometry of the stream pattern to create. 

333 microscope : tbt.Microscope 

334 The microscope object on which to create the pattern. 

335 kwargs : dict 

336 Additional keyword arguments. 

337 

338 Returns 

339 ------- 

340 tbt.StreamPattern 

341 The created stream pattern. 

342 """ 

343 

344 # run image_processing and check that mask file is created 

345 input_image_path = kwargs["kwargs"]["input_image_path"] 

346 image_processing( 

347 geometry=geometry, 

348 input_image_path=input_image_path, 

349 ) 

350 

351 # get mask 

352 mask_path = geometry.mask_file 

353 with pil_img.open(mask_path) as mask_img: 

354 width, height = mask_img.size 

355 mask = np.asarray(mask_img).astype(int) 

356 

357 stream_def = tbt.StreamPatternDefinition() 

358 stream_def.bit_depth = tbt.StreamDepth.BITS_16 # only supported bit depth now 

359 dwell_time = geometry.dwell_us * Conversions.US_TO_S 

360 

361 scale_factor = cs.Constants.stream_pattern_scale / width 

362 point_img = cv2.resize( 

363 mask, 

364 dsize=(int(width * scale_factor), int(height * scale_factor)), 

365 interpolation=cv2.INTER_NEAREST, 

366 ) 

367 idx = np.where(point_img == 1) 

368 num_points = ( 

369 len(idx[0]) + 2 

370 ) # top right and bottom left corners each have a point to make sure pattern is centered 

371 

372 # stream pattern is defined by 4 values for each point 

373 # [x, y, dwell_time, flags] 

374 # flags = 1 --> blank beam 

375 # flags = 0 --> use beam 

376 stream_def.points = np.zeros(shape=(num_points, 4), dtype=object) 

377 stream_def.points[0] = [ 

378 1, # x (top left) 

379 cs.Constants.stream_pattern_y_shift, # y (top left) 

380 dwell_time, # dwell 

381 1, # flag (1 means blank the beam) 

382 ] 

383 flags = 0 

384 for i in range(1, num_points - 1): # start at first point 

385 x = idx[1][i - 1] * 16 + 1 

386 y = ( 

387 idx[0][i - 1] * 16 + cs.Constants.stream_pattern_y_shift 

388 ) # + (0.17 * (2**stream_def.bit_depth)) 

389 stream_def.points[i] = [x, y, dwell_time, flags] 

390 stream_def.points[-1] = [ 

391 2**stream_def.bit_depth, # x (bottom right) 

392 2**stream_def.bit_depth 

393 - cs.Constants.stream_pattern_y_shift, # y (bottom right) 

394 dwell_time, # dwell 

395 1, # flag 

396 ] 

397 

398 stream_def.repeat_count = geometry.repeats 

399 

400 stream_pattern = microscope.patterning.create_stream( 

401 center_x=0.0, 

402 center_y=0.0, 

403 stream_pattern_definition=stream_def, 

404 ) 

405 

406 return stream_pattern 

407 

408 

409def image_processing( 

410 geometry: tbt.FIBStreamPattern, 

411 input_image_path: Path, 

412) -> bool: 

413 """ 

414 Perform image processing for FIB stream pattern. 

415 

416 This function runs an image processing script specified by the `recipe_file` in the `geometry` object, using the input image path and outputting the mask file. 

417 

418 Parameters 

419 ---------- 

420 geometry : tbt.FIBStreamPattern 

421 The geometry of the FIB stream pattern, including the `recipe_file` and `mask_file`. 

422 input_image_path : Path 

423 The path to the input image. 

424 

425 Returns 

426 ------- 

427 bool 

428 True if the image processing is successful. 

429 

430 Raises 

431 ------ 

432 ValueError 

433 If the subprocess call for the script does not execute correctly or if the mask file is not created. 

434 """ 

435 output = subprocess.run( 

436 [ 

437 "python", 

438 (geometry.recipe_file).as_posix(), # recipe_file 

439 input_image_path.as_posix(), # input path, 

440 (geometry.mask_file).as_posix(), # outputpath, 

441 ], 

442 capture_output=True, 

443 ) 

444 if output.returncode != 0: 

445 raise ValueError( 

446 f"Subprocess call for script {geometry.recipe_file} using executable 'python' did not execute correctly." 

447 ) 

448 # check for mask file 

449 if not geometry.mask_file.exists(): 

450 raise ValueError( 

451 f"Mask file at location {geometry.mask_file} should have been created by image processing recipe but does not exist. Please check your recipe_file script." 

452 ) 

453 # TODO 

454 # save masks 

455 

456 return True 

457 

458 

459# TODO add more complex patterning behavior 

460def mill_operation( 

461 step: tbt.Step, 

462 fib_settings: tbt.FIBSettings, 

463 general_settings: tbt.GeneralSettings, 

464 slice_number: int, 

465) -> bool: 

466 """ 

467 Perform a milling operation based on the provided step and settings. 

468 

469 This function performs a milling operation using the specified step, FIB settings, general settings, and slice number. 

470 

471 Parameters 

472 ---------- 

473 step : tbt.Step 

474 The step object containing the operation settings. 

475 fib_settings : tbt.FIBSettings 

476 The FIB settings object containing the microscope and pattern settings. 

477 general_settings : tbt.GeneralSettings 

478 The general settings object. 

479 slice_number : int 

480 The slice number for the operation. 

481 

482 Returns 

483 ------- 

484 bool 

485 True if the milling operation is successful. 

486 

487 Raises 

488 ------ 

489 ValueError 

490 If the ion image for selected area milling is not found. 

491 """ 

492 microscope = fib_settings.microscope 

493 

494 shutter_control(microscope=microscope) 

495 # prepare beam 

496 img.imaging_device(microscope=microscope, beam=fib_settings.mill_beam) 

497 # set milling application and device 

498 

499 prepare_milling( 

500 microscope=microscope, 

501 application=fib_settings.pattern.application, 

502 ) 

503 

504 # get expected path of the fib image 

505 if fib_settings.pattern.type != tbt.FIBPatternType.SELECTED_AREA: 

506 input_image_path = None 

507 else: 

508 input_image_path = Path.join( 

509 general_settings.exp_dir, 

510 step.name, 

511 f"{slice_number:04}.tif", 

512 ) 

513 if not input_image_path.exists(): 

514 raise ValueError( 

515 f"Ion image for selected area milling was not found at '{input_image_path}'." 

516 ) 

517 

518 # make the pattern 

519 pattern = create_pattern( 

520 fib_settings.pattern.geometry, 

521 microscope=microscope, 

522 kwargs={"input_image_path": input_image_path}, 

523 ) 

524 

525 microscope.patterning.run() 

526 

527 return True