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

86 statements  

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

1#!/usr/bin/python3 

2""" 

3Log Module 

4========== 

5 

6This module contains functions for logging various experiment data, including creating log files, extracting YAML configurations, and logging experiment settings, positions, laser power, and specimen current. 

7 

8Functions 

9--------- 

10create_file(path: Path) -> bool 

11 Create a log file at the specified path. 

12 

13yml_from_log(log_path_h5: Path, output_path_yml: Path, row: int, config_field: str = "Config File") -> bool 

14 Extract YAML configuration from a log file and save it to an output path. 

15 

16experiment_settings(slice_number: int, step_number: int, log_filepath: Path, yml_path: Path) -> bool 

17 Log experiment settings to the log file. 

18 

19position(step_number: int, step_name: str, slice_number: int, log_filepath: Path, dataset_name: str, current_position: tbt.StagePositionUser) -> bool 

20 Log the current position to the log file. 

21 

22laser_power(step_number: int, step_name: str, slice_number: int, log_filepath: Path, dataset_name: str, power_w: float) -> bool 

23 Log the laser power to the log file. 

24 

25specimen_current(step_number: int, step_name: str, slice_number: int, log_filepath: Path, dataset_name: str, specimen_current_na: float) -> bool 

26 Log the specimen current to the log file. 

27 

28current_time() -> tbt.TimeStamp 

29 Get the current time as a timestamp. 

30""" 

31 

32# Default python modules 

33# from functools import singledispatch 

34from pathlib import Path 

35import datetime 

36 

37# Autoscript included modules 

38import numpy as np 

39import h5py 

40 

41# 3rd party module 

42 

43# Local scripts 

44from pytribeam.constants import Constants 

45import pytribeam.types as tbt 

46 

47 

48def create_file(path: Path) -> bool: 

49 """ 

50 Create a log file at the specified path. 

51 

52 This function creates a log file at the specified path if it does not already exist. 

53 

54 Parameters 

55 ---------- 

56 path : Path 

57 The path where the log file should be created. 

58 

59 Returns 

60 ------- 

61 bool 

62 True if the log file is created successfully. 

63 

64 Raises 

65 ------ 

66 ValueError 

67 If the log file cannot be created. 

68 """ 

69 if not path.is_file(): 

70 log = h5py.File(path, "w") 

71 log.close() 

72 if path.is_file(): 

73 print(f'Logfile created at "{path}".') 

74 if path.is_file(): 

75 return True 

76 raise ValueError(f'Unable to create log file at location "{path}".') 

77 

78 

79def current_time() -> tbt.TimeStamp: 

80 """ 

81 Get the current time as a timestamp. 

82 

83 This function returns the current time as a `TimeStamp` object, including both human-readable and UNIX time formats. 

84 

85 Returns 

86 ------- 

87 tbt.TimeStamp 

88 The current time as a `TimeStamp` object. 

89 """ 

90 now = datetime.datetime.now() 

91 human_readable = now.strftime("%m/%d/%Y %H:%M:%S") 

92 unix_time = int(now.timestamp()) 

93 time = tbt.TimeStamp(human_readable=human_readable, unix=unix_time) 

94 return time 

95 

96 

97def yml_from_log( 

98 log_path_h5: Path, 

99 output_path_yml: Path, 

100 row: int, 

101 config_field: str = "Config File", 

102) -> bool: 

103 """ 

104 Extract YAML configuration from a log file and save it to an output path. 

105 

106 This function extracts the YAML configuration from a specified row in the log file and saves it to the output path. 

107 

108 Parameters 

109 ---------- 

110 log_path_h5 : Path 

111 The path to the log file. 

112 output_path_yml : Path 

113 The path to save the extracted YAML configuration. 

114 row : int 

115 The row number to extract the configuration from. 

116 config_field : str, optional 

117 The field name for the configuration in the log file (default is "Config File"). 

118 

119 Returns 

120 ------- 

121 bool 

122 True if the YAML configuration is extracted and saved successfully. 

123 """ 

124 # TODO enforce file formats on inputs 

125 with h5py.File(log_path_h5, "r") as file: 

126 data = np.array(file[Constants.settings_dataset_name][:]) 

127 settings = data[row][Constants.settings_dtype.names.index(config_field)].decode( 

128 "utf-8" 

129 ) 

130 

131 with open(output_path_yml, "w") as file: 

132 file.write(settings) 

133 

134 return True 

135 

136 

137def experiment_settings( 

138 slice_number: int, 

139 step_number: int, 

140 log_filepath: Path, 

141 yml_path: Path, 

142) -> bool: 

143 """ 

144 Log experiment settings to the log file. 

145 

146 This function logs the experiment settings from a YAML file to the log file. 

147 

148 Parameters 

149 ---------- 

150 slice_number : int 

151 The slice number for the experiment. 

152 step_number : int 

153 The step number for the experiment. 

154 log_filepath : Path 

155 The path to the log file. 

156 yml_path : Path 

157 The path to the YAML file containing the experiment settings. 

158 

159 Returns 

160 ------- 

161 bool 

162 True if the experiment settings are logged successfully. 

163 """ 

164 dataset_name = Constants.settings_dataset_name 

165 settings_dtype = Constants.settings_dtype 

166 time = current_time() 

167 

168 with open(yml_path, "r") as yml_file: 

169 yml_data = yml_file.read() 

170 

171 if not log_filepath.exists(): 

172 log_filepath.touch() 

173 

174 with h5py.File(log_filepath, "r+") as log: 

175 if not dataset_name in log: 

176 settings = log.create_dataset( 

177 dataset_name, 

178 (0,), 

179 settings_dtype, 

180 maxshape=(None,), 

181 ) 

182 dataset = log[dataset_name] 

183 settings_data = np.array( 

184 [ 

185 ( 

186 slice_number, 

187 step_number, 

188 yml_data, 

189 time.human_readable, 

190 time.unix, 

191 ) 

192 ], 

193 settings_dtype, 

194 ) 

195 # add one row to table 

196 dataset.resize(dataset.shape[0] + 1, axis=0) 

197 dataset[-1:] = settings_data 

198 

199 return True 

200 

201 

202def position( 

203 step_number: int, 

204 step_name: str, 

205 slice_number: int, 

206 log_filepath: Path, 

207 dataset_name: str, 

208 current_position: tbt.StagePositionUser, 

209) -> bool: 

210 """ 

211 Log the current position to the log file. 

212 

213 This function logs the current position of the stage to the log file. 

214 

215 Parameters 

216 ---------- 

217 step_number : int 

218 The step number for the experiment. 

219 step_name : str 

220 The name of the step. 

221 slice_number : int 

222 The slice number for the experiment. 

223 log_filepath : Path 

224 The path to the log file. 

225 dataset_name : str 

226 The name of the dataset to log the position to. 

227 current_position : tbt.StagePositionUser 

228 The current position of the stage. 

229 

230 Returns 

231 ------- 

232 bool 

233 True if the current position is logged successfully. 

234 """ 

235 print("\tLogging current position...") 

236 dataset_location = f"{step_number:02d}_{step_name}/{dataset_name}" 

237 time = current_time() 

238 

239 with h5py.File(log_filepath, "r+") as file: 

240 if not dataset_location in file: 

241 position = file.create_dataset( 

242 dataset_location, 

243 (0,), 

244 Constants.position_dtype, 

245 maxshape=(None,), 

246 ) 

247 position.attrs["X Units"] = np.string_("[mm]") 

248 position.attrs["Y Units"] = np.string_("[mm]") 

249 position.attrs["Z Units"] = np.string_("[mm]") 

250 position.attrs["T Units"] = np.string_("[degrees]") 

251 position.attrs["R Units"] = np.string_("[degrees]") 

252 

253 dataset = file[dataset_location] 

254 position_data = np.array( 

255 [ 

256 ( 

257 slice_number, 

258 round(current_position.x_mm, 6), 

259 round(current_position.y_mm, 6), 

260 round(current_position.z_mm, 6), 

261 round(current_position.t_deg, 6), 

262 round(current_position.r_deg, 6), 

263 time.human_readable, 

264 time.unix, 

265 ) 

266 ], 

267 Constants.position_dtype, 

268 ) 

269 # add one row to table 

270 dataset.resize(dataset.shape[0] + 1, axis=0) 

271 dataset[-1:] = position_data 

272 

273 return True 

274 

275 

276def laser_power( 

277 step_number: int, 

278 step_name: str, 

279 slice_number: int, 

280 log_filepath: Path, 

281 dataset_name: str, 

282 power_w: float, 

283) -> bool: 

284 """ 

285 Log the laser power to the log file. 

286 

287 This function logs the laser power to the log file. 

288 

289 Parameters 

290 ---------- 

291 step_number : int 

292 The step number for the experiment. 

293 step_name : str 

294 The name of the step. 

295 slice_number : int 

296 The slice number for the experiment. 

297 log_filepath : Path 

298 The path to the log file. 

299 dataset_name : str 

300 The name of the dataset to log the laser power to. 

301 power_w : float 

302 The laser power in watts. 

303 

304 Returns 

305 ------- 

306 bool 

307 True if the laser power is logged successfully. 

308 """ 

309 print("\tLogging laser power...") 

310 dataset_location = f"{step_number:02d}_{step_name}/{dataset_name}" 

311 time = current_time() 

312 

313 with h5py.File(log_filepath, "r+") as file: 

314 if not dataset_location in file: 

315 laser_power = file.create_dataset( 

316 dataset_location, 

317 (0,), 

318 Constants.laser_power_dtype, 

319 maxshape=(None,), 

320 ) 

321 laser_power.attrs["Units"] = np.string_("[W]") 

322 

323 dataset = file[dataset_location] 

324 laser_power_data = np.array( 

325 [ 

326 ( 

327 slice_number, 

328 round(power_w, 6), 

329 time.human_readable, 

330 time.unix, 

331 ) 

332 ], 

333 Constants.laser_power_dtype, 

334 ) 

335 # add one row to table 

336 dataset.resize(dataset.shape[0] + 1, axis=0) 

337 dataset[-1:] = laser_power_data 

338 

339 

340def specimen_current( 

341 step_number: int, 

342 step_name: str, 

343 slice_number: int, 

344 log_filepath: Path, 

345 dataset_name: str, 

346 specimen_current_na: float, 

347) -> bool: 

348 """ 

349 Log the specimen current to the log file. 

350 

351 This function logs the specimen current to the log file. 

352 

353 Parameters 

354 ---------- 

355 step_number : int 

356 The step number for the experiment. 

357 step_name : str 

358 The name of the step. 

359 slice_number : int 

360 The slice number for the experiment. 

361 log_filepath : Path 

362 The path to the log file. 

363 dataset_name : str 

364 The name of the dataset to log the specimen current to. 

365 specimen_current_na : float 

366 The specimen current in nanoamperes. 

367 

368 Returns 

369 ------- 

370 bool 

371 True if the specimen current is logged successfully. 

372 """ 

373 print("\tLogging sample current...") 

374 dataset_location = f"{step_number:02d}_{step_name}/{dataset_name}" 

375 time = current_time() 

376 

377 with h5py.File(log_filepath, "r+") as file: 

378 if not dataset_location in file: 

379 specimen_current = file.create_dataset( 

380 dataset_location, 

381 (0,), 

382 Constants.specimen_current_dtype, 

383 maxshape=(None,), 

384 ) 

385 specimen_current.attrs["Units"] = np.string_("[nA]") 

386 

387 dataset = file[dataset_location] 

388 specimen_current_data = np.array( 

389 [ 

390 ( 

391 slice_number, 

392 round(specimen_current_na, 6), 

393 time.human_readable, 

394 time.unix, 

395 ) 

396 ], 

397 Constants.specimen_current_dtype, 

398 ) 

399 # add one row to table 

400 dataset.resize(dataset.shape[0] + 1, axis=0) 

401 dataset[-1:] = specimen_current_data 

402 print("\tLogging sample current complete...") 

403 

404 

405if __name__ == "__main__": 

406 pass