ChessAnalysisPipeline 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ChessAnalysisPipeline might be problematic. Click here for more details.

Files changed (47) hide show
  1. CHAP/__init__.py +3 -0
  2. CHAP/common/__init__.py +19 -0
  3. CHAP/common/models/__init__.py +2 -0
  4. CHAP/common/models/integration.py +515 -0
  5. CHAP/common/models/map.py +535 -0
  6. CHAP/common/processor.py +644 -0
  7. CHAP/common/reader.py +119 -0
  8. CHAP/common/utils/__init__.py +37 -0
  9. CHAP/common/utils/fit.py +2613 -0
  10. CHAP/common/utils/general.py +1225 -0
  11. CHAP/common/utils/material.py +231 -0
  12. CHAP/common/utils/scanparsers.py +785 -0
  13. CHAP/common/writer.py +96 -0
  14. CHAP/edd/__init__.py +7 -0
  15. CHAP/edd/models.py +215 -0
  16. CHAP/edd/processor.py +321 -0
  17. CHAP/edd/reader.py +5 -0
  18. CHAP/edd/writer.py +5 -0
  19. CHAP/inference/__init__.py +3 -0
  20. CHAP/inference/processor.py +68 -0
  21. CHAP/inference/reader.py +5 -0
  22. CHAP/inference/writer.py +5 -0
  23. CHAP/pipeline.py +1 -1
  24. CHAP/processor.py +11 -818
  25. CHAP/reader.py +18 -113
  26. CHAP/saxswaxs/__init__.py +6 -0
  27. CHAP/saxswaxs/processor.py +5 -0
  28. CHAP/saxswaxs/reader.py +5 -0
  29. CHAP/saxswaxs/writer.py +5 -0
  30. CHAP/sin2psi/__init__.py +7 -0
  31. CHAP/sin2psi/processor.py +5 -0
  32. CHAP/sin2psi/reader.py +5 -0
  33. CHAP/sin2psi/writer.py +5 -0
  34. CHAP/tomo/__init__.py +5 -0
  35. CHAP/tomo/models.py +125 -0
  36. CHAP/tomo/processor.py +2009 -0
  37. CHAP/tomo/reader.py +5 -0
  38. CHAP/tomo/writer.py +5 -0
  39. CHAP/writer.py +17 -167
  40. {ChessAnalysisPipeline-0.0.2.dist-info → ChessAnalysisPipeline-0.0.4.dist-info}/METADATA +1 -1
  41. ChessAnalysisPipeline-0.0.4.dist-info/RECORD +50 -0
  42. CHAP/async.py +0 -56
  43. ChessAnalysisPipeline-0.0.2.dist-info/RECORD +0 -17
  44. {ChessAnalysisPipeline-0.0.2.dist-info → ChessAnalysisPipeline-0.0.4.dist-info}/LICENSE +0 -0
  45. {ChessAnalysisPipeline-0.0.2.dist-info → ChessAnalysisPipeline-0.0.4.dist-info}/WHEEL +0 -0
  46. {ChessAnalysisPipeline-0.0.2.dist-info → ChessAnalysisPipeline-0.0.4.dist-info}/entry_points.txt +0 -0
  47. {ChessAnalysisPipeline-0.0.2.dist-info → ChessAnalysisPipeline-0.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,785 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # -*- coding: utf-8 -*-
4
+
5
+ # system modules
6
+ import csv
7
+ import fnmatch
8
+ from functools import cache
9
+ import json
10
+ import os
11
+ import re
12
+
13
+ # necessary for the base class, ScanParser:
14
+ import numpy as np
15
+ from pyspec.file.spec import FileSpec
16
+
17
+ class ScanParser(object):
18
+ def __init__(self,
19
+ spec_file_name:str,
20
+ scan_number:int):
21
+
22
+ self.spec_file_name = spec_file_name
23
+ self.scan_number = scan_number
24
+
25
+ self._scan_path = None
26
+ self._scan_name = None
27
+ self._scan_title = None
28
+
29
+ self._spec_scan = None
30
+ self._spec_command = None
31
+ self._spec_macro = None
32
+ self._spec_args = None
33
+ self._spec_scan_npts = None
34
+ self._spec_scan_data = None
35
+ self._spec_positioner_values = None
36
+
37
+ self._detector_data_path = None
38
+
39
+ def __repr__(self):
40
+ return(f'{self.__class__.__name__}({self.spec_file_name}, {self.scan_number}) -- {self.spec_command}')
41
+
42
+ @property
43
+ def spec_file(self):
44
+ # NB This FileSpec instance is not stored as a private attribute because
45
+ # it cannot be pickled (and therefore could cause problems for
46
+ # parallel code that uses ScanParsers).
47
+ return(FileSpec(self.spec_file_name))
48
+ @property
49
+ def scan_path(self):
50
+ if self._scan_path is None:
51
+ self._scan_path = self.get_scan_path()
52
+ return self._scan_path
53
+ @property
54
+ def scan_name(self):
55
+ if self._scan_name is None:
56
+ self._scan_name = self.get_scan_name()
57
+ return self._scan_name
58
+ @property
59
+ def scan_title(self):
60
+ if self._scan_title is None:
61
+ self._scan_title = self.get_scan_title()
62
+ return self._scan_title
63
+ @property
64
+ def spec_scan(self):
65
+ if self._spec_scan is None:
66
+ self._spec_scan = self.get_spec_scan()
67
+ return self._spec_scan
68
+ @property
69
+ def spec_command(self):
70
+ if self._spec_command is None:
71
+ self._spec_command = self.get_spec_command()
72
+ return self._spec_command
73
+ @property
74
+ def spec_macro(self):
75
+ if self._spec_macro is None:
76
+ self._spec_macro = self.get_spec_macro()
77
+ return self._spec_macro
78
+ @property
79
+ def spec_args(self):
80
+ if self._spec_args is None:
81
+ self._spec_args = self.get_spec_args()
82
+ return self._spec_args
83
+ @property
84
+ def spec_scan_npts(self):
85
+ if self._spec_scan_npts is None:
86
+ self._spec_scan_npts = self.get_spec_scan_npts()
87
+ return self._spec_scan_npts
88
+ @property
89
+ def spec_scan_data(self):
90
+ if self._spec_scan_data is None:
91
+ self._spec_scan_data = self.get_spec_scan_data()
92
+ return self._spec_scan_data
93
+ @property
94
+ def spec_positioner_values(self):
95
+ if self._spec_positioner_values is None:
96
+ self._spec_positioner_values = self.get_spec_positioner_values()
97
+ return self._spec_positioner_values
98
+ @property
99
+ def detector_data_path(self):
100
+ if self._detector_data_path is None:
101
+ self._detector_data_path = self.get_detector_data_path()
102
+ return self._detector_data_path
103
+
104
+ def get_scan_path(self):
105
+ return(os.path.dirname(self.spec_file_name))
106
+ def get_scan_name(self):
107
+ return(None)
108
+ def get_scan_title(self):
109
+ return(None)
110
+ def get_spec_scan(self):
111
+ return(self.spec_file.getScanByNumber(self.scan_number))
112
+ def get_spec_command(self):
113
+ return(self.spec_scan.command)
114
+ def get_spec_macro(self):
115
+ return(self.spec_command.split()[0])
116
+ def get_spec_args(self):
117
+ return(self.spec_command.split()[1:])
118
+ def get_spec_scan_npts(self):
119
+ raise(NotImplementedError)
120
+ def get_spec_scan_data(self):
121
+ return(dict(zip(self.spec_scan.labels, self.spec_scan.data.T)))
122
+ def get_spec_positioner_values(self):
123
+ positioner_values = dict(self.spec_scan.motor_positions)
124
+ names = list(positioner_values.keys())
125
+ mnemonics = self.spec_scan.motors
126
+ if mnemonics is not None:
127
+ for name,mnemonic in zip(names,mnemonics):
128
+ if name != mnemonic:
129
+ positioner_values[mnemonic] = positioner_values[name]
130
+ return(positioner_values)
131
+ def get_detector_data_path(self):
132
+ raise(NotImplementedError)
133
+
134
+ def get_detector_data_file(self, detector_prefix, scan_step_index:int):
135
+ raise(NotImplementedError)
136
+ def get_detector_data(self, detector_prefix, scan_step_index:int):
137
+ '''
138
+ Return a np.ndarray of detector data.
139
+
140
+ :param detector_prefix: The detector's name in any data files, often
141
+ the EPICS macro $(P).
142
+ :type detector_substring: str
143
+
144
+ :param scan_step_index: The index of the scan step for which detector
145
+ data will be returned.
146
+ :type scan_step_index: int
147
+
148
+ :return: The detector data
149
+ :rtype: np.ndarray
150
+ '''
151
+ raise(NotImplementedError)
152
+
153
+ def get_spec_positioner_value(self, positioner_name):
154
+ try:
155
+ positioner_value = self.spec_positioner_values[positioner_name]
156
+ positioner_value = float(positioner_value)
157
+ return(positioner_value)
158
+ except KeyError:
159
+ raise(KeyError(f'{self.scan_title}: motor {positioner_name} not found for this scan'))
160
+ except ValueError:
161
+ raise(ValueError(f'{self.scan_title}: ccould not convert value of {positioner_name} to float: {positioner_value}'))
162
+
163
+
164
+ class FMBScanParser(ScanParser):
165
+ def __init__(self, spec_file_name, scan_number):
166
+ super().__init__(spec_file_name, scan_number)
167
+ def get_scan_name(self):
168
+ return(os.path.basename(self.spec_file.abspath))
169
+ def get_scan_title(self):
170
+ return(f'{self.scan_name}_{self.scan_number:03d}')
171
+
172
+
173
+
174
+ class SMBScanParser(ScanParser):
175
+ def __init__(self, spec_file_name, scan_number):
176
+ super().__init__(spec_file_name, scan_number)
177
+
178
+ self._pars = None # purpose: store values found in the .par file as a dictionary
179
+ self.par_file_pattern = f'*-*-{self.scan_name}'
180
+
181
+ def get_scan_name(self):
182
+ return(os.path.basename(self.scan_path))
183
+ def get_scan_title(self):
184
+ return(f'{self.scan_name}_{self.scan_number}')
185
+
186
+ @property
187
+ def pars(self):
188
+ if self._pars is None:
189
+ self._pars = self.get_pars()
190
+ return(self._pars)
191
+
192
+ def get_pars(self):
193
+ # JSON file holds titles for columns in the par file
194
+ json_files = fnmatch.filter(os.listdir(self.scan_path), f'{self.par_file_pattern}.json')
195
+ if not len(json_files) == 1:
196
+ raise(RuntimeError(f'{self.scan_title}: cannot find the .json file to decode the .par file'))
197
+ with open(os.path.join(self.scan_path, json_files[0])) as json_file:
198
+ par_file_cols = json.load(json_file)
199
+ try:
200
+ par_col_names = list(par_file_cols.values())
201
+ scann_val_idx = par_col_names.index('SCAN_N')
202
+ scann_col_idx = int(list(par_file_cols.keys())[scann_val_idx])
203
+ except:
204
+ raise(RuntimeError(f'{self.scan_title}: cannot find scan pars without a "SCAN_N" column in the par file'))
205
+
206
+ par_files = fnmatch.filter(os.listdir(self.scan_path), f'{self.par_file_pattern}.par')
207
+ if not len(par_files) == 1:
208
+ raise(RuntimeError(f'{self.scan_title}: cannot find the .par file for this scan directory'))
209
+ with open(os.path.join(self.scan_path, par_files[0])) as par_file:
210
+ par_reader = csv.reader(par_file, delimiter=' ')
211
+ for row in par_reader:
212
+ if len(row) == len(par_col_names):
213
+ row_scann = int(row[scann_col_idx])
214
+ if row_scann == self.scan_number:
215
+ par_dict = {}
216
+ for par_col_idx,par_col_name in par_file_cols.items():
217
+ # Convert the string par value from the file to an int or float, if possible.
218
+ par_value = row[int(par_col_idx)]
219
+ try:
220
+ par_value = int(par_value)
221
+ except ValueError:
222
+ try:
223
+ par_value = float(par_value)
224
+ except:
225
+ pass
226
+ par_dict[par_col_name] = par_value
227
+ return(par_dict)
228
+ raise(RuntimeError(f'{self.scan_title}: could not find scan pars for scan number {self.scan_number}'))
229
+
230
+ def get_counter_gain(self, counter_name):
231
+ for comment in self.spec_scan.comments:
232
+ match = re.search(f'{counter_name} gain: (?P<gain_value>\d+) (?P<unit_prefix>[m|u|n])A/V', comment)
233
+ if match:
234
+ unit_prefix = match['unit_prefix']
235
+ gain_scalar = 1 if unit_prefix == 'n' else 1e3 if unit_prefix == 'u' else 1e6
236
+ counter_gain = f'{float(match["gain_value"])*gain_scalar} nA/V'
237
+ return(counter_gain)
238
+ raise(RuntimeError(f'{self.scan_title}: could not get gain for counter {counter_name}'))
239
+
240
+
241
+ class LinearScanParser(ScanParser):
242
+ def __init__(self, spec_file_name, scan_number):
243
+ super().__init__(spec_file_name, scan_number)
244
+
245
+ self._spec_scan_motor_mnes = None
246
+ self._spec_scan_motor_vals = None
247
+ self._spec_scan_shape = None
248
+ self._spec_scan_dwell = None
249
+
250
+ @property
251
+ def spec_scan_motor_mnes(self):
252
+ if self._spec_scan_motor_mnes is None:
253
+ self._spec_scan_motor_mnes = self.get_spec_scan_motor_mnes()
254
+ return self._spec_scan_motor_mnes
255
+ @property
256
+ def spec_scan_motor_vals(self):
257
+ if self._spec_scan_motor_vals is None:
258
+ self._spec_scan_motor_vals = self.get_spec_scan_motor_vals()
259
+ return self._spec_scan_motor_vals
260
+ @property
261
+ def spec_scan_shape(self):
262
+ if self._spec_scan_shape is None:
263
+ self._spec_scan_shape = self.get_spec_scan_shape()
264
+ return self._spec_scan_shape
265
+ @property
266
+ def spec_scan_dwell(self):
267
+ if self._spec_scan_dwell is None:
268
+ self._spec_scan_dwell = self.get_spec_scan_dwell()
269
+ return(self._spec_scan_dwell)
270
+
271
+ def get_spec_scan_motor_names(self):
272
+ raise(NotImplementedError)
273
+ def get_spec_scan_motor_vals(self):
274
+ raise(NotImplementedError)
275
+ def get_spec_scan_shape(self):
276
+ raise(NotImplementedError)
277
+ def get_spec_scan_npts(self):
278
+ return(np.prod(self.spec_scan_shape))
279
+ def get_scan_step(self, scan_step_index:int):
280
+ scan_steps = np.ndindex(self.spec_scan_shape[::-1])
281
+ i = 0
282
+ while i <= scan_step_index:
283
+ scan_step = next(scan_steps)
284
+ i += 1
285
+ return(scan_step)
286
+ def get_scan_step_index(self, scan_step:tuple):
287
+ scan_steps = np.ndindex(self.spec_scan_shape[::-1])
288
+ scan_step_found = False
289
+ scan_step_index = -1
290
+ while not scan_step_found:
291
+ next_scan_step = next(scan_steps)
292
+ scan_step_index += 1
293
+ if next_scan_step == scan_step:
294
+ scan_step_found = True
295
+ break
296
+ return(scan_step_index)
297
+
298
+
299
+ class FMBLinearScanParser(LinearScanParser, FMBScanParser):
300
+ def __init__(self, spec_file_name, scan_number):
301
+ super().__init__(spec_file_name, scan_number)
302
+
303
+ def get_spec_scan_motor_mnes(self):
304
+ if self.spec_macro == 'flymesh':
305
+ return((self.spec_args[0], self.spec_args[5]))
306
+ elif self.spec_macro == 'flyscan':
307
+ return((self.spec_args[0],))
308
+ elif self.spec_macro in ('tseries', 'loopscan'):
309
+ return(('Time',))
310
+ else:
311
+ raise(RuntimeError(f'{self.scan_title}: cannot determine scan motors for scans of type {self.spec_macro}'))
312
+ def get_spec_scan_motor_vals(self):
313
+ if self.spec_macro == 'flymesh':
314
+ fast_mot_vals = np.linspace(float(self.spec_args[1]), float(self.spec_args[2]), int(self.spec_args[3])+1)
315
+ slow_mot_vals = np.linspace(float(self.spec_args[6]), float(self.spec_args[7]), int(self.spec_args[8])+1)
316
+ return((fast_mot_vals, slow_mot_vals))
317
+ elif self.spec_macro == 'flyscan':
318
+ mot_vals = np.linspace(float(self.spec_args[1]), float(self.spec_args[2]), int(self.spec_args[3])+1)
319
+ return((mot_vals,))
320
+ elif self.spec_macro in ('tseries', 'loopscan'):
321
+ return(self.spec_scan.data[:,0])
322
+ else:
323
+ raise(RuntimeError(f'{self.scan_title}: cannot determine scan motors for scans of type {self.spec_macro}'))
324
+ def get_spec_scan_shape(self):
325
+ if self.spec_macro == 'flymesh':
326
+ fast_mot_npts = int(self.spec_args[3])+1
327
+ slow_mot_npts = int(self.spec_args[8])+1
328
+ return((fast_mot_npts, slow_mot_npts))
329
+ elif self.spec_macro == 'flyscan':
330
+ mot_npts = int(self.spec_args[3])+1
331
+ return((mot_npts,))
332
+ elif self.spec_macro in ('tseries', 'loopscan'):
333
+ return(len(np.array(self.spec_scan.data[:,0])))
334
+ else:
335
+ raise(RuntimeError(f'{self.scan_title}: cannot determine scan shape for scans of type {self.spec_macro}'))
336
+ def get_spec_scan_dwell(self):
337
+ if self.spec_macro in ('flymesh', 'flyscan'):
338
+ return(float(self.spec_args[4]))
339
+ elif self.spec_macro in ('tseries', 'loopscan'):
340
+ return(float(self.spec_args[1]))
341
+ else:
342
+ raise(RuntimeError(f'{self.scan_title}: cannot determine dwell for scans of type {self.spec_macro}'))
343
+ def get_detector_data_path(self):
344
+ return(os.path.join(self.scan_path, self.scan_title))
345
+
346
+
347
+ class FMBSAXSWAXSScanParser(FMBLinearScanParser):
348
+ def __init__(self, spec_file_name, scan_number):
349
+ super().__init__(spec_file_name, scan_number)
350
+
351
+ def get_scan_title(self):
352
+ return(f'{self.scan_name}_{self.scan_number:03d}')
353
+ def get_detector_data_file(self, detector_prefix, scan_step_index:int):
354
+ scan_step = self.get_scan_step(scan_step_index)
355
+ file_indices = [f'{scan_step[i]:03d}' for i in range(len(self.spec_scan_shape)) if self.spec_scan_shape[i] != 1]
356
+ file_name = f'{self.scan_name}_{detector_prefix}_{self.scan_number:03d}_{"_".join(file_indices)}.tiff'
357
+ file_name_full = os.path.join(self.detector_data_path, file_name)
358
+ if os.path.isfile(file_name_full):
359
+ return(file_name_full)
360
+ else:
361
+ raise(RuntimeError(f'{self.scan_title}: could not find detector image file for detector {detector_prefix} scan step ({scan_step})'))
362
+ def get_detector_data(self, detector_prefix, scan_step_index:int):
363
+ from pyspec.file.tiff import TiffFile
364
+ image_file = self.get_detector_data_file(detector_prefix, scan_step_index)
365
+ with TiffFile(image_file) as tiff_file:
366
+ image_data = tiff_file.asarray()
367
+ return(image_data)
368
+
369
+
370
+ class FMBXRFScanParser(FMBLinearScanParser):
371
+ def __init__(self, spec_file_name, scan_number):
372
+ super().__init__(spec_file_name, scan_number)
373
+ def get_scan_title(self):
374
+ return(f'{self.scan_name}_scan{self.scan_number}')
375
+ def get_detector_data_file(self, detector_prefix, scan_step_index:int):
376
+ scan_step = self.get_scan_step(scan_step_index)
377
+ file_name = f'scan{self.scan_number}_{scan_step[1]:03d}.hdf5'
378
+ file_name_full = os.path.join(self.detector_data_path, file_name)
379
+ if os.path.isfile(file_name_full):
380
+ return(file_name_full)
381
+ else:
382
+ raise(RuntimeError(f'{self.scan_title}: could not find detector image file for detector {detector_prefix} scan step ({scan_step_index})'))
383
+ def get_detector_data(self, detector_prefix, scan_step_index:int):
384
+ import h5py
385
+ detector_file = self.get_detector_data_file(detector_prefix, scan_step_index)
386
+ scan_step = self.get_scan_step(scan_step_index)
387
+ with h5py.File(detector_file) as h5_file:
388
+ detector_data = h5_file['/entry/instrument/detector/data'][scan_step[0]]
389
+ return(detector_data)
390
+
391
+
392
+ class SMBLinearScanParser(LinearScanParser, SMBScanParser):
393
+ def __init__(self, spec_file_name, scan_number):
394
+ super().__init__(spec_file_name, scan_number)
395
+ def get_spec_scan_motor_mnes(self):
396
+ if self.spec_macro == 'flymesh':
397
+ return((self.spec_args[0], self.spec_args[5]))
398
+ elif self.spec_macro == 'flyscan':
399
+ return((self.spec_args[0],))
400
+ elif self.spec_macro in ('tseries', 'loopscan'):
401
+ return(('Time',))
402
+ else:
403
+ raise(RuntimeError(f'{self.scan_title}: cannot determine scan motors for scans of type {self.spec_macro}'))
404
+ def get_spec_scan_motor_vals(self):
405
+ if self.spec_macro == 'flymesh':
406
+ fast_mot_vals = np.linspace(float(self.spec_args[1]), float(self.spec_args[2]), int(self.spec_args[3])+1)
407
+ slow_mot_vals = np.linspace(float(self.spec_args[6]), float(self.spec_args[7]), int(self.spec_args[8])+1)
408
+ return((fast_mot_vals, slow_mot_vals))
409
+ elif self.spec_macro == 'flyscan':
410
+ mot_vals = np.linspace(float(self.spec_args[1]), float(self.spec_args[2]), int(self.spec_args[3])+1)
411
+ return((mot_vals,))
412
+ elif self.spec_macro in ('tseries', 'loopscan'):
413
+ return(self.spec_scan.data[:,0])
414
+ else:
415
+ raise(RuntimeError(f'{self.scan_title}: cannot determine scan motors for scans of type {self.spec_macro}'))
416
+ def get_spec_scan_shape(self):
417
+ if self.spec_macro == 'flymesh':
418
+ fast_mot_npts = int(self.spec_args[3])+1
419
+ slow_mot_npts = int(self.spec_args[8])+1
420
+ return((fast_mot_npts, slow_mot_npts))
421
+ elif self.spec_macro == 'flyscan':
422
+ mot_npts = int(self.spec_args[3])+1
423
+ return((mot_npts,))
424
+ elif self.spec_macro in ('tseries', 'loopscan'):
425
+ return(len(np.array(self.spec_scan.data[:,0])))
426
+ else:
427
+ raise(RuntimeError(f'{self.scan_title}: cannot determine scan shape for scans of type {self.spec_macro}'))
428
+ def get_spec_scan_dwell(self):
429
+ if self.spec_macro == 'flymesh':
430
+ return(float(self.spec_args[4]))
431
+ elif self.spec_macro == 'flyscan':
432
+ return(float(self.spec_args[-1]))
433
+ else:
434
+ raise(RuntimeError(f'{self.scan_title}: cannot determine dwell time for scans of type {self.spec_macro}'))
435
+ def get_detector_data_path(self):
436
+ return(os.path.join(self.scan_path, str(self.scan_number)))
437
+ def get_detector_data_file(self, detector_prefix, scan_step_index:int):
438
+ scan_step = self.get_scan_step(scan_step_index)
439
+ if len(scan_step) == 1:
440
+ scan_step = (0, *scan_step)
441
+ file_name_pattern = f'{detector_prefix}_{self.scan_name}_*_{scan_step[0]}_data_{(scan_step[1]+1):06d}.h5'
442
+ file_name_matches = fnmatch.filter(os.listdir(self.detector_data_path), file_name_pattern)
443
+ if len(file_name_matches) == 1:
444
+ return(os.path.join(self.detector_data_path, file_name_matches[0]))
445
+ else:
446
+ raise(RuntimeError(f'{self.scan_title}: could not find detector image file for detector {detector_prefix} scan step ({scan_step_index})'))
447
+ def get_detector_data(self, detector_prefix, scan_step_index:int):
448
+ import h5py
449
+ image_file = self.get_detector_data_file(detector_prefix, scan_step_index)
450
+ with h5py.File(image_file) as h5_file:
451
+ image_data = h5_file['/entry/data/data'][0]
452
+ return(image_data)
453
+
454
+
455
+ class RotationScanParser(ScanParser):
456
+ def __init__(self, spec_file_name, scan_number):
457
+ super().__init__(spec_file_name, scan_number)
458
+
459
+ self._scan_type = None
460
+ self._theta_vals = None
461
+ self._horizontal_shift = None
462
+ self._vertical_shift = None
463
+ self._starting_image_index = None
464
+ self._starting_image_offset = None
465
+
466
+ @property
467
+ def scan_type(self):
468
+ if self._scan_type is None:
469
+ self._scan_type = self.get_scan_type()
470
+ return(self._scan_type)
471
+ @property
472
+ def theta_vals(self):
473
+ if self._theta_vals is None:
474
+ self._theta_vals = self.get_theta_vals()
475
+ return(self._theta_vals)
476
+ @property
477
+ def horizontal_shift(self):
478
+ if self._horizontal_shift is None:
479
+ self._horizontal_shift = self.get_horizontal_shift()
480
+ return(self._horizontal_shift)
481
+ @property
482
+ def vertical_shift(self):
483
+ if self._vertical_shift is None:
484
+ self._vertical_shift = self.get_vertical_shift()
485
+ return(self._vertical_shift)
486
+ @property
487
+ def starting_image_index(self):
488
+ if self._starting_image_index is None:
489
+ self._starting_image_index = self.get_starting_image_index()
490
+ return(self._starting_image_index)
491
+ @property
492
+ def starting_image_offset(self):
493
+ if self._starting_image_offset is None:
494
+ self._starting_image_offset = self.get_starting_image_offset()
495
+ return(self._starting_image_offset)
496
+
497
+ def get_scan_type(self):
498
+ return(None)
499
+ def get_theta_vals(self):
500
+ raise(NotImplementedError)
501
+ def get_horizontal_shift(self):
502
+ raise(NotImplementedError)
503
+ def get_vertical_shift(self):
504
+ raise(NotImplementedError)
505
+ def get_starting_image_index(self):
506
+ raise(NotImplementedError)
507
+ def get_starting_image_offset(self):
508
+ raise(NotImplementedError)
509
+ def get_num_image(self, detector_prefix):
510
+ raise(NotImplementedError)
511
+
512
+
513
+ class FMBRotationScanParser(RotationScanParser, FMBScanParser):
514
+ def __init__(self, spec_file_name, scan_number):
515
+ super().__init__(spec_file_name, scan_number)
516
+ def get_spec_scan_npts(self):
517
+ if self.spec_macro == 'flyscan':
518
+ if len(self.spec_args) == 2:
519
+ # Flat field (dark or bright)
520
+ return(int(self.spec_args[0])+1)
521
+ elif len(self.spec_args) == 5:
522
+ return(int(self.spec_args[3])+1)
523
+ else:
524
+ raise(RuntimeError(f'{self.scan_title}: cannot obtain number of points from '+
525
+ f'{self.spec_macro} with arguments {self.spec_args}'))
526
+ else:
527
+ raise(RuntimeError(f'{self.scan_title}: cannot determine number of points for scans '+
528
+ f'of type {self.spec_macro}'))
529
+ def get_theta_vals(self):
530
+ if self.spec_macro == 'flyscan':
531
+ if len(self.spec_args) == 2:
532
+ # Flat field (dark or bright)
533
+ return({'num': int(self.spec_args[0])})
534
+ elif len(self.spec_args) == 5:
535
+ return({'start': float(self.spec_args[1]), 'end': float(self.spec_args[2]),
536
+ 'num': int(self.spec_args[3])+1})
537
+ else:
538
+ raise(RuntimeError(f'{self.scan_title}: cannot obtain theta values from '+
539
+ f'{self.spec_macro} with arguments {self.spec_args}'))
540
+ else:
541
+ raise(RuntimeError(f'{self.scan_title}: cannot determine theta values for scans '+
542
+ f'of type {self.spec_macro}'))
543
+ def get_horizontal_shift(self):
544
+ return(0.0)
545
+ def get_vertical_shift(self):
546
+ return(float(self.get_spec_positioner_value('4C_samz')))
547
+ def get_starting_image_index(self):
548
+ return(0)
549
+ def get_starting_image_offset(self):
550
+ return(1)
551
+ def get_num_image(self, detector_prefix):
552
+ import h5py
553
+ detector_file = self.get_detector_data_file(detector_prefix)
554
+ with h5py.File(detector_file) as h5_file:
555
+ num_image = h5_file['/entry/instrument/detector/data'].shape[0]
556
+ return(num_image-self.starting_image_offset)
557
+ def get_detector_data_path(self):
558
+ return(self.scan_path)
559
+ def get_detector_data_file(self, detector_prefix):
560
+ prefix = detector_prefix.upper()
561
+ file_name = f'{self.scan_name}_{prefix}_{self.scan_number:03d}.h5'
562
+ file_name_full = os.path.join(self.detector_data_path, file_name)
563
+ if os.path.isfile(file_name_full):
564
+ return(file_name_full)
565
+ else:
566
+ raise(RuntimeError(f'{self.scan_title}: could not find detector image file for '+
567
+ f'detector {detector_prefix}'))
568
+ #@cache
569
+ def get_all_detector_data_in_file(self, detector_prefix, scan_step_index=None):
570
+ import h5py
571
+ detector_file = self.get_detector_data_file(detector_prefix)
572
+ with h5py.File(detector_file) as h5_file:
573
+ if scan_step_index is None:
574
+ detector_data = h5_file['/entry/instrument/detector/data'][
575
+ self.starting_image_index:]
576
+ elif isinstance(scan_step_index, int):
577
+ detector_data = h5_file['/entry/instrument/detector/data'][
578
+ self.starting_image_index+scan_step_index]
579
+ elif isinstance(scan_step_index, (list, tuple)) and len(scan_step_index) == 2:
580
+ detector_data = h5_file['/entry/instrument/detector/data'][
581
+ self.starting_image_index+scan_step_index[0]:
582
+ self.starting_image_index+scan_step_index[1]]
583
+ else:
584
+ raise(ValueError(f'Invalid parameter scan_step_index ({scan_step_index})'))
585
+ return(detector_data)
586
+ def get_detector_data(self, detector_prefix, scan_step_index=None):
587
+ return(self.get_all_detector_data_in_file(detector_prefix, scan_step_index))
588
+
589
+
590
+ class SMBRotationScanParser(RotationScanParser, SMBScanParser):
591
+ def __init__(self, spec_file_name, scan_number):
592
+ super().__init__(spec_file_name, scan_number)
593
+ self.par_file_pattern = f'id*-*tomo*-{self.scan_name}'
594
+ def get_spec_scan_npts(self):
595
+ if self.spec_macro == 'slew_ome' or self.spec_macro == 'rams4_slew_ome':
596
+ return(int(self.pars['nframes_real']))
597
+ else:
598
+ raise(RuntimeError(f'{self.scan_title}: cannot determine number of points for scans of type {self.spec_macro}'))
599
+ def get_scan_type(self):
600
+ try:
601
+ return(self.pars['tomo_type'])
602
+ except:
603
+ try:
604
+ return(self.pars['tomotype'])
605
+ except:
606
+ raise(RuntimeError(f'{self.scan_title}: cannot determine the scan_type'))
607
+ def get_theta_vals(self):
608
+ return({'start': float(self.pars['ome_start_real']),
609
+ 'end': float(self.pars['ome_end_real']), 'num': int(self.pars['nframes_real'])})
610
+ def get_horizontal_shift(self):
611
+ try:
612
+ return(float(self.pars['rams4x']))
613
+ except:
614
+ try:
615
+ return(float(self.pars['ramsx']))
616
+ except:
617
+ raise(RuntimeError(f'{self.scan_title}: cannot determine the horizontal shift'))
618
+ def get_vertical_shift(self):
619
+ try:
620
+ return(float(self.pars['rams4z']))
621
+ except:
622
+ try:
623
+ return(float(self.pars['ramsz']))
624
+ except:
625
+ raise(RuntimeError(f'{self.scan_title}: cannot determine the vertical shift'))
626
+ def get_starting_image_index(self):
627
+ try:
628
+ return(int(self.pars['junkstart']))
629
+ except:
630
+ raise(RuntimeError(f'{self.scan_title}: cannot determine first detector image index'))
631
+ def get_starting_image_offset(self):
632
+ try:
633
+ return(int(self.pars['goodstart'])-self.get_starting_image_index())
634
+ except:
635
+ raise(RuntimeError(f'{self.scan_title}: cannot determine index offset of first good '+
636
+ 'detector image'))
637
+ def get_num_image(self, detector_prefix=None):
638
+ try:
639
+ return(int(self.pars['nframes_real']))
640
+ # indexRegex = re.compile(r'\d+')
641
+ # # At this point only tiffs
642
+ # path = self.get_detector_data_path()
643
+ # files = sorted([f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and
644
+ # f.endswith('.tif') and indexRegex.search(f)])
645
+ # return(len(files)-self.starting_image_offset)
646
+ except:
647
+ raise(RuntimeError(f'{self.scan_title}: cannot determine the number of good '+
648
+ 'detector images'))
649
+ def get_detector_data_path(self):
650
+ return(os.path.join(self.scan_path, str(self.scan_number), 'nf'))
651
+ def get_detector_data_file(self, scan_step_index:int):
652
+ file_name = f'nf_{self.starting_image_index+scan_step_index:06d}.tif'
653
+ file_name_full = os.path.join(self.detector_data_path, file_name)
654
+ if os.path.isfile(file_name_full):
655
+ return(file_name_full)
656
+ else:
657
+ raise(RuntimeError(f'{self.scan_title}: could not find detector image file for '+
658
+ f'scan step ({scan_step_index})'))
659
+ def get_detector_data(self, detector_prefix, scan_step_index=None):
660
+ if scan_step_index is None:
661
+ detector_data = []
662
+ for index in range(len(self.get_num_image(detector_prefix))):
663
+ detector_data.append(self.get_detector_data(detector_prefix, index))
664
+ detector_data = np.asarray(detector_data)
665
+ elif isinstance(scan_step_index, int):
666
+ image_file = self.get_detector_data_file(scan_step_index)
667
+ from pyspec.file.tiff import TiffFile
668
+ with TiffFile(image_file) as tiff_file:
669
+ detector_data = tiff_file.asarray()
670
+ elif isinstance(scan_step_index, (list, tuple)) and len(scan_step_index) == 2:
671
+ detector_data = []
672
+ for index in range(scan_step_index[0], scan_step_index[1]):
673
+ detector_data.append(self.get_detector_data(detector_prefix, index))
674
+ detector_data = np.asarray(detector_data)
675
+ else:
676
+ raise(ValueError(f'Invalid parameter scan_step_index ({scan_step_index})'))
677
+ return(detector_data)
678
+
679
+
680
+ class MCAScanParser(ScanParser):
681
+ def __init__(self, spec_file_name, scan_number):
682
+ super().__init__(spec_file_name, scan_number)
683
+
684
+ self._dwell_time = None
685
+ self._detector_num_bins = None
686
+
687
+ @property
688
+ def dwell_time(self):
689
+ if self._dwell_time is None:
690
+ self._dwell_time = self.get_dwell_time()
691
+ return(self._dwell_time)
692
+
693
+ def get_dwell_time(self):
694
+ raise(NotImplementedError)
695
+ @cache
696
+ def get_detector_num_bins(self, detector_prefix):
697
+ raise(NotImplementedError)
698
+
699
+ class SMBMCAScanParser(MCAScanParser, SMBScanParser):
700
+ def __init__(self, spec_file_name, scan_number):
701
+ super().__init__(spec_file_name, scan_number)
702
+
703
+ def get_spec_scan_npts(self):
704
+ if self.spec_macro == 'tseries':
705
+ return(1)
706
+ elif self.spec_macro == 'ascan':
707
+ return(int(self.spec_args[3]))
708
+ elif self.spec_scan == 'wbslew_scan':
709
+ return(1)
710
+ else:
711
+ raise(RuntimeError(f'{self.scan_title}: cannot determine number of points for scans of type {self.spec_macro}'))
712
+
713
+ def get_dwell_time(self):
714
+ if self.spec_macro == 'tseries':
715
+ return(float(self.spec_args[1]))
716
+ elif self.spec_macro == 'ascan':
717
+ return(float(self.spec_args[4]))
718
+ elif self.spec_macro == 'wbslew_scan':
719
+ return(float(self.spec_args[3]))
720
+ else:
721
+ raise(RuntimeError(f'{self.scan_title}: cannot determine dwell time for scans of type {self.spec_macro}'))
722
+
723
+ def get_detector_num_bins(self, detector_prefix):
724
+ with open(self.get_detector_file(detector_prefix)) as detector_file:
725
+ lines = detector_file.readlines()
726
+ for line in lines:
727
+ if line.startswith('#@CHANN'):
728
+ try:
729
+ line_prefix, number_saved, first_saved, last_saved, reduction_coef = line.split()
730
+ return(int(number_saved))
731
+ except:
732
+ continue
733
+ raise(RuntimeError(f'{self.scan_title}: could not find num_bins for detector {detector_prefix}'))
734
+
735
+ def get_detector_data_path(self):
736
+ return(self.scan_path)
737
+
738
+ def get_detector_file(self, detector_prefix, scan_step_index:int=0):
739
+ file_name = f'spec.log.scan{self.scan_number}.mca1.mca'
740
+ file_name_full = os.path.join(self.detector_data_path, file_name)
741
+ if os.path.isfile(file_name_full):
742
+ return(file_name_full)
743
+ else:
744
+ raise(RuntimeError(f'{self.scan_title}: could not find detector image file'))
745
+
746
+ @cache
747
+ def get_all_detector_data(self, detector_prefix):
748
+ # This should be easy with pyspec, but there are bugs in pyspec for MCA data.....
749
+ # or is the 'bug' from a nonstandard implementation of some macro on our end?
750
+ # According to spec manual and pyspec code, mca data should always begin w/ '@A'
751
+ # In example scans, it begins with '@mca1' instead
752
+ data = []
753
+
754
+ with open(self.get_detector_file(detector_prefix)) as detector_file:
755
+ lines = [line.strip("\\\n") for line in detector_file.readlines()]
756
+
757
+ num_bins = self.get_detector_num_bins(detector_prefix)
758
+
759
+ counter = 0
760
+ for line in lines:
761
+ a = line.split()
762
+
763
+ if len(a) > 0:
764
+ if a[0] == ("@"+detector_prefix):
765
+ counter = 1
766
+ spectrum = np.zeros(num_bins)
767
+ if counter == 1:
768
+ b = np.array(a[1:]).astype('uint16')
769
+ spectrum[(counter-1)*25:((counter-1)*25+25)] = b
770
+ counter = counter + 1
771
+ elif counter > 1 and counter <= (np.floor(num_bins/25.)):
772
+ b = np.array(a).astype('uint16')
773
+ spectrum[(counter-1)*25:((counter-1)*25+25)] = b
774
+ counter = counter+1
775
+ elif counter == (np.ceil(num_bins/25.)):
776
+ b = np.array(a).astype('uint16')
777
+ spectrum[(counter-1)*25:((counter-1)*25+(np.mod(num_bins,25)))] = b
778
+ data.append(spectrum)
779
+ counter = 0
780
+
781
+ return(data)
782
+
783
+ def get_detector_data(self, detector_prefix, scan_step_index:int):
784
+ detector_data = self.get_all_detector_data(detector_prefix)
785
+ return(detector_data[scan_step_index])