ChessAnalysisPipeline 0.0.15__py3-none-any.whl → 0.0.16__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.
- CHAP/__init__.py +1 -1
- CHAP/common/__init__.py +4 -0
- CHAP/common/models/integration.py +29 -26
- CHAP/common/models/map.py +186 -255
- CHAP/common/processor.py +956 -160
- CHAP/common/reader.py +93 -27
- CHAP/common/writer.py +15 -5
- CHAP/edd/__init__.py +2 -2
- CHAP/edd/models.py +299 -449
- CHAP/edd/processor.py +639 -448
- CHAP/edd/reader.py +232 -15
- CHAP/giwaxs/__init__.py +8 -0
- CHAP/giwaxs/models.py +100 -0
- CHAP/giwaxs/processor.py +520 -0
- CHAP/giwaxs/reader.py +5 -0
- CHAP/giwaxs/writer.py +5 -0
- CHAP/pipeline.py +47 -9
- CHAP/runner.py +160 -71
- CHAP/tomo/models.py +25 -25
- CHAP/tomo/processor.py +51 -79
- CHAP/utils/general.py +18 -0
- CHAP/utils/models.py +76 -49
- CHAP/utils/parfile.py +10 -2
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/RECORD +29 -25
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/WHEEL +1 -1
- CHAP/utils/scanparsers.py +0 -1544
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/top_level.txt +0 -0
CHAP/utils/scanparsers.py
DELETED
|
@@ -1,1544 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
# -*- coding: utf-8 -*-
|
|
4
|
-
|
|
5
|
-
"""Parsing data from certain CHESS SPEC scans is supported by a family
|
|
6
|
-
of classes derived from the base class, `ScanParser` (defined
|
|
7
|
-
below). An instance of `ScanParser` represents a single SPEC scan --
|
|
8
|
-
each instance is initialized with the name of a specific spec file and
|
|
9
|
-
integer scan number. Access to certain data collected by that scan
|
|
10
|
-
(counter data, positioner values, scan shape, detector data, etc.) are
|
|
11
|
-
made available through the properties and methods of that object.
|
|
12
|
-
|
|
13
|
-
`ScanParser` is just an incomplete abstraction -- one should not
|
|
14
|
-
declare or work with an instance of `ScanParser` directly. Instead,
|
|
15
|
-
one must find the appropriate concrete subclass to use for the
|
|
16
|
-
particular type of scan one wishes to parse, then declare an instance
|
|
17
|
-
of that specific class to begin accessing data from that scan.
|
|
18
|
-
|
|
19
|
-
Basic usage examples:
|
|
20
|
-
1. Print out the position of a the SPEC positioner motor with mnemonic
|
|
21
|
-
`'mne0'` for a SAXS/WAXS scan collected at FMB:
|
|
22
|
-
```python
|
|
23
|
-
from CHAP.utils.scanparsers import FMBSAXSWAXSScanParser
|
|
24
|
-
sp = FMBSAXSWAXSScanParser('/path/to/fmb/saxswaxs/spec/file', 1)
|
|
25
|
-
print(sp.get_spec_positioner_value('mne0'))
|
|
26
|
-
```
|
|
27
|
-
1. Store all the detector data collected by the detector with prefix
|
|
28
|
-
`'det'` over a rotation series collected at SMB in the variable
|
|
29
|
-
`data`:
|
|
30
|
-
```python
|
|
31
|
-
from CHAP.utils.scanparsers import SMBRotationScanParser
|
|
32
|
-
sp = SMBRotationScanParser('/path/to/smb/rotation/spec/file', 1)
|
|
33
|
-
data = sp.get_detector_data('det')
|
|
34
|
-
```
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
# System modules
|
|
38
|
-
from csv import reader
|
|
39
|
-
from fnmatch import filter as fnmatch_filter
|
|
40
|
-
from functools import cache
|
|
41
|
-
from json import load
|
|
42
|
-
import os
|
|
43
|
-
import re
|
|
44
|
-
|
|
45
|
-
# Third party modules
|
|
46
|
-
import numpy as np
|
|
47
|
-
from pyspec.file.spec import FileSpec
|
|
48
|
-
from pyspec.file.tiff import TiffFile
|
|
49
|
-
|
|
50
|
-
@cache
|
|
51
|
-
def filespec(spec_file_name):
|
|
52
|
-
return FileSpec(spec_file_name)
|
|
53
|
-
|
|
54
|
-
class ScanParser:
|
|
55
|
-
"""Partial implementation of a class representing a SPEC scan and
|
|
56
|
-
some of its metadata.
|
|
57
|
-
|
|
58
|
-
:param spec_file_name: path to a SPEC file on the CLASSE DAQ
|
|
59
|
-
:type spec_file_name: str
|
|
60
|
-
:param scan_number: the number of a scan in the SPEC file provided
|
|
61
|
-
with `spec_file_name`
|
|
62
|
-
:type scan_number: int
|
|
63
|
-
"""
|
|
64
|
-
|
|
65
|
-
def __init__(self,
|
|
66
|
-
spec_file_name:str,
|
|
67
|
-
scan_number:int):
|
|
68
|
-
"""Constructor method"""
|
|
69
|
-
|
|
70
|
-
self.spec_file_name = spec_file_name
|
|
71
|
-
self.scan_number = scan_number
|
|
72
|
-
|
|
73
|
-
self._scan_path = None
|
|
74
|
-
self._scan_name = None
|
|
75
|
-
self._scan_title = None
|
|
76
|
-
|
|
77
|
-
self._spec_scan = None
|
|
78
|
-
self._spec_command = None
|
|
79
|
-
self._spec_macro = None
|
|
80
|
-
self._spec_args = None
|
|
81
|
-
self._spec_scan_npts = None
|
|
82
|
-
self._spec_scan_data = None
|
|
83
|
-
self._spec_positioner_values = None
|
|
84
|
-
|
|
85
|
-
self._detector_data_path = None
|
|
86
|
-
|
|
87
|
-
if isinstance(self, FMBRotationScanParser) and scan_number > 1:
|
|
88
|
-
scanparser = FMBRotationScanParser(spec_file_name, scan_number-1)
|
|
89
|
-
if (scanparser.spec_macro in ('rams4_step_ome', 'rams4_fly_ome')
|
|
90
|
-
and len(scanparser.spec_args) == 5):
|
|
91
|
-
self._rams4_args = scanparser.spec_args
|
|
92
|
-
|
|
93
|
-
def __repr_(self):
|
|
94
|
-
return (f'{self.__class__.__name__}'
|
|
95
|
-
f'({self.spec_file_name}, {self.scan_number}) '
|
|
96
|
-
f'-- {self.spec_command}')
|
|
97
|
-
|
|
98
|
-
@property
|
|
99
|
-
def spec_file(self):
|
|
100
|
-
# NB This FileSpec instance is not stored as a private
|
|
101
|
-
# attribute because it cannot be pickled (and therefore could
|
|
102
|
-
# cause problems for parallel code that uses ScanParsers).
|
|
103
|
-
return filespec(self.spec_file_name)
|
|
104
|
-
|
|
105
|
-
@property
|
|
106
|
-
def scan_path(self):
|
|
107
|
-
if self._scan_path is None:
|
|
108
|
-
self._scan_path = self.get_scan_path()
|
|
109
|
-
return self._scan_path
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def scan_name(self):
|
|
113
|
-
if self._scan_name is None:
|
|
114
|
-
self._scan_name = self.get_scan_name()
|
|
115
|
-
return self._scan_name
|
|
116
|
-
|
|
117
|
-
@property
|
|
118
|
-
def scan_title(self):
|
|
119
|
-
if self._scan_title is None:
|
|
120
|
-
self._scan_title = self.get_scan_title()
|
|
121
|
-
return self._scan_title
|
|
122
|
-
|
|
123
|
-
@property
|
|
124
|
-
def spec_scan(self):
|
|
125
|
-
if self._spec_scan is None:
|
|
126
|
-
self._spec_scan = self.get_spec_scan()
|
|
127
|
-
return self._spec_scan
|
|
128
|
-
|
|
129
|
-
@property
|
|
130
|
-
def spec_command(self):
|
|
131
|
-
if self._spec_command is None:
|
|
132
|
-
self._spec_command = self.get_spec_command()
|
|
133
|
-
return self._spec_command
|
|
134
|
-
|
|
135
|
-
@property
|
|
136
|
-
def spec_macro(self):
|
|
137
|
-
if self._spec_macro is None:
|
|
138
|
-
self._spec_macro = self.get_spec_macro()
|
|
139
|
-
return self._spec_macro
|
|
140
|
-
|
|
141
|
-
@property
|
|
142
|
-
def spec_args(self):
|
|
143
|
-
if self._spec_args is None:
|
|
144
|
-
self._spec_args = self.get_spec_args()
|
|
145
|
-
return self._spec_args
|
|
146
|
-
|
|
147
|
-
@property
|
|
148
|
-
def spec_scan_npts(self):
|
|
149
|
-
if self._spec_scan_npts is None:
|
|
150
|
-
self._spec_scan_npts = self.get_spec_scan_npts()
|
|
151
|
-
return self._spec_scan_npts
|
|
152
|
-
|
|
153
|
-
@property
|
|
154
|
-
def spec_scan_data(self):
|
|
155
|
-
if self._spec_scan_data is None:
|
|
156
|
-
self._spec_scan_data = self.get_spec_scan_data()
|
|
157
|
-
return self._spec_scan_data
|
|
158
|
-
|
|
159
|
-
@property
|
|
160
|
-
def spec_positioner_values(self):
|
|
161
|
-
if self._spec_positioner_values is None:
|
|
162
|
-
self._spec_positioner_values = self.get_spec_positioner_values()
|
|
163
|
-
return self._spec_positioner_values
|
|
164
|
-
|
|
165
|
-
@property
|
|
166
|
-
def detector_data_path(self):
|
|
167
|
-
if self._detector_data_path is None:
|
|
168
|
-
self._detector_data_path = self.get_detector_data_path()
|
|
169
|
-
return self._detector_data_path
|
|
170
|
-
|
|
171
|
-
def get_scan_path(self):
|
|
172
|
-
"""Return the name of the directory containining the SPEC file
|
|
173
|
-
for this scan.
|
|
174
|
-
|
|
175
|
-
:rtype: str
|
|
176
|
-
"""
|
|
177
|
-
return os.path.dirname(self.spec_file_name)
|
|
178
|
-
|
|
179
|
-
def get_scan_name(self):
|
|
180
|
-
"""Return the name of this SPEC scan (not unique to scans
|
|
181
|
-
within a single spec file).
|
|
182
|
-
|
|
183
|
-
:rtype: str
|
|
184
|
-
"""
|
|
185
|
-
raise NotImplementedError
|
|
186
|
-
|
|
187
|
-
def get_scan_title(self):
|
|
188
|
-
"""Return the title of this spec scan (unique to each scan
|
|
189
|
-
within a spec file).
|
|
190
|
-
|
|
191
|
-
:rtype: str
|
|
192
|
-
"""
|
|
193
|
-
raise NotImplementedError
|
|
194
|
-
|
|
195
|
-
def get_spec_scan(self):
|
|
196
|
-
"""Return the `pyspec.file.spec.Scan` object parsed from the
|
|
197
|
-
spec file and scan number provided to the constructor.
|
|
198
|
-
|
|
199
|
-
:rtype: pyspec.file.spec.Scan
|
|
200
|
-
"""
|
|
201
|
-
return self.spec_file.getScanByNumber(self.scan_number)
|
|
202
|
-
|
|
203
|
-
def get_spec_command(self):
|
|
204
|
-
"""Return the string command of this SPEC scan.
|
|
205
|
-
|
|
206
|
-
:rtype: str
|
|
207
|
-
"""
|
|
208
|
-
return self.spec_scan.command
|
|
209
|
-
|
|
210
|
-
def get_spec_macro(self):
|
|
211
|
-
"""Return the macro used in this scan's SPEC command.
|
|
212
|
-
|
|
213
|
-
:rtype: str
|
|
214
|
-
"""
|
|
215
|
-
return self.spec_command.split()[0]
|
|
216
|
-
|
|
217
|
-
def get_spec_args(self):
|
|
218
|
-
"""Return a list of the arguments provided to the macro for
|
|
219
|
-
this SPEC scan.
|
|
220
|
-
|
|
221
|
-
:rtype: list[str]
|
|
222
|
-
"""
|
|
223
|
-
return self.spec_command.split()[1:]
|
|
224
|
-
|
|
225
|
-
def get_spec_scan_npts(self):
|
|
226
|
-
"""Return the number of points collected in this SPEC scan
|
|
227
|
-
|
|
228
|
-
:rtype: int
|
|
229
|
-
"""
|
|
230
|
-
raise NotImplementedError
|
|
231
|
-
|
|
232
|
-
def get_spec_scan_data(self):
|
|
233
|
-
"""Return a dictionary of all the counter data collected by
|
|
234
|
-
this SPEC scan.
|
|
235
|
-
|
|
236
|
-
:rtype: dict[str, numpy.ndarray]
|
|
237
|
-
"""
|
|
238
|
-
return dict(zip(self.spec_scan.labels, self.spec_scan.data.T))
|
|
239
|
-
|
|
240
|
-
def get_spec_positioner_values(self):
|
|
241
|
-
"""Return a dictionary of all the SPEC positioner values
|
|
242
|
-
recorded by SPEC just before the scan began.
|
|
243
|
-
|
|
244
|
-
:rtype: dict[str,str]
|
|
245
|
-
"""
|
|
246
|
-
positioner_values = dict(self.spec_scan.motor_positions)
|
|
247
|
-
names = list(positioner_values.keys())
|
|
248
|
-
mnemonics = self.spec_scan.motors
|
|
249
|
-
if mnemonics is not None:
|
|
250
|
-
for name,mnemonic in zip(names,mnemonics):
|
|
251
|
-
if name != mnemonic:
|
|
252
|
-
positioner_values[mnemonic] = positioner_values[name]
|
|
253
|
-
return positioner_values
|
|
254
|
-
|
|
255
|
-
def get_detector_data_path(self):
|
|
256
|
-
"""Return the name of the directory containing detector data
|
|
257
|
-
collected by this scan.
|
|
258
|
-
|
|
259
|
-
:rtype: str
|
|
260
|
-
"""
|
|
261
|
-
raise NotImplementedError
|
|
262
|
-
|
|
263
|
-
def get_detector_data_file(self, detector_prefix, scan_step_index:int):
|
|
264
|
-
"""Return the name of the file containing detector data
|
|
265
|
-
collected at a certain step of this scan.
|
|
266
|
-
|
|
267
|
-
:param detector_prefix: the prefix used in filenames for the
|
|
268
|
-
detector
|
|
269
|
-
:type detector_prefix: str
|
|
270
|
-
:param scan_step_index: the index of the point in this scan
|
|
271
|
-
whose detector file name should be returned.
|
|
272
|
-
:type scan_step_index: int
|
|
273
|
-
:rtype: str
|
|
274
|
-
"""
|
|
275
|
-
raise NotImplementedError
|
|
276
|
-
|
|
277
|
-
def get_detector_data(self, detector_prefix, scan_step_index:int):
|
|
278
|
-
"""Return the detector data collected at a certain step of
|
|
279
|
-
this scan.
|
|
280
|
-
|
|
281
|
-
:param detector_prefix: the prefix used in filenames for the
|
|
282
|
-
detector
|
|
283
|
-
:type detector_prefix: str
|
|
284
|
-
:param scan_step_index: the index of the point in this scan
|
|
285
|
-
whose detector data should be returned.
|
|
286
|
-
:type scan_step_index: int
|
|
287
|
-
:rtype: numpy.ndarray
|
|
288
|
-
"""
|
|
289
|
-
raise NotImplementedError
|
|
290
|
-
|
|
291
|
-
def get_spec_positioner_value(self, positioner_name):
|
|
292
|
-
"""Return the value of a spec positioner recorded before this
|
|
293
|
-
scan began.
|
|
294
|
-
|
|
295
|
-
:param positioner_name: the name or mnemonic of a SPEC motor
|
|
296
|
-
whose position should be returned.
|
|
297
|
-
:raises KeyError: if `positioner_name` is not the name or
|
|
298
|
-
mnemonic of a SPEC motor recorded for this scan.
|
|
299
|
-
:raises ValueError: if the recorded string value of the
|
|
300
|
-
positioner in the SPEC file cannot be converted to a
|
|
301
|
-
float.
|
|
302
|
-
:rtype: float
|
|
303
|
-
"""
|
|
304
|
-
try:
|
|
305
|
-
positioner_value = self.spec_positioner_values[positioner_name]
|
|
306
|
-
positioner_value = float(positioner_value)
|
|
307
|
-
except KeyError:
|
|
308
|
-
raise KeyError(f'{self.scan_title}: motor {positioner_name} '
|
|
309
|
-
'not found for this scan')
|
|
310
|
-
except ValueError:
|
|
311
|
-
raise ValueError(f'{self.scan_title}: could not convert value of'
|
|
312
|
-
f' {positioner_name} to float: '
|
|
313
|
-
f'{positioner_value}')
|
|
314
|
-
return positioner_value
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
class FMBScanParser(ScanParser):
|
|
318
|
-
"""Partial implementation of a class representing a SPEC scan
|
|
319
|
-
collected at FMB.
|
|
320
|
-
"""
|
|
321
|
-
|
|
322
|
-
def get_scan_name(self):
|
|
323
|
-
return os.path.basename(self.spec_file.abspath)
|
|
324
|
-
|
|
325
|
-
def get_scan_title(self):
|
|
326
|
-
return f'{self.scan_name}_{self.scan_number:03d}'
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
class SMBScanParser(ScanParser):
|
|
330
|
-
"""Partial implementation of a class representing a SPEC scan
|
|
331
|
-
collected at SMB or FAST.
|
|
332
|
-
"""
|
|
333
|
-
|
|
334
|
-
def __init__(self, spec_file_name, scan_number):
|
|
335
|
-
super().__init__(spec_file_name, scan_number)
|
|
336
|
-
|
|
337
|
-
self._pars = None
|
|
338
|
-
self._par_file_pattern = f'*-*-{self.scan_name}'
|
|
339
|
-
self._par_file = None
|
|
340
|
-
|
|
341
|
-
def get_scan_name(self):
|
|
342
|
-
return os.path.basename(self.scan_path)
|
|
343
|
-
|
|
344
|
-
def get_scan_title(self):
|
|
345
|
-
return f'{self.scan_name}_{self.scan_number}'
|
|
346
|
-
|
|
347
|
-
@property
|
|
348
|
-
def pars(self):
|
|
349
|
-
if self._pars is None:
|
|
350
|
-
self._pars = self.get_pars()
|
|
351
|
-
return self._pars
|
|
352
|
-
|
|
353
|
-
def get_pars(self):
|
|
354
|
-
"""Return a dictionary of values recorded in the .par file
|
|
355
|
-
associated with this SPEC scan.
|
|
356
|
-
|
|
357
|
-
:rtype: dict[str,object]
|
|
358
|
-
"""
|
|
359
|
-
# JSON file holds titles for columns in the par file
|
|
360
|
-
json_files = fnmatch_filter(
|
|
361
|
-
os.listdir(self.scan_path),
|
|
362
|
-
f'{self._par_file_pattern}.json')
|
|
363
|
-
if not json_files:
|
|
364
|
-
json_files = fnmatch_filter(
|
|
365
|
-
os.listdir(self.scan_path),
|
|
366
|
-
f'*.json')
|
|
367
|
-
if len(json_files) != 1:
|
|
368
|
-
raise RuntimeError(f'{self.scan_title}: cannot find the '
|
|
369
|
-
'.json file to decode the .par file')
|
|
370
|
-
with open(os.path.join(self.scan_path, json_files[0])) as json_file:
|
|
371
|
-
par_file_cols = load(json_file)
|
|
372
|
-
try:
|
|
373
|
-
par_col_names = list(par_file_cols.values())
|
|
374
|
-
scann_val_idx = par_col_names.index('SCAN_N')
|
|
375
|
-
scann_col_idx = int(list(par_file_cols.keys())[scann_val_idx])
|
|
376
|
-
except:
|
|
377
|
-
raise RuntimeError(f'{self.scan_title}: cannot find scan pars '
|
|
378
|
-
'without a "SCAN_N" column in the par file')
|
|
379
|
-
|
|
380
|
-
if getattr(self, '_par_file'):
|
|
381
|
-
par_file = self._par_file
|
|
382
|
-
else:
|
|
383
|
-
par_files = fnmatch_filter(
|
|
384
|
-
os.listdir(self.scan_path),
|
|
385
|
-
f'{self._par_file_pattern}.par')
|
|
386
|
-
if len(par_files) != 1:
|
|
387
|
-
raise RuntimeError(f'{self.scan_title}: cannot find the .par '
|
|
388
|
-
'file for this scan directory')
|
|
389
|
-
par_file = os.path.join(self.scan_path, par_files[0])
|
|
390
|
-
self._par_file = par_file
|
|
391
|
-
par_dict = None
|
|
392
|
-
with open(par_file) as f:
|
|
393
|
-
par_reader = reader(f, delimiter=' ')
|
|
394
|
-
for row in par_reader:
|
|
395
|
-
if len(row) == len(par_col_names):
|
|
396
|
-
row_scann = int(row[scann_col_idx])
|
|
397
|
-
if row_scann == self.scan_number:
|
|
398
|
-
par_dict = {}
|
|
399
|
-
for par_col_idx,par_col_name in par_file_cols.items():
|
|
400
|
-
# Convert the string par value from the
|
|
401
|
-
# file to an int or float, if possible.
|
|
402
|
-
par_value = row[int(par_col_idx)]
|
|
403
|
-
try:
|
|
404
|
-
par_value = int(par_value)
|
|
405
|
-
except ValueError:
|
|
406
|
-
try:
|
|
407
|
-
par_value = float(par_value)
|
|
408
|
-
except:
|
|
409
|
-
pass
|
|
410
|
-
par_dict[par_col_name] = par_value
|
|
411
|
-
if par_dict is None:
|
|
412
|
-
raise RuntimeError(f'{self.scan_title}: could not find scan pars '
|
|
413
|
-
f'for scan number {self.scan_number}')
|
|
414
|
-
return par_dict
|
|
415
|
-
|
|
416
|
-
def get_counter_gain(self, counter_name):
|
|
417
|
-
"""Return the gain of a counter as recorded in the user lines
|
|
418
|
-
of a scan in a SPEC file converted to nA/V.
|
|
419
|
-
|
|
420
|
-
:param counter_name: the name of the counter
|
|
421
|
-
:type counter_name: str
|
|
422
|
-
:rtype: str
|
|
423
|
-
"""
|
|
424
|
-
counter_gain = None
|
|
425
|
-
for comment in self.spec_scan.comments + self.spec_scan.user_lines:
|
|
426
|
-
match = re.search(
|
|
427
|
-
f'{counter_name} gain: ' # start of counter gain comments
|
|
428
|
-
'(?P<gain_value>\d+) ' # gain numerical value
|
|
429
|
-
'(?P<unit_prefix>[m|u|n])A/V', # gain units
|
|
430
|
-
comment)
|
|
431
|
-
if match:
|
|
432
|
-
unit_prefix = match['unit_prefix']
|
|
433
|
-
gain_scalar = 1 if unit_prefix == 'n' \
|
|
434
|
-
else 1e3 if unit_prefix == 'u' else 1e6
|
|
435
|
-
counter_gain = f'{float(match["gain_value"])*gain_scalar} nA/V'
|
|
436
|
-
break
|
|
437
|
-
|
|
438
|
-
if counter_gain is None:
|
|
439
|
-
raise RuntimeError(f'{self.scan_title}: could not get gain for '
|
|
440
|
-
f'counter {counter_name}')
|
|
441
|
-
return counter_gain
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
class LinearScanParser(ScanParser):
|
|
445
|
-
"""Partial implementation of a class representing a typical line
|
|
446
|
-
or mesh scan in SPEC.
|
|
447
|
-
"""
|
|
448
|
-
def __init__(self, spec_file_name, scan_number):
|
|
449
|
-
super().__init__(spec_file_name, scan_number)
|
|
450
|
-
|
|
451
|
-
self._spec_scan_motor_mnes = None
|
|
452
|
-
self._spec_scan_motor_vals = None
|
|
453
|
-
self._spec_scan_motor_vals_relative = None
|
|
454
|
-
self._spec_scan_shape = None
|
|
455
|
-
self._spec_scan_dwell = None
|
|
456
|
-
|
|
457
|
-
@property
|
|
458
|
-
def spec_scan_motor_mnes(self):
|
|
459
|
-
if self._spec_scan_motor_mnes is None:
|
|
460
|
-
self._spec_scan_motor_mnes = self.get_spec_scan_motor_mnes()
|
|
461
|
-
return self._spec_scan_motor_mnes
|
|
462
|
-
|
|
463
|
-
@property
|
|
464
|
-
def spec_scan_motor_vals(self):
|
|
465
|
-
if self._spec_scan_motor_vals is None:
|
|
466
|
-
self._spec_scan_motor_vals = self.get_spec_scan_motor_vals(
|
|
467
|
-
relative=False)
|
|
468
|
-
return self._spec_scan_motor_vals
|
|
469
|
-
|
|
470
|
-
@property
|
|
471
|
-
def spec_scan_motor_vals_relative(self):
|
|
472
|
-
if self._spec_scan_motor_vals_relative is None:
|
|
473
|
-
self._spec_scan_motor_vals_relative = \
|
|
474
|
-
self.get_spec_scan_motor_vals(relative=True)
|
|
475
|
-
return self._spec_scan_motor_vals_relative
|
|
476
|
-
|
|
477
|
-
@property
|
|
478
|
-
def spec_scan_shape(self):
|
|
479
|
-
if self._spec_scan_shape is None:
|
|
480
|
-
self._spec_scan_shape = self.get_spec_scan_shape()
|
|
481
|
-
return self._spec_scan_shape
|
|
482
|
-
|
|
483
|
-
@property
|
|
484
|
-
def spec_scan_dwell(self):
|
|
485
|
-
if self._spec_scan_dwell is None:
|
|
486
|
-
self._spec_scan_dwell = self.get_spec_scan_dwell()
|
|
487
|
-
return self._spec_scan_dwell
|
|
488
|
-
|
|
489
|
-
def get_spec_scan_motor_mnes(self):
|
|
490
|
-
"""Return the mnemonics of the SPEC motor(s) provided to the
|
|
491
|
-
macro for this scan. If there is more than one motor scanned
|
|
492
|
-
(in a "flymesh" scan, for example), the order of motors in the
|
|
493
|
-
returned tuple will go from the fastest moving motor first to
|
|
494
|
-
the slowest moving motor last.
|
|
495
|
-
|
|
496
|
-
:rtype: tuple
|
|
497
|
-
"""
|
|
498
|
-
raise NotImplementedError
|
|
499
|
-
|
|
500
|
-
def get_spec_scan_motor_vals(self, relative=False):
|
|
501
|
-
"""Return the values visited by each of the scanned motors. If
|
|
502
|
-
there is more than one motor scanned (in a "flymesh" scan, for
|
|
503
|
-
example), the order of motor values in the returned tuple will
|
|
504
|
-
go from the fastest moving motor's values first to the slowest
|
|
505
|
-
moving motor's values last.
|
|
506
|
-
|
|
507
|
-
:param relative: If `True`, return scanned motor positions
|
|
508
|
-
*relative* to the scanned motors' positions before the scan
|
|
509
|
-
started, defaults to False.
|
|
510
|
-
:type relative: bool, optional
|
|
511
|
-
:rtype: tuple
|
|
512
|
-
"""
|
|
513
|
-
raise NotImplementedError
|
|
514
|
-
|
|
515
|
-
def get_spec_scan_shape(self):
|
|
516
|
-
"""Return the number of points visited by each of the scanned
|
|
517
|
-
motors. If there is more than one motor scanned (in a
|
|
518
|
-
"flymesh" scan, for example), the order of number of motor
|
|
519
|
-
values in the returned tuple will go from the number of points
|
|
520
|
-
visited by the fastest moving motor first to the the number of
|
|
521
|
-
points visited by the slowest moving motor last.
|
|
522
|
-
|
|
523
|
-
:rtype: tuple
|
|
524
|
-
"""
|
|
525
|
-
raise NotImplementedError
|
|
526
|
-
|
|
527
|
-
def get_spec_scan_dwell(self):
|
|
528
|
-
"""Return the dwell time for each point in the scan as it
|
|
529
|
-
appears in the command string.
|
|
530
|
-
|
|
531
|
-
:rtype: float
|
|
532
|
-
"""
|
|
533
|
-
raise NotImplementedError
|
|
534
|
-
|
|
535
|
-
def get_spec_scan_npts(self):
|
|
536
|
-
"""Return the number of points collected in this SPEC scan.
|
|
537
|
-
|
|
538
|
-
:rtype: int
|
|
539
|
-
"""
|
|
540
|
-
return np.prod(self.spec_scan_shape)
|
|
541
|
-
|
|
542
|
-
def get_scan_step(self, scan_step_index:int):
|
|
543
|
-
"""Return the index of each motor coordinate corresponding to
|
|
544
|
-
the index of a single point in the scan. If there is more than
|
|
545
|
-
one motor scanned (in a "flymesh" scan, for example), the
|
|
546
|
-
order of indices in the returned tuple will go from the index
|
|
547
|
-
of the value of the fastest moving motor first to the index of
|
|
548
|
-
the value of the slowest moving motor last.
|
|
549
|
-
|
|
550
|
-
:param scan_step_index: the index of a single point in the
|
|
551
|
-
scan.
|
|
552
|
-
:type scan_step_index: int
|
|
553
|
-
:rtype: tuple
|
|
554
|
-
"""
|
|
555
|
-
scan_steps = np.ndindex(self.spec_scan_shape[::-1])
|
|
556
|
-
i = 0
|
|
557
|
-
while i <= scan_step_index:
|
|
558
|
-
scan_step = next(scan_steps)
|
|
559
|
-
i += 1
|
|
560
|
-
return scan_step
|
|
561
|
-
|
|
562
|
-
def get_scan_step_index(self, scan_step:tuple):
|
|
563
|
-
"""Return the index of a single scan point corresponding to a
|
|
564
|
-
tuple of indices for each scanned motor coordinate.
|
|
565
|
-
|
|
566
|
-
:param scan_step: a tuple of the indices of each scanned motor
|
|
567
|
-
coordinate. If there is more than one motor scanned (in a
|
|
568
|
-
"flymesh" scan, for example), the order of indices should
|
|
569
|
-
go from the index of the value of the fastest moving motor
|
|
570
|
-
first to the index of the value of the slowest moving
|
|
571
|
-
motor last.
|
|
572
|
-
:type scan_step: tuple
|
|
573
|
-
:trype: int
|
|
574
|
-
"""
|
|
575
|
-
scan_steps = np.ndindex(self.spec_scan_shape[::-1])
|
|
576
|
-
scan_step_found = False
|
|
577
|
-
scan_step_index = -1
|
|
578
|
-
while not scan_step_found:
|
|
579
|
-
next_scan_step = next(scan_steps)
|
|
580
|
-
scan_step_index += 1
|
|
581
|
-
if next_scan_step == scan_step:
|
|
582
|
-
scan_step_found = True
|
|
583
|
-
break
|
|
584
|
-
return scan_step_index
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
class FMBLinearScanParser(LinearScanParser, FMBScanParser):
|
|
588
|
-
"""Partial implementation of a class representing a typical line
|
|
589
|
-
or mesh scan in SPEC collected at FMB.
|
|
590
|
-
"""
|
|
591
|
-
|
|
592
|
-
def get_spec_scan_motor_mnes(self):
|
|
593
|
-
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
594
|
-
m1_mne = self.spec_args[0]
|
|
595
|
-
try:
|
|
596
|
-
# Try post-summer-2022 format
|
|
597
|
-
dwell = float(self.spec_args[4])
|
|
598
|
-
except:
|
|
599
|
-
# Accommodate pre-summer-2022 format
|
|
600
|
-
m2_mne_i = 4
|
|
601
|
-
else:
|
|
602
|
-
m2_mne_i = 5
|
|
603
|
-
m2_mne = self.spec_args[m2_mne_i]
|
|
604
|
-
return (m1_mne, m2_mne)
|
|
605
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
606
|
-
return (self.spec_args[0],)
|
|
607
|
-
if self.spec_macro in ('tseries', 'loopscan'):
|
|
608
|
-
return ('Time',)
|
|
609
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
610
|
-
f'for scans of type {self.spec_macro}')
|
|
611
|
-
|
|
612
|
-
def get_spec_scan_motor_vals(self, relative=False):
|
|
613
|
-
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
614
|
-
m1_start = float(self.spec_args[1])
|
|
615
|
-
m1_end = float(self.spec_args[2])
|
|
616
|
-
m1_npt = int(self.spec_args[3]) + 1
|
|
617
|
-
try:
|
|
618
|
-
# Try post-summer-2022 format
|
|
619
|
-
dwell = float(self.spec_args[4])
|
|
620
|
-
except:
|
|
621
|
-
# Accommodate pre-summer-2022 format
|
|
622
|
-
m2_start_i = 5
|
|
623
|
-
m2_end_i = 6
|
|
624
|
-
m2_nint_i = 7
|
|
625
|
-
else:
|
|
626
|
-
m2_start_i = 6
|
|
627
|
-
m2_end_i = 7
|
|
628
|
-
m2_nint_i = 8
|
|
629
|
-
m2_start = float(self.spec_args[m2_start_i])
|
|
630
|
-
m2_end = float(self.spec_args[m2_end_i])
|
|
631
|
-
m2_npt = int(self.spec_args[m2_nint_i]) + 1
|
|
632
|
-
fast_mot_vals = np.linspace(m1_start, m1_end, m1_npt)
|
|
633
|
-
slow_mot_vals = np.linspace(m2_start, m2_end, m2_npt)
|
|
634
|
-
if relative:
|
|
635
|
-
fast_mot_vals -= self.get_spec_positioner_value(
|
|
636
|
-
self.spec_scan_motor_mnes[0])
|
|
637
|
-
slow_mot_vals -= self.get_spec_positioner_value(
|
|
638
|
-
self.spec_scan_motor_mnes[1])
|
|
639
|
-
return (fast_mot_vals, slow_mot_vals)
|
|
640
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
641
|
-
mot_vals = np.linspace(float(self.spec_args[1]),
|
|
642
|
-
float(self.spec_args[2]),
|
|
643
|
-
int(self.spec_args[3])+1)
|
|
644
|
-
if relative:
|
|
645
|
-
mot_vals -= self.get_spec_positioner_value(
|
|
646
|
-
self.spec_scan_motor_mnes[0])
|
|
647
|
-
return (mot_vals,)
|
|
648
|
-
if self.spec_macro in ('tseries', 'loopscan'):
|
|
649
|
-
return (self.spec_scan.data[:,0],)
|
|
650
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
651
|
-
f'for scans of type {self.spec_macro}')
|
|
652
|
-
|
|
653
|
-
def get_spec_scan_shape(self):
|
|
654
|
-
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
655
|
-
fast_mot_npts = int(self.spec_args[3]) + 1
|
|
656
|
-
try:
|
|
657
|
-
# Try post-summer-2022 format
|
|
658
|
-
dwell = float(self.spec_args[4])
|
|
659
|
-
except:
|
|
660
|
-
# Accommodate pre-summer-2022 format
|
|
661
|
-
m2_nint_i = 7
|
|
662
|
-
else:
|
|
663
|
-
m2_nint_i = 8
|
|
664
|
-
slow_mot_npts = int(self.spec_args[m2_nint_i]) + 1
|
|
665
|
-
return (fast_mot_npts, slow_mot_npts)
|
|
666
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
667
|
-
mot_npts = int(self.spec_args[3])+1
|
|
668
|
-
return (mot_npts,)
|
|
669
|
-
if self.spec_macro in ('tseries', 'loopscan'):
|
|
670
|
-
return (len(np.array(self.spec_scan.data[:,0])),)
|
|
671
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine scan shape '
|
|
672
|
-
f'for scans of type {self.spec_macro}')
|
|
673
|
-
|
|
674
|
-
def get_spec_scan_dwell(self):
|
|
675
|
-
if self.macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
676
|
-
try:
|
|
677
|
-
# Try post-summer-2022 format
|
|
678
|
-
dwell = float(self.spec_args[4])
|
|
679
|
-
except:
|
|
680
|
-
# Accommodate pre-summer-2022 format
|
|
681
|
-
dwell = float(self.spec_args[8])
|
|
682
|
-
return dwell
|
|
683
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
684
|
-
return float(self.spec_args[4])
|
|
685
|
-
if self.spec_macro in ('tseries', 'loopscan'):
|
|
686
|
-
return float(self.spec_args[1])
|
|
687
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine dwell for '
|
|
688
|
-
f'scans of type {self.spec_macro}')
|
|
689
|
-
|
|
690
|
-
def get_detector_data_path(self):
|
|
691
|
-
return os.path.join(self.scan_path, self.scan_title)
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
@cache
|
|
696
|
-
def list_fmb_saxswaxs_detector_files(detector_data_path, detector_prefix):
|
|
697
|
-
"""Return a sorted list of all data files for the given detector
|
|
698
|
-
in the given directory. This function is cached to improve
|
|
699
|
-
performace for carrying our full FAMB SAXS/WAXS data-processing
|
|
700
|
-
workflows.
|
|
701
|
-
|
|
702
|
-
:param detector_data_path: directory in which to look for detector
|
|
703
|
-
data files
|
|
704
|
-
:type detector_data_path: str
|
|
705
|
-
:param detector_prefix: detector name to list files for
|
|
706
|
-
:type detector_prefix: str
|
|
707
|
-
:return: list of detector filenames
|
|
708
|
-
:rtype: list[str]
|
|
709
|
-
"""
|
|
710
|
-
return sorted(
|
|
711
|
-
[f for f in os.listdir(detector_data_path)
|
|
712
|
-
if detector_prefix in f
|
|
713
|
-
and not f.endswith('.log')])
|
|
714
|
-
|
|
715
|
-
class FMBSAXSWAXSScanParser(FMBLinearScanParser):
|
|
716
|
-
"""Concrete implementation of a class representing a scan taken
|
|
717
|
-
with the typical SAXS/WAXS setup at FMB.
|
|
718
|
-
"""
|
|
719
|
-
|
|
720
|
-
def get_scan_title(self):
|
|
721
|
-
return f'{self.scan_name}_{self.scan_number:03d}'
|
|
722
|
-
|
|
723
|
-
def get_detector_data_file(self, detector_prefix, scan_step_index:int):
|
|
724
|
-
detector_files = list_fmb_saxswaxs_detector_files(
|
|
725
|
-
self.detector_data_path, detector_prefix)
|
|
726
|
-
if len(detector_files) == self.spec_scan_npts:
|
|
727
|
-
return os.path.join(
|
|
728
|
-
self.detector_data_path, detector_files[scan_step_index])
|
|
729
|
-
else:
|
|
730
|
-
scan_step = self.get_scan_step(scan_step_index)
|
|
731
|
-
for f in detector_files:
|
|
732
|
-
filename, _ = os.path.splitext(f)
|
|
733
|
-
file_indices = tuple(
|
|
734
|
-
[int(i) for i in \
|
|
735
|
-
filename.split('_')[-len(self.spec_scan_shape):]])
|
|
736
|
-
if file_indices == scan_step:
|
|
737
|
-
return os.path.join(self.detector_data_path, f)
|
|
738
|
-
raise RuntimeError(
|
|
739
|
-
'Could not find a matching detector data file for detector '
|
|
740
|
-
+ f'{detector_prefix} at scan step index {scan_step_index}')
|
|
741
|
-
|
|
742
|
-
def get_detector_data(self, detector_prefix, scan_step_index:int):
|
|
743
|
-
import fabio
|
|
744
|
-
image_file = self.get_detector_data_file(detector_prefix,
|
|
745
|
-
scan_step_index)
|
|
746
|
-
with fabio.open(image_file) as det_file:
|
|
747
|
-
image_data = det_file.data
|
|
748
|
-
return image_data
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
class FMBXRFScanParser(FMBLinearScanParser):
|
|
752
|
-
"""Concrete implementation of a class representing a scan taken
|
|
753
|
-
with the typical XRF setup at FMB.
|
|
754
|
-
"""
|
|
755
|
-
|
|
756
|
-
def get_scan_title(self):
|
|
757
|
-
return f'{self.scan_name}_scan{self.scan_number}'
|
|
758
|
-
|
|
759
|
-
def get_detector_data_file(self, detector_prefix, scan_step_index:int):
|
|
760
|
-
scan_step = self.get_scan_step(scan_step_index)
|
|
761
|
-
file_name = f'scan{self.scan_number}_{scan_step[0]:03d}.hdf5'
|
|
762
|
-
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
763
|
-
if os.path.isfile(file_name_full):
|
|
764
|
-
return file_name_full
|
|
765
|
-
raise RuntimeError(f'{self.scan_title}: could not find detector image '
|
|
766
|
-
f'file for detector {detector_prefix} scan step '
|
|
767
|
-
f'({scan_step_index})')
|
|
768
|
-
|
|
769
|
-
def get_detector_data(self, detector_prefix, scan_step_index:int):
|
|
770
|
-
# Third party modules
|
|
771
|
-
from h5py import File
|
|
772
|
-
|
|
773
|
-
detector_file = self.get_detector_data_file(
|
|
774
|
-
detector_prefix, scan_step_index)
|
|
775
|
-
scan_step = self.get_scan_step(scan_step_index)
|
|
776
|
-
with File(detector_file) as h5_file:
|
|
777
|
-
detector_data = \
|
|
778
|
-
h5_file['/entry/instrument/detector/data'][scan_step[0]]
|
|
779
|
-
return detector_data
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
class SMBLinearScanParser(LinearScanParser, SMBScanParser):
|
|
783
|
-
"""Concrete implementation of a class representing a scan taken
|
|
784
|
-
with the typical powder diffraction setup at SMB.
|
|
785
|
-
"""
|
|
786
|
-
|
|
787
|
-
def get_spec_scan_dwell(self):
|
|
788
|
-
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
789
|
-
try:
|
|
790
|
-
# Try post-summer-2022 format
|
|
791
|
-
dwell = float(self.spec_args[4])
|
|
792
|
-
except:
|
|
793
|
-
# Accommodate pre-summer-2022 format
|
|
794
|
-
dwell = float(self.spec_args[8])
|
|
795
|
-
return dwell
|
|
796
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
797
|
-
return float(self.spec_args[4])
|
|
798
|
-
if self.spec_macro == 'tseries':
|
|
799
|
-
return float(self.spec_args[1])
|
|
800
|
-
if self.spec_macro == 'wbslew_scan':
|
|
801
|
-
return float(self.spec_args[3])
|
|
802
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine dwell time '
|
|
803
|
-
f'for scans of type {self.spec_macro}')
|
|
804
|
-
|
|
805
|
-
def get_spec_scan_motor_mnes(self):
|
|
806
|
-
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
807
|
-
m1_mne = self.spec_args[0]
|
|
808
|
-
try:
|
|
809
|
-
# Try post-summer-2022 format
|
|
810
|
-
dwell = float(self.spec_args[4])
|
|
811
|
-
except:
|
|
812
|
-
# Accommodate pre-summer-2022 format
|
|
813
|
-
m2_mne_i = 4
|
|
814
|
-
else:
|
|
815
|
-
m2_mne_i = 5
|
|
816
|
-
m2_mne = self.spec_args[m2_mne_i]
|
|
817
|
-
return (m1_mne, m2_mne)
|
|
818
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
819
|
-
return (self.spec_args[0],)
|
|
820
|
-
if self.spec_macro in ('tseries', 'loopscan'):
|
|
821
|
-
return ('Time',)
|
|
822
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
823
|
-
f'for scans of type {self.spec_macro}')
|
|
824
|
-
|
|
825
|
-
def get_spec_scan_motor_vals(self, relative=False):
|
|
826
|
-
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
827
|
-
m1_start = float(self.spec_args[1])
|
|
828
|
-
m1_end = float(self.spec_args[2])
|
|
829
|
-
m1_npt = int(self.spec_args[3]) + 1
|
|
830
|
-
try:
|
|
831
|
-
# Try post-summer-2022 format
|
|
832
|
-
dwell = float(self.spec_args[4])
|
|
833
|
-
except:
|
|
834
|
-
# Accommodate pre-summer-2022 format
|
|
835
|
-
m2_start_i = 5
|
|
836
|
-
m2_end_i = 6
|
|
837
|
-
m2_nint_i = 7
|
|
838
|
-
else:
|
|
839
|
-
m2_start_i = 6
|
|
840
|
-
m2_end_i = 7
|
|
841
|
-
m2_nint_i = 8
|
|
842
|
-
m2_start = float(self.spec_args[m2_start_i])
|
|
843
|
-
m2_end = float(self.spec_args[m2_end_i])
|
|
844
|
-
m2_npt = int(self.spec_args[m2_nint_i]) + 1
|
|
845
|
-
fast_mot_vals = np.linspace(m1_start, m1_end, m1_npt)
|
|
846
|
-
slow_mot_vals = np.linspace(m2_start, m2_end, m2_npt)
|
|
847
|
-
if relative:
|
|
848
|
-
fast_mot_vals -= self.spec_positioner_values[
|
|
849
|
-
self.spec_scan_motor_mnes[0]]
|
|
850
|
-
slow_mot_vals -= self.spec_positioner_values[
|
|
851
|
-
self.spec_scan_motor_mnes[1]]
|
|
852
|
-
return (fast_mot_vals, slow_mot_vals)
|
|
853
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
854
|
-
mot_vals = np.linspace(float(self.spec_args[1]),
|
|
855
|
-
float(self.spec_args[2]),
|
|
856
|
-
int(self.spec_args[3])+1)
|
|
857
|
-
if relative:
|
|
858
|
-
mot_vals -= self.get_spec_positioner_value(
|
|
859
|
-
self.spec_scan_motor_mnes[0])
|
|
860
|
-
return (mot_vals,)
|
|
861
|
-
if self.spec_macro in ('tseries', 'loopscan'):
|
|
862
|
-
return (self.spec_scan.data[:,0],)
|
|
863
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
864
|
-
f'for scans of type {self.spec_macro}')
|
|
865
|
-
|
|
866
|
-
def get_spec_scan_shape(self):
|
|
867
|
-
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
868
|
-
fast_mot_npts = int(self.spec_args[3]) + 1
|
|
869
|
-
try:
|
|
870
|
-
# Try post-summer-2022 format
|
|
871
|
-
dwell = float(self.spec_args[4])
|
|
872
|
-
except:
|
|
873
|
-
# Accommodate pre-summer-2022 format
|
|
874
|
-
m2_nint_i = 7
|
|
875
|
-
else:
|
|
876
|
-
m2_nint_i = 8
|
|
877
|
-
slow_mot_npts = int(self.spec_args[m2_nint_i]) + 1
|
|
878
|
-
return (fast_mot_npts, slow_mot_npts)
|
|
879
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
880
|
-
mot_npts = int(self.spec_args[3])+1
|
|
881
|
-
return (mot_npts,)
|
|
882
|
-
if self.spec_macro in ('tseries', 'loopscan'):
|
|
883
|
-
return (len(np.array(self.spec_scan.data[:,0])),)
|
|
884
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine scan shape '
|
|
885
|
-
f'for scans of type {self.spec_macro}')
|
|
886
|
-
|
|
887
|
-
def get_detector_data_path(self):
|
|
888
|
-
return os.path.join(self.scan_path, str(self.scan_number))
|
|
889
|
-
|
|
890
|
-
def get_detector_data_file(self, detector_prefix, scan_step_index:int):
|
|
891
|
-
scan_step = self.get_scan_step(scan_step_index)
|
|
892
|
-
if len(scan_step) == 1:
|
|
893
|
-
scan_step = (0, *scan_step)
|
|
894
|
-
file_name_pattern = (f'{detector_prefix}_'
|
|
895
|
-
f'{self.scan_name}_*_'
|
|
896
|
-
f'{scan_step[0]}_data_'
|
|
897
|
-
f'{(scan_step[1]+1):06d}.h5')
|
|
898
|
-
file_name_matches = fnmatch_filter(
|
|
899
|
-
os.listdir(self.detector_data_path),
|
|
900
|
-
file_name_pattern)
|
|
901
|
-
if len(file_name_matches) == 1:
|
|
902
|
-
return os.path.join(self.detector_data_path, file_name_matches[0])
|
|
903
|
-
raise RuntimeError(f'{self.scan_title}: could not find detector image '
|
|
904
|
-
f'file for detector {detector_prefix} scan step '
|
|
905
|
-
f'({scan_step_index})')
|
|
906
|
-
|
|
907
|
-
def get_detector_data(self, detector_prefix, scan_step_index:int):
|
|
908
|
-
# Third party modules
|
|
909
|
-
from h5py import File
|
|
910
|
-
|
|
911
|
-
image_file = self.get_detector_data_file(
|
|
912
|
-
detector_prefix, scan_step_index)
|
|
913
|
-
with File(image_file) as h5_file:
|
|
914
|
-
image_data = h5_file['/entry/data/data'][0]
|
|
915
|
-
return image_data
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
class RotationScanParser(ScanParser):
|
|
919
|
-
"""Partial implementation of a class representing a rotation
|
|
920
|
-
scan.
|
|
921
|
-
"""
|
|
922
|
-
|
|
923
|
-
def __init__(self, spec_file_name, scan_number):
|
|
924
|
-
super().__init__(spec_file_name, scan_number)
|
|
925
|
-
self._starting_image_index = None
|
|
926
|
-
self._starting_image_offset = None
|
|
927
|
-
|
|
928
|
-
@property
|
|
929
|
-
def starting_image_index(self):
|
|
930
|
-
if self._starting_image_index is None:
|
|
931
|
-
self._starting_image_index = self.get_starting_image_index()
|
|
932
|
-
return self._starting_image_index
|
|
933
|
-
|
|
934
|
-
@property
|
|
935
|
-
def starting_image_offset(self):
|
|
936
|
-
if self._starting_image_offset is None:
|
|
937
|
-
self._starting_image_offset = self.get_starting_image_offset()
|
|
938
|
-
return self._starting_image_offset
|
|
939
|
-
|
|
940
|
-
def get_starting_image_index(self):
|
|
941
|
-
"""Return the first frame of the detector data collected by
|
|
942
|
-
this scan from the index of the first frame of detector data
|
|
943
|
-
collected by this scan.
|
|
944
|
-
|
|
945
|
-
:rtype: int
|
|
946
|
-
"""
|
|
947
|
-
raise NotImplementedError
|
|
948
|
-
|
|
949
|
-
def get_starting_image_offset(self):
|
|
950
|
-
"""Return the offset of the index of the first "good" frame of
|
|
951
|
-
detector data collected by this scan from the index of the
|
|
952
|
-
first frame of detector data collected by this scan.
|
|
953
|
-
|
|
954
|
-
:rtype: int
|
|
955
|
-
"""
|
|
956
|
-
raise NotImplementedError
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
class FMBRotationScanParser(RotationScanParser, FMBScanParser):
|
|
960
|
-
"""Concrete implementation of a class representing a scan taken
|
|
961
|
-
with the typical tomography setup at FMB.
|
|
962
|
-
"""
|
|
963
|
-
|
|
964
|
-
def get_spec_scan_data(self):
|
|
965
|
-
spec_scan_data = super().get_spec_scan_data()
|
|
966
|
-
if hasattr(self, '_rams4_args'):
|
|
967
|
-
spec_scan_data['theta'] = np.linspace(
|
|
968
|
-
float(self._rams4_args[0]), float(self._rams4_args[1]),
|
|
969
|
-
1+int(self._rams4_args[2]))
|
|
970
|
-
return spec_scan_data
|
|
971
|
-
|
|
972
|
-
def get_spec_scan_npts(self):
|
|
973
|
-
if hasattr(self, '_rams4_args'):
|
|
974
|
-
return 1+int(self._rams4_args[2])
|
|
975
|
-
if self.spec_macro == 'flyscan':
|
|
976
|
-
if len(self.spec_args) == 2:
|
|
977
|
-
return 1+int(self.spec_args[0])
|
|
978
|
-
if len(self.spec_args) == 5:
|
|
979
|
-
return 1+int(self.spec_args[3])
|
|
980
|
-
raise RuntimeError(f'{self.scan_title}: cannot obtain number of '
|
|
981
|
-
f'points from {self.spec_macro} with arguments '
|
|
982
|
-
f'{self.spec_args}')
|
|
983
|
-
# if self.spec_macro == 'ascan':
|
|
984
|
-
# if len(self.spec_args) == 5:
|
|
985
|
-
# return int(self.spec_args[3])
|
|
986
|
-
# raise RuntimeError(f'{self.scan_title}: cannot obtain number of '
|
|
987
|
-
# f'points from {self.spec_macro} with arguments '
|
|
988
|
-
# f'{self.spec_args}')
|
|
989
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine rotation '
|
|
990
|
-
f' angles for scans of type {self.spec_macro}')
|
|
991
|
-
|
|
992
|
-
def get_starting_image_index(self):
|
|
993
|
-
return 0
|
|
994
|
-
|
|
995
|
-
def get_starting_image_offset(self):
|
|
996
|
-
if hasattr(self, '_rams4_args'):
|
|
997
|
-
return int(self.spec_args[0]) - self.spec_scan_npts
|
|
998
|
-
if self.spec_macro == 'flyscan':
|
|
999
|
-
return 1
|
|
1000
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine starting '
|
|
1001
|
-
f'image offset for scans of type {self.spec_macro}')
|
|
1002
|
-
|
|
1003
|
-
def get_detector_data_path(self):
|
|
1004
|
-
return self.scan_path
|
|
1005
|
-
|
|
1006
|
-
def get_detector_data_file(self, detector_prefix):
|
|
1007
|
-
prefix = detector_prefix.upper()
|
|
1008
|
-
file_name = f'{self.scan_name}_{prefix}_{self.scan_number:03d}.h5'
|
|
1009
|
-
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
1010
|
-
if os.path.isfile(file_name_full):
|
|
1011
|
-
return file_name_full
|
|
1012
|
-
raise RuntimeError(f'{self.scan_title}: could not find detector image '
|
|
1013
|
-
f'file for detector {detector_prefix}')
|
|
1014
|
-
|
|
1015
|
-
def get_all_detector_data_in_file(
|
|
1016
|
-
self, detector_prefix, scan_step_index=None):
|
|
1017
|
-
# Third party modules
|
|
1018
|
-
from h5py import File
|
|
1019
|
-
|
|
1020
|
-
detector_file = self.get_detector_data_file(detector_prefix)
|
|
1021
|
-
with File(detector_file) as h5_file:
|
|
1022
|
-
if scan_step_index is None:
|
|
1023
|
-
detector_data = h5_file['/entry/instrument/detector/data'][
|
|
1024
|
-
self.starting_image_offset:]
|
|
1025
|
-
elif isinstance(scan_step_index, int):
|
|
1026
|
-
detector_data = h5_file['/entry/instrument/detector/data'][
|
|
1027
|
-
self.starting_image_offset+scan_step_index]
|
|
1028
|
-
elif (isinstance(scan_step_index, (list, tuple))
|
|
1029
|
-
and len(scan_step_index) == 2):
|
|
1030
|
-
detector_data = h5_file['/entry/instrument/detector/data'][
|
|
1031
|
-
self.starting_image_offset+scan_step_index[0]:
|
|
1032
|
-
self.starting_image_offset+scan_step_index[1]]
|
|
1033
|
-
else:
|
|
1034
|
-
raise ValueError('Invalid parameter scan_step_index '
|
|
1035
|
-
f'({scan_step_index})')
|
|
1036
|
-
return detector_data
|
|
1037
|
-
|
|
1038
|
-
def get_detector_data(self, detector_prefix, scan_step_index=None):
|
|
1039
|
-
try:
|
|
1040
|
-
# Detector files in h5 format
|
|
1041
|
-
detector_data = self.get_all_detector_data_in_file(
|
|
1042
|
-
detector_prefix, scan_step_index)
|
|
1043
|
-
except:
|
|
1044
|
-
# Detector files in tiff format
|
|
1045
|
-
if scan_step_index is None:
|
|
1046
|
-
detector_data = []
|
|
1047
|
-
for index in range(self.spec_scan_npts):
|
|
1048
|
-
detector_data.append(
|
|
1049
|
-
self.get_detector_data(detector_prefix, index))
|
|
1050
|
-
detector_data = np.asarray(detector_data)
|
|
1051
|
-
elif isinstance(scan_step_index, int):
|
|
1052
|
-
image_file = self._get_detector_tiff_file(
|
|
1053
|
-
detector_prefix, scan_step_index)
|
|
1054
|
-
if image_file is None:
|
|
1055
|
-
detector_data = None
|
|
1056
|
-
else:
|
|
1057
|
-
with TiffFile(image_file) as tiff_file:
|
|
1058
|
-
detector_data = tiff_file.asarray()
|
|
1059
|
-
elif (isinstance(scan_step_index, (list, tuple))
|
|
1060
|
-
and len(scan_step_index) == 2):
|
|
1061
|
-
detector_data = []
|
|
1062
|
-
for index in range(scan_step_index[0], scan_step_index[1]):
|
|
1063
|
-
detector_data.append(
|
|
1064
|
-
self.get_detector_data(detector_prefix, index))
|
|
1065
|
-
detector_data = np.asarray(detector_data)
|
|
1066
|
-
else:
|
|
1067
|
-
raise ValueError('Invalid parameter scan_step_index '
|
|
1068
|
-
f'({scan_step_index})')
|
|
1069
|
-
return detector_data
|
|
1070
|
-
|
|
1071
|
-
def _get_detector_tiff_file(self, detector_prefix, scan_step_index):
|
|
1072
|
-
file_name_full = f'{self.spec_file_name}_{detector_prefix.upper()}_' \
|
|
1073
|
-
f'{self.scan_number:03d}_' \
|
|
1074
|
-
f'{self.starting_image_offset+scan_step_index:03d}.tiff'
|
|
1075
|
-
if os.path.isfile(file_name_full):
|
|
1076
|
-
return file_name_full
|
|
1077
|
-
return None
|
|
1078
|
-
# raise RuntimeError(f'{self.scan_title}: could not find detector image '
|
|
1079
|
-
# f'file for scan step ({scan_step_index})')
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
class SMBRotationScanParser(RotationScanParser, SMBScanParser):
|
|
1083
|
-
"""Concrete implementation of a class representing a scan taken
|
|
1084
|
-
with the typical tomography setup at SMB.
|
|
1085
|
-
"""
|
|
1086
|
-
|
|
1087
|
-
def __init__(self, spec_file_name, scan_number, par_file=None):
|
|
1088
|
-
self._scan_type = None
|
|
1089
|
-
super().__init__(spec_file_name, scan_number)
|
|
1090
|
-
|
|
1091
|
-
self._katefix = 0 # RV remove when no longer needed
|
|
1092
|
-
self._par_file_pattern = f'id*-*tomo*-{self.scan_name}'
|
|
1093
|
-
if par_file is not None:
|
|
1094
|
-
self._par_file = par_file
|
|
1095
|
-
|
|
1096
|
-
@property
|
|
1097
|
-
def scan_type(self):
|
|
1098
|
-
if self._scan_type is None:
|
|
1099
|
-
self._scan_type = self.get_scan_type()
|
|
1100
|
-
return self._scan_type
|
|
1101
|
-
|
|
1102
|
-
def get_spec_scan_data(self):
|
|
1103
|
-
spec_scan_data = super().get_spec_scan_data()
|
|
1104
|
-
spec_scan_data['theta'] = np.linspace(
|
|
1105
|
-
float(self.pars['ome_start_real']),
|
|
1106
|
-
float(self.pars['ome_end_real']), int(self.pars['nframes_real']))
|
|
1107
|
-
return spec_scan_data
|
|
1108
|
-
|
|
1109
|
-
def get_spec_scan_npts(self):
|
|
1110
|
-
return int(self.pars['nframes_real'])
|
|
1111
|
-
|
|
1112
|
-
def get_scan_type(self):
|
|
1113
|
-
scan_type = self.pars.get(
|
|
1114
|
-
'tomo_type', self.pars.get(
|
|
1115
|
-
'tomotype', self.pars.get('scan_type', None)))
|
|
1116
|
-
if scan_type is None:
|
|
1117
|
-
raise RuntimeError(
|
|
1118
|
-
f'{self.scan_title}: cannot determine the scan_type')
|
|
1119
|
-
return scan_type
|
|
1120
|
-
|
|
1121
|
-
def get_starting_image_index(self):
|
|
1122
|
-
try:
|
|
1123
|
-
junkstart = int(self.pars['junkstart'])
|
|
1124
|
-
#RV temp fix for error in par files at Kate's beamline
|
|
1125
|
-
#Remove this and self._katefix when no longer needed
|
|
1126
|
-
file_name = f'nf_{junkstart:06d}.tif'
|
|
1127
|
-
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
1128
|
-
if not os.path.isfile(file_name_full):
|
|
1129
|
-
self._katefix = min([
|
|
1130
|
-
int(re.findall(r'\d+', f)[0])
|
|
1131
|
-
for f in os.listdir(self.detector_data_path)
|
|
1132
|
-
if re.match(r'nf_\d+\.tif', f)])
|
|
1133
|
-
return junkstart
|
|
1134
|
-
#return int(self.pars['junkstart'])
|
|
1135
|
-
except:
|
|
1136
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine first '
|
|
1137
|
-
'detector image index')
|
|
1138
|
-
|
|
1139
|
-
def get_starting_image_offset(self):
|
|
1140
|
-
try:
|
|
1141
|
-
return (int(self.pars['goodstart'])-self.starting_image_index)
|
|
1142
|
-
except:
|
|
1143
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine index '
|
|
1144
|
-
'offset of first good detector image')
|
|
1145
|
-
|
|
1146
|
-
def get_detector_data_path(self):
|
|
1147
|
-
return os.path.join(self.scan_path, str(self.scan_number), 'nf')
|
|
1148
|
-
|
|
1149
|
-
def get_detector_data_file(self, scan_step_index:int):
|
|
1150
|
-
index = self.starting_image_index + self.starting_image_offset \
|
|
1151
|
-
+ scan_step_index
|
|
1152
|
-
file_name = f'nf_{index:06d}.tif'
|
|
1153
|
-
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
1154
|
-
if os.path.isfile(file_name_full):
|
|
1155
|
-
return file_name_full
|
|
1156
|
-
#RV temp fix for error in par files at Kate's beamline
|
|
1157
|
-
#Remove this and self._katefix when no longer needed
|
|
1158
|
-
index += self._katefix
|
|
1159
|
-
file_name = f'nf_{index:06d}.tif'
|
|
1160
|
-
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
1161
|
-
if os.path.isfile(file_name_full):
|
|
1162
|
-
return file_name_full
|
|
1163
|
-
raise RuntimeError(f'{self.scan_title}: could not find detector image '
|
|
1164
|
-
f'file ({file_name_full}) for scan step '
|
|
1165
|
-
f'({scan_step_index})')
|
|
1166
|
-
|
|
1167
|
-
def get_detector_data(self, detector_prefix, scan_step_index=None):
|
|
1168
|
-
if scan_step_index is None:
|
|
1169
|
-
detector_data = []
|
|
1170
|
-
for index in range(self.spec_scan_npts):
|
|
1171
|
-
detector_data.append(
|
|
1172
|
-
self.get_detector_data(detector_prefix, index))
|
|
1173
|
-
detector_data = np.asarray(detector_data)
|
|
1174
|
-
elif isinstance(scan_step_index, int):
|
|
1175
|
-
image_file = self.get_detector_data_file(scan_step_index)
|
|
1176
|
-
with TiffFile(image_file) as tiff_file:
|
|
1177
|
-
detector_data = tiff_file.asarray()
|
|
1178
|
-
elif (isinstance(scan_step_index, (list, tuple))
|
|
1179
|
-
and len(scan_step_index) == 2):
|
|
1180
|
-
detector_data = []
|
|
1181
|
-
for index in range(scan_step_index[0], scan_step_index[1]):
|
|
1182
|
-
detector_data.append(
|
|
1183
|
-
self.get_detector_data(detector_prefix, index))
|
|
1184
|
-
detector_data = np.asarray(detector_data)
|
|
1185
|
-
else:
|
|
1186
|
-
raise ValueError('Invalid parameter scan_step_index '
|
|
1187
|
-
f'({scan_step_index})')
|
|
1188
|
-
return detector_data
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
class MCAScanParser(ScanParser):
|
|
1192
|
-
"""Partial implementation of a class representing a scan taken
|
|
1193
|
-
while collecting SPEC MCA data.
|
|
1194
|
-
"""
|
|
1195
|
-
|
|
1196
|
-
def __init__(self, spec_file_name, scan_number):
|
|
1197
|
-
super().__init__(spec_file_name, scan_number)
|
|
1198
|
-
|
|
1199
|
-
self._detector_num_bins = None
|
|
1200
|
-
|
|
1201
|
-
def get_detector_num_bins(self, detector_prefix):
|
|
1202
|
-
"""Return the number of bins for the detector with the given
|
|
1203
|
-
prefix.
|
|
1204
|
-
|
|
1205
|
-
:param detector_prefix: the detector prefix as used in SPEC
|
|
1206
|
-
MCA data files
|
|
1207
|
-
:type detector_prefix: str
|
|
1208
|
-
:rtype: int
|
|
1209
|
-
"""
|
|
1210
|
-
raise NotImplementedError
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
class SMBMCAScanParser(MCAScanParser, SMBLinearScanParser):
|
|
1214
|
-
"""Concrete implementation of a class representing a scan taken
|
|
1215
|
-
with the typical EDD setup at SMB or FAST.
|
|
1216
|
-
"""
|
|
1217
|
-
detector_data_formats = ('spec', 'h5')
|
|
1218
|
-
def __init__(self, spec_file_name, scan_number, detector_data_format=None):
|
|
1219
|
-
"""Constructor for SMBMCAScnaParser.
|
|
1220
|
-
|
|
1221
|
-
:param spec_file: Path to scan's SPEC file
|
|
1222
|
-
:type spec_file: str
|
|
1223
|
-
:param scan_number: Number of the SPEC scan
|
|
1224
|
-
:type scan_number: int
|
|
1225
|
-
:param detector_data_format: Format of the MCA data collected,
|
|
1226
|
-
defaults to None
|
|
1227
|
-
:type detector_data_format: Optional[Literal["spec", "h5"]]
|
|
1228
|
-
"""
|
|
1229
|
-
super().__init__(spec_file_name, scan_number)
|
|
1230
|
-
|
|
1231
|
-
self.detector_data_format = None
|
|
1232
|
-
if detector_data_format is None:
|
|
1233
|
-
self.init_detector_data_format()
|
|
1234
|
-
else:
|
|
1235
|
-
if detector_data_format.lower() in self.detector_data_formats:
|
|
1236
|
-
self.detector_data_format = detector_data_format.lower()
|
|
1237
|
-
else:
|
|
1238
|
-
raise ValueError(
|
|
1239
|
-
'Unrecognized value for detector_data_format: '
|
|
1240
|
-
+ f'{detector_data_format}. Allowed values are: '
|
|
1241
|
-
+ ', '.join(self.detector_data_formats))
|
|
1242
|
-
|
|
1243
|
-
def get_spec_scan_motor_vals(self, relative=True):
|
|
1244
|
-
if not relative:
|
|
1245
|
-
# The scanned motor's recorded position in the spec.log
|
|
1246
|
-
# file's "#P" lines does not always give the right offset
|
|
1247
|
-
# to use to obtain absolute motor postions from relative
|
|
1248
|
-
# motor positions (or relative from actual). Sometimes,
|
|
1249
|
-
# the labx/y/z/ometotal value from the scan's .par file is
|
|
1250
|
-
# the quantity for the offset that _should_ be used, but
|
|
1251
|
-
# there is currently no consistent way to determine when
|
|
1252
|
-
# to use the labx/y/z/ometotal .par file value and when to
|
|
1253
|
-
# use the spec file "#P" lines value. Because the relative
|
|
1254
|
-
# motor values are the only ones currently used in EDD
|
|
1255
|
-
# workflows, obtain them from relevant values available in
|
|
1256
|
-
# the .par file, and defer implementation for absolute
|
|
1257
|
-
# motor postions to later.
|
|
1258
|
-
return super().get_spec_scan_motor_vals(relative=True)
|
|
1259
|
-
# raise NotImplementedError('Only relative motor values are available.')
|
|
1260
|
-
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
1261
|
-
mot_vals_axis0 = np.linspace(self.pars['fly_axis0_start'],
|
|
1262
|
-
self.pars['fly_axis0_end'],
|
|
1263
|
-
self.pars['fly_axis0_npts'])
|
|
1264
|
-
mot_vals_axis1 = np.linspace(self.pars['fly_axis1_start'],
|
|
1265
|
-
self.pars['fly_axis1_end'],
|
|
1266
|
-
self.pars['fly_axis1_npts'])
|
|
1267
|
-
return (mot_vals_axis0, mot_vals_axis1)
|
|
1268
|
-
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
1269
|
-
mot_vals = np.linspace(self.pars['fly_axis0_start'],
|
|
1270
|
-
self.pars['fly_axis0_end'],
|
|
1271
|
-
self.pars['fly_axis0_npts'])
|
|
1272
|
-
return (mot_vals,)
|
|
1273
|
-
if self.spec_macro in ('tseries', 'loopscan'):
|
|
1274
|
-
return (self.spec_scan.data[:,0],)
|
|
1275
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
1276
|
-
f'for scans of type {self.spec_macro}')
|
|
1277
|
-
|
|
1278
|
-
def init_detector_data_format(self):
|
|
1279
|
-
"""Determine and set a value for the instance variable
|
|
1280
|
-
`detector_data_format` based on the presence / absence of
|
|
1281
|
-
detector data files of different formats conventionally
|
|
1282
|
-
associated with this scan. Also set the corresponding
|
|
1283
|
-
appropriate value for `_detector_data_path`.
|
|
1284
|
-
"""
|
|
1285
|
-
try:
|
|
1286
|
-
self._detector_data_path = self.scan_path
|
|
1287
|
-
detector_file = self.get_detector_data_file_spec()
|
|
1288
|
-
except OSError:
|
|
1289
|
-
try:
|
|
1290
|
-
self._detector_data_path = os.path.join(
|
|
1291
|
-
self.scan_path, str(self.scan_number), 'edd')
|
|
1292
|
-
detector_file = self.get_detector_data_file_h5()
|
|
1293
|
-
except OSError:
|
|
1294
|
-
raise RuntimeError(
|
|
1295
|
-
f"{self.scan_title}: Can't determine detector data format")
|
|
1296
|
-
else:
|
|
1297
|
-
self.detector_data_format = 'h5'
|
|
1298
|
-
else:
|
|
1299
|
-
self.detector_data_format = 'spec'
|
|
1300
|
-
|
|
1301
|
-
def get_detector_data_path(self):
|
|
1302
|
-
raise NotImplementedError
|
|
1303
|
-
|
|
1304
|
-
def get_detector_num_bins(self, element_index=0):
|
|
1305
|
-
if self.detector_data_format == 'spec':
|
|
1306
|
-
return self.get_detector_num_bins_spec()
|
|
1307
|
-
elif self.detector_data_format == 'h5':
|
|
1308
|
-
return self.get_detector_num_bins_h5(element_index)
|
|
1309
|
-
|
|
1310
|
-
def get_detector_num_bins_spec(self):
|
|
1311
|
-
with open(self.get_detector_data_file_spec()) as detector_file:
|
|
1312
|
-
lines = detector_file.readlines()
|
|
1313
|
-
for line in lines:
|
|
1314
|
-
if line.startswith('#@CHANN'):
|
|
1315
|
-
try:
|
|
1316
|
-
line_prefix, number_saved, first_saved, last_saved, \
|
|
1317
|
-
reduction_coef = line.split()
|
|
1318
|
-
return int(number_saved)
|
|
1319
|
-
except:
|
|
1320
|
-
continue
|
|
1321
|
-
raise RuntimeError(f'{self.scan_title}: could not find num_bins')
|
|
1322
|
-
|
|
1323
|
-
def get_detector_num_bins_h5(self, element_index):
|
|
1324
|
-
from h5py import File
|
|
1325
|
-
detector_file = self.get_detector_data_file_h5()
|
|
1326
|
-
with File(detector_file) as h5_file:
|
|
1327
|
-
dset_shape = h5_file['/entry/data/data'].shape
|
|
1328
|
-
return dset_shape[-1]
|
|
1329
|
-
|
|
1330
|
-
def get_detector_data_file(self, scan_step_index=0):
|
|
1331
|
-
if self.detector_data_format == 'spec':
|
|
1332
|
-
return self.get_detector_data_file_spec()
|
|
1333
|
-
elif self.detector_data_format == 'h5':
|
|
1334
|
-
return self.get_detector_data_file_h5(
|
|
1335
|
-
scan_step_index=scan_step_index)
|
|
1336
|
-
|
|
1337
|
-
def get_detector_data_file_spec(self):
|
|
1338
|
-
"""Return the filename (full absolute path) to the file
|
|
1339
|
-
containing spec-formatted MCA data for this scan.
|
|
1340
|
-
"""
|
|
1341
|
-
file_name = f'spec.log.scan{self.scan_number}.mca1.mca'
|
|
1342
|
-
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
1343
|
-
if os.path.isfile(file_name_full):
|
|
1344
|
-
return file_name_full
|
|
1345
|
-
raise OSError(
|
|
1346
|
-
'{self.scan_title}: could not find detector image file'
|
|
1347
|
-
)
|
|
1348
|
-
|
|
1349
|
-
def get_detector_data_file_h5(self, scan_step_index=0):
|
|
1350
|
-
"""Return the filename (full absolute path) to the file
|
|
1351
|
-
containing h5-formatted MCA data for this scan.
|
|
1352
|
-
|
|
1353
|
-
:param scan_step_index:
|
|
1354
|
-
"""
|
|
1355
|
-
scan_step = self.get_scan_step(scan_step_index)
|
|
1356
|
-
if len(self.spec_scan_shape) == 1:
|
|
1357
|
-
filename_index = 0
|
|
1358
|
-
elif len(self.spec_scan_shape) == 2:
|
|
1359
|
-
scan_step = self.get_scan_step(scan_step_index)
|
|
1360
|
-
filename_index = scan_step[0]
|
|
1361
|
-
else:
|
|
1362
|
-
raise NotImplementedError(
|
|
1363
|
-
'Cannot find detector file for scans with dimension > 2')
|
|
1364
|
-
file_name = list_smb_mca_detector_files_h5(
|
|
1365
|
-
self.detector_data_path)[filename_index]
|
|
1366
|
-
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
1367
|
-
if os.path.isfile(file_name_full):
|
|
1368
|
-
return file_name_full
|
|
1369
|
-
raise OSError(
|
|
1370
|
-
'{self.scan_title}: could not find detector image file'
|
|
1371
|
-
)
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
def get_all_detector_data(self, detector):
|
|
1375
|
-
"""Return a 2D array of all MCA spectra collected in this scan
|
|
1376
|
-
by the detector element indicated with `detector`.
|
|
1377
|
-
|
|
1378
|
-
:param detector: For detector data collected in SPEC format,
|
|
1379
|
-
this is the detector prefix as it appears in the spec MCA
|
|
1380
|
-
data file. For detector data collected in H5 format, this
|
|
1381
|
-
is the index of a particular detector element.
|
|
1382
|
-
:type detector: Union[str, int]
|
|
1383
|
-
:rtype: numpy.ndarray
|
|
1384
|
-
"""
|
|
1385
|
-
if self.detector_data_format == 'spec':
|
|
1386
|
-
return self.get_all_detector_data_spec(detector)
|
|
1387
|
-
elif self.detector_data_format == 'h5':
|
|
1388
|
-
try:
|
|
1389
|
-
element_index = int(detector)
|
|
1390
|
-
except:
|
|
1391
|
-
raise TypeError(f'{detector} is not an integer element index')
|
|
1392
|
-
return self.get_all_detector_data_h5(element_index)
|
|
1393
|
-
|
|
1394
|
-
def get_all_detector_data_spec(self, detector_prefix):
|
|
1395
|
-
"""Return a 2D array of all MCA spectra collected by a
|
|
1396
|
-
detector in the spec MCA file format during the scan.
|
|
1397
|
-
|
|
1398
|
-
:param detector_prefix: Detector name at is appears in the
|
|
1399
|
-
spec MCA file.
|
|
1400
|
-
:type detector_prefix: str
|
|
1401
|
-
:returns: 2D array of MCA spectra
|
|
1402
|
-
:rtype: numpy.ndarray
|
|
1403
|
-
"""
|
|
1404
|
-
# This should be easy with pyspec, but there are bugs in
|
|
1405
|
-
# pyspec for MCA data..... or is the 'bug' from a nonstandard
|
|
1406
|
-
# implementation of some macro on our end? According to spec
|
|
1407
|
-
# manual and pyspec code, mca data should always begin w/ '@A'
|
|
1408
|
-
# In example scans, it begins with '@{detector_prefix}'
|
|
1409
|
-
# instead
|
|
1410
|
-
data = []
|
|
1411
|
-
|
|
1412
|
-
with open(self.get_detector_data_file_spec()) as detector_file:
|
|
1413
|
-
lines = [line.strip("\\\n") for line in detector_file.readlines()]
|
|
1414
|
-
|
|
1415
|
-
num_bins = self.get_detector_num_bins()
|
|
1416
|
-
|
|
1417
|
-
counter = 0
|
|
1418
|
-
for line in lines:
|
|
1419
|
-
a = line.split()
|
|
1420
|
-
|
|
1421
|
-
if len(a) > 0:
|
|
1422
|
-
if a[0] == ("@"+detector_prefix):
|
|
1423
|
-
counter = 1
|
|
1424
|
-
spectrum = np.zeros(num_bins)
|
|
1425
|
-
if counter == 1:
|
|
1426
|
-
b = np.array(a[1:]).astype('uint16')
|
|
1427
|
-
spectrum[(counter-1) * 25:((counter-1) * 25 + 25)] = b
|
|
1428
|
-
counter = counter + 1
|
|
1429
|
-
elif counter > 1 and counter <= (np.floor(num_bins / 25.)):
|
|
1430
|
-
b = np.array(a).astype('uint16')
|
|
1431
|
-
spectrum[(counter-1) * 25:((counter-1) * 25 + 25)] = b
|
|
1432
|
-
counter = counter + 1
|
|
1433
|
-
elif counter == (np.ceil(num_bins/25.)):
|
|
1434
|
-
b = np.array(a).astype('uint16')
|
|
1435
|
-
spectrum[(counter-1) * 25:
|
|
1436
|
-
((counter-1) * 25 + (np.mod(num_bins, 25)))] = b
|
|
1437
|
-
data.append(spectrum)
|
|
1438
|
-
counter = 0
|
|
1439
|
-
|
|
1440
|
-
return np.array(data)
|
|
1441
|
-
|
|
1442
|
-
def get_all_detector_data_h5(self, element_index):
|
|
1443
|
-
"""Return a 2D array of all MCA spectra collected by a
|
|
1444
|
-
detector in the h5 file format during the scan.
|
|
1445
|
-
|
|
1446
|
-
:param element_index: The index of a particualr MCA element to
|
|
1447
|
-
return data for.
|
|
1448
|
-
:type element_index: int
|
|
1449
|
-
:returns: 2D array of MCA spectra
|
|
1450
|
-
:rtype: numpy.ndarray
|
|
1451
|
-
"""
|
|
1452
|
-
detector_data = np.empty(
|
|
1453
|
-
(self.spec_scan_npts,
|
|
1454
|
-
self.get_detector_num_bins_h5(element_index)))
|
|
1455
|
-
detector_files = list_smb_mca_detector_files_h5(
|
|
1456
|
-
self.detector_data_path)
|
|
1457
|
-
for i, detector_file in enumerate(detector_files):
|
|
1458
|
-
full_filename = os.path.join(
|
|
1459
|
-
self.detector_data_path, detector_file)
|
|
1460
|
-
element_data = get_all_mca_data_h5(
|
|
1461
|
-
full_filename)[:,element_index,:]
|
|
1462
|
-
i_0 = i * self.spec_scan_shape[0]
|
|
1463
|
-
if len(self.spec_scan_shape) == 2:
|
|
1464
|
-
i_f = i_0 + self.spec_scan_shape[0]
|
|
1465
|
-
else:
|
|
1466
|
-
i_f = self.spec_scan_npts
|
|
1467
|
-
detector_data[i_0:i_f] = element_data
|
|
1468
|
-
return detector_data
|
|
1469
|
-
|
|
1470
|
-
def get_detector_data(self, detector, scan_step_index:int):
|
|
1471
|
-
"""Return a single MCA spectrum for the detector indicated.
|
|
1472
|
-
|
|
1473
|
-
:param detector: If this scan collected MCA data in "spec"
|
|
1474
|
-
format, this is the detector prefix as it appears in the
|
|
1475
|
-
spec MCA data file. If this scan collected data in .h5
|
|
1476
|
-
format, this is the index of the detector element of
|
|
1477
|
-
interest.:type detector: typing.Union[str, int]
|
|
1478
|
-
:param scan_step_index: Index of the scan step to return the
|
|
1479
|
-
spectrum from.
|
|
1480
|
-
:type scan_step_index: int
|
|
1481
|
-
:returns: A single MCA spectrum
|
|
1482
|
-
:rtype: numpy.ndarray
|
|
1483
|
-
"""
|
|
1484
|
-
detector_data = self.get_all_detector_data(detector)
|
|
1485
|
-
return detector_data[scan_step_index]
|
|
1486
|
-
|
|
1487
|
-
@cache
|
|
1488
|
-
def list_smb_mca_detector_files_h5(detector_data_path):
|
|
1489
|
-
"""Return a sorted list of all *.hdf5 files in a directory
|
|
1490
|
-
|
|
1491
|
-
:param detector_data_path: Directory to return *.hdf5 files from
|
|
1492
|
-
:type detector_data_path: str
|
|
1493
|
-
:returns: Sorted list of detector data filenames
|
|
1494
|
-
:rtype: list[str]
|
|
1495
|
-
"""
|
|
1496
|
-
return sorted(
|
|
1497
|
-
[f for f in os.listdir(detector_data_path) if f.endswith('.hdf5')])
|
|
1498
|
-
|
|
1499
|
-
@cache
|
|
1500
|
-
def get_all_mca_data_h5(filename):
|
|
1501
|
-
"""Return all data from all elements from an MCA data file
|
|
1502
|
-
|
|
1503
|
-
:param filename: Name of the MCA h5 data file
|
|
1504
|
-
:type filename: str
|
|
1505
|
-
:returns: 3D array of MCA spectra where the first axis is scan
|
|
1506
|
-
step, second index is detector element, third index is channel
|
|
1507
|
-
energy.
|
|
1508
|
-
:rtype: numpy.ndarray
|
|
1509
|
-
"""
|
|
1510
|
-
import os
|
|
1511
|
-
|
|
1512
|
-
from h5py import File
|
|
1513
|
-
import numpy as np
|
|
1514
|
-
|
|
1515
|
-
with File(filename) as h5_file:
|
|
1516
|
-
data = h5_file['/entry/data/data'][:]
|
|
1517
|
-
|
|
1518
|
-
# Prior to 2023-12-12, there was an issue where the XPS23 detector
|
|
1519
|
-
# was capturing one or two frames of all 0s at the start of the
|
|
1520
|
-
# dataset in every hdf5 file. In both cases, there is only ONE
|
|
1521
|
-
# extra frame of data relative to the number of frames that should
|
|
1522
|
-
# be there (based on the number of points in the spec scan). If
|
|
1523
|
-
# one frame of all 0s is present: skip it and deliver only the
|
|
1524
|
-
# real data. If two frames of all 0s are present: detector data
|
|
1525
|
-
# will be missing for the LAST step in the scan. Skip the first
|
|
1526
|
-
# two frames of all 0s in the hdf5 dataset, then add a frame of
|
|
1527
|
-
# fake data (all 0-s) to the end of that real data so that the
|
|
1528
|
-
# number of detector data frames matches the number of points in
|
|
1529
|
-
# the spec scan.
|
|
1530
|
-
check_zeros_before = 1702357200
|
|
1531
|
-
file_mtime = os.path.getmtime(filename)
|
|
1532
|
-
if file_mtime <= check_zeros_before:
|
|
1533
|
-
if not np.any(data[0]):
|
|
1534
|
-
# If present, remove first frame of blank data
|
|
1535
|
-
print('Warning: removing blank first frame of detector data')
|
|
1536
|
-
data = data[1:]
|
|
1537
|
-
if not np.any(data[0]):
|
|
1538
|
-
# If present, shift second frame of blank data to the
|
|
1539
|
-
# end
|
|
1540
|
-
print('Warning: shifting second frame of blank detector data '
|
|
1541
|
-
+ 'to the end of the scan')
|
|
1542
|
-
data = np.concatenate((data[1:], np.asarray([data[0]])))
|
|
1543
|
-
|
|
1544
|
-
return data
|