ChessAnalysisPipeline 0.0.13__py3-none-any.whl → 0.0.15__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 +10 -0
- CHAP/common/models/map.py +389 -124
- CHAP/common/processor.py +1494 -59
- CHAP/common/reader.py +180 -8
- CHAP/common/writer.py +192 -15
- CHAP/edd/__init__.py +12 -3
- CHAP/edd/models.py +868 -451
- CHAP/edd/processor.py +2383 -462
- CHAP/edd/reader.py +672 -0
- CHAP/edd/utils.py +906 -172
- CHAP/foxden/__init__.py +6 -0
- CHAP/foxden/processor.py +42 -0
- CHAP/foxden/writer.py +65 -0
- CHAP/pipeline.py +35 -3
- CHAP/runner.py +43 -16
- CHAP/tomo/models.py +15 -5
- CHAP/tomo/processor.py +871 -761
- CHAP/utils/__init__.py +1 -0
- CHAP/utils/fit.py +1339 -1309
- CHAP/utils/general.py +568 -105
- CHAP/utils/models.py +567 -0
- CHAP/utils/scanparsers.py +460 -77
- ChessAnalysisPipeline-0.0.15.dist-info/LICENSE +60 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/RECORD +29 -25
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/WHEEL +1 -1
- ChessAnalysisPipeline-0.0.13.dist-info/LICENSE +0 -21
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/top_level.txt +0 -0
CHAP/edd/reader.py
CHANGED
|
@@ -1,4 +1,676 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
+
from CHAP.reader import Reader
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class EddMapReader(Reader):
|
|
6
|
+
"""Reader for taking an EDD-style .par file and returning a
|
|
7
|
+
`MapConfig` representing one of the datasets in the
|
|
8
|
+
file. Independent dimensions are determined automatically, and a
|
|
9
|
+
specific set of items to use for extra scalar datasets to include
|
|
10
|
+
are hard-coded in."""
|
|
11
|
+
def read(self, parfile, dataset_id):
|
|
12
|
+
"""Return a validated `MapConfig` object representing an EDD
|
|
13
|
+
dataset.
|
|
14
|
+
|
|
15
|
+
:param parfile: Name of the EDD-style .par file containing the
|
|
16
|
+
dataset.
|
|
17
|
+
:type parfile: str
|
|
18
|
+
:param dataset_id: Number of the dataset in the .par file
|
|
19
|
+
to return as a map.
|
|
20
|
+
:type dataset_id: int
|
|
21
|
+
:returns: Map configuration packaged with the appropriate
|
|
22
|
+
value for 'schema'.
|
|
23
|
+
:rtype: PipelineData
|
|
24
|
+
"""
|
|
25
|
+
import numpy as np
|
|
26
|
+
from CHAP.common.models.map import MapConfig
|
|
27
|
+
from CHAP.pipeline import PipelineData
|
|
28
|
+
from CHAP.utils.parfile import ParFile
|
|
29
|
+
from CHAP.utils.scanparsers import SMBMCAScanParser as ScanParser
|
|
30
|
+
|
|
31
|
+
parfile = ParFile(parfile)
|
|
32
|
+
self.logger.debug(f'spec_file: {parfile.spec_file}')
|
|
33
|
+
|
|
34
|
+
# Get list of scan numbers for the dataset
|
|
35
|
+
dataset_ids = np.asarray(parfile.get_values('dataset_id'))
|
|
36
|
+
dataset_rows_i = np.argwhere(
|
|
37
|
+
np.where(
|
|
38
|
+
np.asarray(dataset_ids) == dataset_id, 1, 0)).flatten()
|
|
39
|
+
scan_nos = [parfile.data[i][parfile.scann_i] for i in dataset_rows_i\
|
|
40
|
+
if parfile.data[i][parfile.scann_i] in \
|
|
41
|
+
parfile.good_scan_numbers()]
|
|
42
|
+
self.logger.debug(f'Scan numbers: {scan_nos}')
|
|
43
|
+
spec_scans = [dict(spec_file=parfile.spec_file, scan_numbers=scan_nos)]
|
|
44
|
+
|
|
45
|
+
# Get scan type for this dataset
|
|
46
|
+
scan_types = parfile.get_values('scan_type', scan_numbers=scan_nos)
|
|
47
|
+
if any([st != scan_types[0] for st in scan_types]):
|
|
48
|
+
msg = 'Only one scan type per dataset is suported.'
|
|
49
|
+
self.logger.error(msg)
|
|
50
|
+
raise Exception(msg)
|
|
51
|
+
scan_type = scan_types[0]
|
|
52
|
+
self.logger.debug(f'Scan type: {scan_type}')
|
|
53
|
+
|
|
54
|
+
# Based on scan type, get independent_dimensions for the map
|
|
55
|
+
# Start by adding labx, laby, labz, and omega. Any "extra"
|
|
56
|
+
# dimensions will be sqeezed out of the map later.
|
|
57
|
+
independent_dimensions = [
|
|
58
|
+
dict(label='labx', units='mm', data_type='smb_par',
|
|
59
|
+
name='labx'),
|
|
60
|
+
dict(label='laby', units='mm', data_type='smb_par',
|
|
61
|
+
name='laby'),
|
|
62
|
+
dict(label='labz', units='mm', data_type='smb_par',
|
|
63
|
+
name='labz'),
|
|
64
|
+
dict(label='ometotal', units='degrees', data_type='smb_par',
|
|
65
|
+
name='ometotal')
|
|
66
|
+
]
|
|
67
|
+
scalar_data = []
|
|
68
|
+
attrs = {}
|
|
69
|
+
if scan_type != 0:
|
|
70
|
+
self.logger.warning(
|
|
71
|
+
'Assuming all fly axes parameters are identical for all scans')
|
|
72
|
+
attrs['fly_axis_labels'] = []
|
|
73
|
+
axes_labels = {1: 'fly_labx', 2: 'fly_laby', 3: 'fly_labz',
|
|
74
|
+
4: 'fly_ometotal'}
|
|
75
|
+
axes_units = {1: 'mm', 2: 'mm', 3: 'mm', 4: 'degrees'}
|
|
76
|
+
axes_added = []
|
|
77
|
+
scanparser = ScanParser(parfile.spec_file, scan_nos[0])
|
|
78
|
+
def add_fly_axis(fly_axis_index):
|
|
79
|
+
if fly_axis_index in axes_added:
|
|
80
|
+
return
|
|
81
|
+
fly_axis_key = scanparser.pars[f'fly_axis{fly_axis_index}']
|
|
82
|
+
independent_dimensions.append(dict(
|
|
83
|
+
label=axes_labels[fly_axis_key],
|
|
84
|
+
data_type='spec_motor',
|
|
85
|
+
units=axes_units[fly_axis_key],
|
|
86
|
+
name=scanparser.spec_scan_motor_mnes[fly_axis_index]))
|
|
87
|
+
axes_added.append(fly_axis_index)
|
|
88
|
+
attrs['fly_axis_labels'].append(axes_labels[fly_axis_key])
|
|
89
|
+
add_fly_axis(0)
|
|
90
|
+
if scan_type in (2, 3, 5):
|
|
91
|
+
add_fly_axis(1)
|
|
92
|
+
if scan_type == 5:
|
|
93
|
+
scalar_data.append(dict(
|
|
94
|
+
label='bin_axis', units='n/a', data_type='smb_par',
|
|
95
|
+
name='bin_axis'))
|
|
96
|
+
attrs['bin_axis_label'] = axes_labels[
|
|
97
|
+
scanparser.pars['bin_axis']].replace('fly_', '')
|
|
98
|
+
|
|
99
|
+
# Add in the usual extra scalar data maps for EDD
|
|
100
|
+
scalar_data.extend([
|
|
101
|
+
dict(label='SCAN_N', units='n/a', data_type='smb_par',
|
|
102
|
+
name='SCAN_N'),
|
|
103
|
+
dict(label='rsgap_size', units='mm', data_type='smb_par',
|
|
104
|
+
name='rsgap_size'),
|
|
105
|
+
dict(label='x_effective', units='mm', data_type='smb_par',
|
|
106
|
+
name='x_effective'),
|
|
107
|
+
dict(label='z_effective', units='mm', data_type='smb_par',
|
|
108
|
+
name='z_effective'),
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
# Construct initial map config dictionary
|
|
112
|
+
scanparser = ScanParser(parfile.spec_file, scan_nos[0])
|
|
113
|
+
map_config_dict = dict(
|
|
114
|
+
title=f'{scanparser.scan_name}_dataset{dataset_id}',
|
|
115
|
+
station='id1a3',
|
|
116
|
+
experiment_type='EDD',
|
|
117
|
+
sample=dict(name=scanparser.scan_name),
|
|
118
|
+
spec_scans=[
|
|
119
|
+
dict(spec_file=parfile.spec_file, scan_numbers=scan_nos)],
|
|
120
|
+
independent_dimensions=independent_dimensions,
|
|
121
|
+
scalar_data=scalar_data,
|
|
122
|
+
presample_intensity=dict(name='a3ic1', data_type='scan_column'),
|
|
123
|
+
postsample_intensity=dict(name='diode', data_type='scan_column'),
|
|
124
|
+
dwell_time_actual=dict(name='sec', data_type='scan_column'),
|
|
125
|
+
attrs=attrs
|
|
126
|
+
)
|
|
127
|
+
map_config = MapConfig(**map_config_dict)
|
|
128
|
+
|
|
129
|
+
# Squeeze out extraneous independent dimensions (dimensions
|
|
130
|
+
# along which data were taken at only one unique coordinate
|
|
131
|
+
# value)
|
|
132
|
+
while 1 in map_config.shape:
|
|
133
|
+
remove_dim_index = map_config.shape[::-1].index(1)
|
|
134
|
+
self.logger.debug(
|
|
135
|
+
'Map dimensions: '
|
|
136
|
+
+ str([dim["label"] for dim in independent_dimensions]))
|
|
137
|
+
self.logger.debug(f'Map shape: {map_config.shape}')
|
|
138
|
+
self.logger.debug(
|
|
139
|
+
'Sqeezing out independent dimension '
|
|
140
|
+
+ independent_dimensions[remove_dim_index]['label'])
|
|
141
|
+
independent_dimensions.pop(remove_dim_index)
|
|
142
|
+
map_config = MapConfig(**map_config_dict)
|
|
143
|
+
self.logger.debug(
|
|
144
|
+
'Map dimensions: '
|
|
145
|
+
+ str([dim["label"] for dim in independent_dimensions]))
|
|
146
|
+
self.logger.debug(f'Map shape: {map_config.shape}')
|
|
147
|
+
|
|
148
|
+
# Add lab coordinates to the map's scalar_data only if they
|
|
149
|
+
# are NOT already one of the sqeezed map's
|
|
150
|
+
# independent_dimensions.
|
|
151
|
+
lab_dims = [
|
|
152
|
+
dict(label='labx', units='mm', data_type='smb_par', name='labx'),
|
|
153
|
+
dict(label='laby', units='mm', data_type='smb_par', name='laby'),
|
|
154
|
+
dict(label='labz', units='mm', data_type='smb_par', name='labz'),
|
|
155
|
+
dict(label='ometotal', units='degrees', data_type='smb_par',
|
|
156
|
+
name='ometotal')
|
|
157
|
+
]
|
|
158
|
+
for dim in lab_dims:
|
|
159
|
+
if dim not in independent_dimensions:
|
|
160
|
+
scalar_data.append(dim)
|
|
161
|
+
|
|
162
|
+
return map_config_dict
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ScanToMapReader(Reader):
|
|
166
|
+
"""Reader for turning a single SPEC scan into a MapConfig."""
|
|
167
|
+
def read(self, spec_file, scan_number):
|
|
168
|
+
"""Return a dictionary representing a valid map configuration
|
|
169
|
+
consisting of the single SPEC scan specified.
|
|
170
|
+
|
|
171
|
+
:param spec_file: Name of the SPEC file.
|
|
172
|
+
:type spec_file: str
|
|
173
|
+
:param scan_number: Number of the SPEC scan.
|
|
174
|
+
:type scan_number: int
|
|
175
|
+
:returns: Map configuration dictionary
|
|
176
|
+
:rtype: dict
|
|
177
|
+
"""
|
|
178
|
+
from CHAP.utils.scanparsers import SMBMCAScanParser as ScanParser
|
|
179
|
+
|
|
180
|
+
scanparser = ScanParser(spec_file, scan_number)
|
|
181
|
+
|
|
182
|
+
if scanparser.spec_macro in ('tseries', 'loopscan') or \
|
|
183
|
+
(scanparser.spec_macro == 'flyscan' and \
|
|
184
|
+
not len(scanparser.spec_args) ==5):
|
|
185
|
+
independent_dimensions = [
|
|
186
|
+
{'label': 'Time',
|
|
187
|
+
'units': 'seconds',
|
|
188
|
+
'data_type': 'scan_column',
|
|
189
|
+
'name': 'Time'}]
|
|
190
|
+
else:
|
|
191
|
+
independent_dimensions = [
|
|
192
|
+
{'label': mne,
|
|
193
|
+
'units': 'unknown units',
|
|
194
|
+
'data_type': 'spec_motor',
|
|
195
|
+
'name': mne}
|
|
196
|
+
for mne in scanparser.spec_scan_motor_mnes]
|
|
197
|
+
|
|
198
|
+
map_config_dict = dict(
|
|
199
|
+
title=f'{scanparser.scan_name}_{scan_number:03d}',
|
|
200
|
+
station='id1a3',
|
|
201
|
+
experiment_type='EDD',
|
|
202
|
+
sample=dict(name=scanparser.scan_name),
|
|
203
|
+
spec_scans=[
|
|
204
|
+
dict(spec_file=spec_file, scan_numbers=[scan_number])],
|
|
205
|
+
independent_dimensions=independent_dimensions,
|
|
206
|
+
presample_intensity=dict(name='a3ic1', data_type='scan_column'),
|
|
207
|
+
postsample_intensity=dict(name='diode', data_type='scan_column'),
|
|
208
|
+
dwell_time_actual=dict(name='sec', data_type='scan_column')
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return map_config_dict
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class SetupNXdataReader(Reader):
|
|
215
|
+
"""Reader for converting the SPEC input .txt file for EDD dataset
|
|
216
|
+
collection to an approporiate input argument for
|
|
217
|
+
`CHAP.common.SetupNXdataProcessor`.
|
|
218
|
+
|
|
219
|
+
Example of use in a `Pipeline` configuration:
|
|
220
|
+
```yaml
|
|
221
|
+
config:
|
|
222
|
+
inputdir: /rawdata/samplename
|
|
223
|
+
outputdir: /reduceddata/samplename
|
|
224
|
+
pipeline:
|
|
225
|
+
- edd.SetupNXdataReader:
|
|
226
|
+
filename: SpecInput.txt
|
|
227
|
+
dataset_id: 1
|
|
228
|
+
- common.SetupNXdataProcessor:
|
|
229
|
+
nxname: samplename_dataset_1
|
|
230
|
+
- common.NexusWriter:
|
|
231
|
+
filename: data.nxs
|
|
232
|
+
```
|
|
233
|
+
"""
|
|
234
|
+
def read(self, filename, dataset_id):
|
|
235
|
+
"""Return a dictionary containing the `coords`, `signals`, and
|
|
236
|
+
`attrs` arguments appropriate for use with
|
|
237
|
+
`CHAP.common.SetupNXdataProcessor.process` to set up an
|
|
238
|
+
initial `NXdata` object representing a complete and organized
|
|
239
|
+
structured EDD dataset.
|
|
240
|
+
|
|
241
|
+
:param filename: Name of the input .txt file provided to SPEC
|
|
242
|
+
for EDD dataset collection.
|
|
243
|
+
:type filename: str
|
|
244
|
+
:param dataset_id: Number of the dataset in the .txt file to
|
|
245
|
+
return
|
|
246
|
+
`CHAP.common.SetupNXdataProcessor.process`
|
|
247
|
+
arguments for.
|
|
248
|
+
:type dataset_id: int
|
|
249
|
+
:returns: The dataset's coordinate names, values, attributes,
|
|
250
|
+
and signal names, shapes, and attributes.
|
|
251
|
+
:rtype: dict
|
|
252
|
+
"""
|
|
253
|
+
# Columns in input .txt file:
|
|
254
|
+
# 0: scan number
|
|
255
|
+
# 1: dataset index
|
|
256
|
+
# 2: configuration descriptor
|
|
257
|
+
# 3: labx
|
|
258
|
+
# 4: laby
|
|
259
|
+
# 5: labz
|
|
260
|
+
# 6: omega (reference)
|
|
261
|
+
# 7: omega (offset)
|
|
262
|
+
# 8: dwell time
|
|
263
|
+
# 9: beam width
|
|
264
|
+
# 10: beam height
|
|
265
|
+
# 11: detector slit gap width
|
|
266
|
+
# 12: scan type
|
|
267
|
+
|
|
268
|
+
# Following columns used only for scan types 1 and up and
|
|
269
|
+
# specify flyscan/flymesh parameters.
|
|
270
|
+
# 13 + 4n: scan direction axis index
|
|
271
|
+
# 14 + 4n: lower bound
|
|
272
|
+
# 15 + 4n: upper bound
|
|
273
|
+
# 16 + 4n: no. points
|
|
274
|
+
# (For scan types 1, 4: n = 1)
|
|
275
|
+
# (For scan types 2, 3, 5: n = 1 or 2)
|
|
276
|
+
|
|
277
|
+
# For scan type 5 only:
|
|
278
|
+
# 21: bin axis
|
|
279
|
+
|
|
280
|
+
import numpy as np
|
|
281
|
+
|
|
282
|
+
# Parse dataset from the input .txt file.
|
|
283
|
+
with open(filename) as inf:
|
|
284
|
+
file_lines = inf.readlines()
|
|
285
|
+
dataset_lines = []
|
|
286
|
+
for l in file_lines:
|
|
287
|
+
vals = l.split()
|
|
288
|
+
for i, v in enumerate(vals):
|
|
289
|
+
try:
|
|
290
|
+
vals[i] = int(v)
|
|
291
|
+
except:
|
|
292
|
+
try:
|
|
293
|
+
vals[i] = float(v)
|
|
294
|
+
except:
|
|
295
|
+
pass
|
|
296
|
+
if vals[1] == dataset_id:
|
|
297
|
+
dataset_lines.append(vals)
|
|
298
|
+
|
|
299
|
+
# Start inferring coords and signals lists for EDD experiments
|
|
300
|
+
self.logger.warning(
|
|
301
|
+
'Assuming the following parameters are identical across the '
|
|
302
|
+
+ 'entire dataset: scan type, configuration descriptor')
|
|
303
|
+
scan_type = dataset_lines[0][12]
|
|
304
|
+
self.logger.debug(f'scan_type = {scan_type}')
|
|
305
|
+
coords = [
|
|
306
|
+
dict(name='labx',
|
|
307
|
+
values=np.unique([l[3] for l in dataset_lines]),
|
|
308
|
+
attrs=dict(
|
|
309
|
+
units='mm', local_name='labx', data_type='smb_par')),
|
|
310
|
+
dict(name='laby',
|
|
311
|
+
values=np.unique([l[4] for l in dataset_lines]),
|
|
312
|
+
attrs=dict(
|
|
313
|
+
units='mm', local_name='laby', data_type='smb_par')),
|
|
314
|
+
dict(name='labz',
|
|
315
|
+
values=np.unique([l[5] for l in dataset_lines]),
|
|
316
|
+
attrs=dict(
|
|
317
|
+
units='mm', local_name='labz', data_type='smb_par')),
|
|
318
|
+
dict(name='ometotal',
|
|
319
|
+
values=np.unique([l[6] + l[7] for l in dataset_lines]),
|
|
320
|
+
attrs=dict(
|
|
321
|
+
units='degrees', local_name='ometotal',
|
|
322
|
+
data_type='smb_par'))
|
|
323
|
+
]
|
|
324
|
+
signals = [
|
|
325
|
+
dict(name='presample_intensity', shape=[],
|
|
326
|
+
attrs=dict(units='counts', local_name='a3ic1',
|
|
327
|
+
data_type='scan_column')),
|
|
328
|
+
dict(name='postsample_intensity', shape=[],
|
|
329
|
+
attrs=dict(units='counts', local_name='diode',
|
|
330
|
+
data_type='scan_column')),
|
|
331
|
+
dict(name='dwell_time_actual', shape=[],
|
|
332
|
+
attrs=dict(units='seconds', local_name='sec',
|
|
333
|
+
data_type='scan_column')),
|
|
334
|
+
dict(name='SCAN_N', shape=[],
|
|
335
|
+
attrs=dict(units='n/a', local_name='SCAN_N',
|
|
336
|
+
data_type='smb_par')),
|
|
337
|
+
dict(name='rsgap_size', shape=[],
|
|
338
|
+
attrs=dict(units='mm', local_name='rsgap_size',
|
|
339
|
+
data_type='smb_par')),
|
|
340
|
+
dict(name='x_effective', shape=[],
|
|
341
|
+
attrs=dict(units='mm', local_name='x_effective',
|
|
342
|
+
data_type='smb_par')),
|
|
343
|
+
dict(name='z_effective', shape=[],
|
|
344
|
+
attrs=dict(units='mm', local_name='z_effective',
|
|
345
|
+
data_type='smb_par')),
|
|
346
|
+
]
|
|
347
|
+
for i in range(23):
|
|
348
|
+
signals.append(dict(
|
|
349
|
+
name=str(i), shape=[4096,],
|
|
350
|
+
attrs=dict(
|
|
351
|
+
units='counts', local_name=f'XPS23 element {i}',
|
|
352
|
+
eta='unknown')))
|
|
353
|
+
|
|
354
|
+
attrs = dict(dataset_id=dataset_id,
|
|
355
|
+
config_id=dataset_lines[0][2],
|
|
356
|
+
scan_type=scan_type)
|
|
357
|
+
|
|
358
|
+
# For potential coordinate axes w/ only one unique value, do
|
|
359
|
+
# not consider them a coordinate. Make them a signal instead.
|
|
360
|
+
_coords = []
|
|
361
|
+
for i, c in enumerate(coords):
|
|
362
|
+
if len(c['values']) == 1:
|
|
363
|
+
self.logger.debug(f'Moving {c["name"]} from coords to signals')
|
|
364
|
+
# signal = coords.pop(i)
|
|
365
|
+
del c['values']
|
|
366
|
+
c['shape'] = []
|
|
367
|
+
signals.append(c)
|
|
368
|
+
else:
|
|
369
|
+
_coords.append(c)
|
|
370
|
+
coords = _coords
|
|
371
|
+
|
|
372
|
+
# Append additional coords depending on the scan type of the
|
|
373
|
+
# dataset. Also find the number of points / scan.
|
|
374
|
+
if scan_type == 0:
|
|
375
|
+
scan_npts = 1
|
|
376
|
+
else:
|
|
377
|
+
self.logger.warning(
|
|
378
|
+
'Assuming scan parameters are identical for all scans.')
|
|
379
|
+
axes_labels = {1: 'scan_labx', 2: 'scan_laby', 3: 'scan_labz',
|
|
380
|
+
4: 'scan_ometotal'}
|
|
381
|
+
axes_units = {1: 'mm', 2: 'mm', 3: 'mm', 4: 'degrees'}
|
|
382
|
+
coords.append(
|
|
383
|
+
dict(name=axes_labels[dataset_lines[0][13]],
|
|
384
|
+
values=np.round(np.linspace(
|
|
385
|
+
dataset_lines[0][14], dataset_lines[0][15],
|
|
386
|
+
dataset_lines[0][16]), 3),
|
|
387
|
+
attrs=dict(units=axes_units[dataset_lines[0][13]],
|
|
388
|
+
relative=True)))
|
|
389
|
+
scan_npts = len(coords[-1]['values'])
|
|
390
|
+
if scan_type in (2, 3, 5):
|
|
391
|
+
coords.append(
|
|
392
|
+
dict(name=axes_labels[dataset_lines[0][17]],
|
|
393
|
+
values=np.round(np.linspace(
|
|
394
|
+
dataset_lines[0][18], dataset_lines[0][19],
|
|
395
|
+
dataset_lines[0][20]), 3),
|
|
396
|
+
attrs=dict(units=axes_units[dataset_lines[0][17]],
|
|
397
|
+
relative=True)))
|
|
398
|
+
scan_npts *= len(coords[-1]['values'])
|
|
399
|
+
if scan_type == 5:
|
|
400
|
+
attrs['bin_axis'] = axes_labels[dataset_lines[0][21]]
|
|
401
|
+
|
|
402
|
+
# Determine if the datset is structured or unstructured.
|
|
403
|
+
total_npts = len(dataset_lines) * scan_npts
|
|
404
|
+
self.logger.debug(f'Total # of points in the dataset: {total_npts}')
|
|
405
|
+
self.logger.debug(
|
|
406
|
+
'Determined number of unique coordinate values: '
|
|
407
|
+
+ str({c['name']: len(c['values']) for c in coords}))
|
|
408
|
+
coords_npts = np.prod([len(c['values']) for c in coords])
|
|
409
|
+
self.logger.debug(
|
|
410
|
+
f'If dataset is structured, # of points should be: {coords_npts}')
|
|
411
|
+
if coords_npts != total_npts:
|
|
412
|
+
attrs['unstructured_axes'] = []
|
|
413
|
+
self.logger.warning(
|
|
414
|
+
'Dataset is unstructured. All coordinates will be treated as '
|
|
415
|
+
+ 'singals, and the dataset will have a single coordinate '
|
|
416
|
+
+ 'instead: data point index.')
|
|
417
|
+
for c in coords:
|
|
418
|
+
del c['values']
|
|
419
|
+
c['shape'] = []
|
|
420
|
+
signals.append(c)
|
|
421
|
+
attrs['unstructured_axes'].append(c['name'])
|
|
422
|
+
coords = [dict(name='dataset_point_index',
|
|
423
|
+
values=np.arange(total_npts),
|
|
424
|
+
attrs=dict(units='n/a'))]
|
|
425
|
+
else:
|
|
426
|
+
signals.append(dict(name='dataset_point_index', shape=[],
|
|
427
|
+
attrs=dict(units='n/a')))
|
|
428
|
+
|
|
429
|
+
return dict(coords=coords, signals=signals, attrs=attrs)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class UpdateNXdataReader(Reader):
|
|
433
|
+
"""Companion to `edd.SetupNXdataReader` and
|
|
434
|
+
`common.UpdateNXDataProcessor`. Constructs a list of data points
|
|
435
|
+
to pass as pipeline data to `common.UpdateNXDataProcessor` so that
|
|
436
|
+
an `NXdata` constructed by `edd.SetupNXdataReader` and
|
|
437
|
+
`common.SetupNXdataProcessor` can be updated live as individual
|
|
438
|
+
scans in an EDD dataset are completed.
|
|
439
|
+
|
|
440
|
+
Example of use in a `Pipeline` configuration:
|
|
441
|
+
```yaml
|
|
442
|
+
config:
|
|
443
|
+
inputdir: /rawdata/samplename
|
|
444
|
+
pipeline:
|
|
445
|
+
- edd.UpdateNXdataReader:
|
|
446
|
+
spec_file: spec.log
|
|
447
|
+
scan_number: 1
|
|
448
|
+
- common.SetupNXdataProcessor:
|
|
449
|
+
nxfilename: /reduceddata/samplename/data.nxs
|
|
450
|
+
nxdata_path: /entry/samplename_dataset_1
|
|
451
|
+
```
|
|
452
|
+
"""
|
|
453
|
+
def read(self, spec_file, scan_number, inputdir='.'):
|
|
454
|
+
"""Return a list of data points containing raw data values for
|
|
455
|
+
a single EDD spec scan. The returned values can be passed
|
|
456
|
+
along to `common.UpdateNXdataProcessor` to fill in an existing
|
|
457
|
+
`NXdata` set up with `common.SetupNXdataProcessor`.
|
|
458
|
+
|
|
459
|
+
:param spec_file: Name of the spec file containing the spec
|
|
460
|
+
scan (a relative or absolute path).
|
|
461
|
+
:type spec_file: str
|
|
462
|
+
:param scan_number: Number of the spec scan.
|
|
463
|
+
:type scan_number: int
|
|
464
|
+
:param inputdir: Parent directory of `spec_file`, used only if
|
|
465
|
+
`spec_file` is a relative path. Will be ignored if
|
|
466
|
+
`spec_file` is an absolute path. Defaults to `'.'`.
|
|
467
|
+
:type inputdir: str
|
|
468
|
+
:returs: List of data points appropriate for input to
|
|
469
|
+
`common.UpdateNXdataProcessor`.
|
|
470
|
+
:rtype: list[dict[str, object]]
|
|
471
|
+
"""
|
|
472
|
+
import os
|
|
473
|
+
from CHAP.utils.scanparsers import SMBMCAScanParser as ScanParser
|
|
474
|
+
from CHAP.utils.parfile import ParFile
|
|
475
|
+
|
|
476
|
+
if not os.path.isabs(spec_file):
|
|
477
|
+
spec_file = os.path.join(inputdir, spec_file)
|
|
478
|
+
scanparser = ScanParser(spec_file, scan_number)
|
|
479
|
+
self.logger.debug('Parsed scan')
|
|
480
|
+
|
|
481
|
+
# A label / counter mne dict for convenience
|
|
482
|
+
counters = dict(presample_intensity='a3ic0',
|
|
483
|
+
postsample_intensity='diode',
|
|
484
|
+
dwell_time_actual='sec')
|
|
485
|
+
# Determine the scan's own coordinate axes based on scan type
|
|
486
|
+
scan_type = scanparser.pars['scan_type']
|
|
487
|
+
self.logger.debug(f'scan_type = {scan_type}')
|
|
488
|
+
if scan_type == 0:
|
|
489
|
+
scan_axes = []
|
|
490
|
+
else:
|
|
491
|
+
axes_labels = {1: 'scan_labx', 2: 'scan_laby', 3: 'scan_labz',
|
|
492
|
+
4: 'scan_ometotal'}
|
|
493
|
+
scan_axes = [axes_labels[scanparser.pars['fly_axis0']]]
|
|
494
|
+
if scan_type in (2, 3, 5):
|
|
495
|
+
scan_axes.append(axes_labels[scanparser.pars['fly_axis1']])
|
|
496
|
+
self.logger.debug(f'Determined scan axes: {scan_axes}')
|
|
497
|
+
|
|
498
|
+
# Par file values will be the same for all points in any scan
|
|
499
|
+
smb_par_values = {}
|
|
500
|
+
for smb_par in ('labx', 'laby', 'labz', 'ometotal', 'SCAN_N',
|
|
501
|
+
'rsgap_size', 'x_effective', 'z_effective'):
|
|
502
|
+
smb_par_values[smb_par] = scanparser.pars[smb_par]
|
|
503
|
+
|
|
504
|
+
# Get offset for the starting index of this scan's points in
|
|
505
|
+
# the entire dataset.
|
|
506
|
+
dataset_id = scanparser.pars['dataset_id']
|
|
507
|
+
parfile = ParFile(scanparser._par_file)
|
|
508
|
+
good_scans = parfile.good_scan_numbers()
|
|
509
|
+
n_prior_dataset_scans = sum(
|
|
510
|
+
[1 if did == dataset_id and scan_n < scan_number else 0 \
|
|
511
|
+
for did, scan_n in zip(
|
|
512
|
+
parfile.get_values(
|
|
513
|
+
'dataset_id', scan_numbers=good_scans),
|
|
514
|
+
good_scans)])
|
|
515
|
+
dataset_point_index_offset = n_prior_dataset_scans \
|
|
516
|
+
* scanparser.spec_scan_npts
|
|
517
|
+
self.logger.debug(
|
|
518
|
+
f'dataset_point_index_offset = {dataset_point_index_offset}')
|
|
519
|
+
|
|
520
|
+
# Get full data point for every point in the scan
|
|
521
|
+
data_points = []
|
|
522
|
+
self.logger.info(f'Getting {scanparser.spec_scan_npts} data points')
|
|
523
|
+
for i in range(scanparser.spec_scan_npts):
|
|
524
|
+
self.logger.debug(f'Getting data point for scan step index {i}')
|
|
525
|
+
step = scanparser.get_scan_step(i)
|
|
526
|
+
data_points.append(dict(
|
|
527
|
+
dataset_point_index=dataset_point_index_offset + i,
|
|
528
|
+
**smb_par_values,
|
|
529
|
+
**{str(_i): scanparser.get_detector_data(_i, i) \
|
|
530
|
+
for _i in range(23)},
|
|
531
|
+
**{c: scanparser.spec_scan_data[counters[c]][i] \
|
|
532
|
+
for c in counters},
|
|
533
|
+
**{a: round(
|
|
534
|
+
scanparser.spec_scan_motor_vals_relative[_i][step[_i]], 3)\
|
|
535
|
+
for _i, a in enumerate(scan_axes)}))
|
|
536
|
+
|
|
537
|
+
return data_points
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class NXdataSliceReader(Reader):
|
|
541
|
+
"""Reader for returning a sliced verison of an `NXdata` (which
|
|
542
|
+
represents a full EDD dataset) that contains data from just a
|
|
543
|
+
single SPEC scan.
|
|
544
|
+
|
|
545
|
+
Example of use in a `Pipeline` configuration:
|
|
546
|
+
```yaml
|
|
547
|
+
config:
|
|
548
|
+
inputdir: /rawdata/samplename
|
|
549
|
+
outputdir: /reduceddata/samplename
|
|
550
|
+
pipeline:
|
|
551
|
+
- edd.NXdataSliceReader:
|
|
552
|
+
filename: /reduceddata/samplename/data.nxs
|
|
553
|
+
nxpath: /path/to/nxdata
|
|
554
|
+
spec_file: spec.log
|
|
555
|
+
scan_number: 1
|
|
556
|
+
- common.NexusWriter:
|
|
557
|
+
filename: scan_1.nxs
|
|
558
|
+
```
|
|
559
|
+
"""
|
|
560
|
+
def read(self, filename, nxpath, spec_file, scan_number, inputdir='.'):
|
|
561
|
+
"""Return a "slice" of an EDD dataset's NXdata that represents
|
|
562
|
+
just the data from one scan in the dataset.
|
|
563
|
+
|
|
564
|
+
:param filename: Name of the NeXus file in which the
|
|
565
|
+
existing full EDD dataset's NXdata resides.
|
|
566
|
+
:type filename: str
|
|
567
|
+
:param nxpath: Path to the existing full EDD dataset's NXdata
|
|
568
|
+
group in `filename`.
|
|
569
|
+
:type nxpath: str
|
|
570
|
+
:param spec_file: Name of the spec file containing whose data
|
|
571
|
+
will be the only contents of the returned `NXdata`.
|
|
572
|
+
:type spec_file: str
|
|
573
|
+
:param scan_number: Number of the spec scan whose data will be
|
|
574
|
+
the only contents of the returned `NXdata`.
|
|
575
|
+
:type scan_number: int
|
|
576
|
+
:param inputdir: Directory containing `filename` and/or
|
|
577
|
+
`spec_file`, if either one / both of them are not absolute
|
|
578
|
+
paths. Defaults to `'.'`.
|
|
579
|
+
:type inputdir: str, optional
|
|
580
|
+
:returns: An `NXdata` similar to the one at `nxpath` in
|
|
581
|
+
`filename`, but containing only the data collected by the
|
|
582
|
+
specified spec scan.
|
|
583
|
+
:rtype: nexusformat.nexus.NXdata
|
|
584
|
+
"""
|
|
585
|
+
import os
|
|
586
|
+
|
|
587
|
+
from nexusformat.nexus import nxload
|
|
588
|
+
import numpy as np
|
|
589
|
+
|
|
590
|
+
from CHAP.common import NXdataReader
|
|
591
|
+
from CHAP.utils.parfile import ParFile
|
|
592
|
+
from CHAP.utils.scanparsers import SMBMCAScanParser as ScanParser
|
|
593
|
+
|
|
594
|
+
# Parse existing NXdata
|
|
595
|
+
root = nxload(filename)
|
|
596
|
+
nxdata = root[nxpath]
|
|
597
|
+
if nxdata.nxclass != 'NXdata':
|
|
598
|
+
raise TypeError(
|
|
599
|
+
f'Object at {nxpath} in {filename} is not an NXdata')
|
|
600
|
+
self.logger.debug('Loaded existing NXdata')
|
|
601
|
+
|
|
602
|
+
# Parse scan
|
|
603
|
+
if not os.path.isabs(spec_file):
|
|
604
|
+
spec_file = os.path.join(inputdir, spec_file)
|
|
605
|
+
scanparser = ScanParser(spec_file, scan_number)
|
|
606
|
+
self.logger.debug('Parsed scan')
|
|
607
|
+
|
|
608
|
+
# Assemble arguments for NXdataReader
|
|
609
|
+
name = f'{nxdata.nxname}_scan_{scan_number}'
|
|
610
|
+
axes_names = [a.nxname for a in nxdata.nxaxes]
|
|
611
|
+
if nxdata.nxsignal is not None:
|
|
612
|
+
signal_name = nxdata.nxsignal.nxname
|
|
613
|
+
else:
|
|
614
|
+
signal_name = list(nxdata.entries.keys())[0]
|
|
615
|
+
attrs = nxdata.attrs
|
|
616
|
+
nxfield_params = []
|
|
617
|
+
if 'dataset_point_index' in nxdata:
|
|
618
|
+
# Get offset for the starting index of this scan's points in
|
|
619
|
+
# the entire dataset.
|
|
620
|
+
dataset_id = scanparser.pars['dataset_id']
|
|
621
|
+
parfile = ParFile(scanparser._par_file)
|
|
622
|
+
good_scans = parfile.good_scan_numbers()
|
|
623
|
+
n_prior_dataset_scans = sum(
|
|
624
|
+
[1 if did == dataset_id and scan_n < scan_number else 0 \
|
|
625
|
+
for did, scan_n in zip(
|
|
626
|
+
parfile.get_values(
|
|
627
|
+
'dataset_id', scan_numbers=good_scans),
|
|
628
|
+
good_scans)])
|
|
629
|
+
dataset_point_index_offset = n_prior_dataset_scans \
|
|
630
|
+
* scanparser.spec_scan_npts
|
|
631
|
+
self.logger.debug(
|
|
632
|
+
f'dataset_point_index_offset = {dataset_point_index_offset}')
|
|
633
|
+
slice_params = dict(
|
|
634
|
+
start=dataset_point_index_offset,
|
|
635
|
+
end=dataset_point_index_offset + scanparser.spec_scan_npts + 1)
|
|
636
|
+
nxfield_params = [dict(filename=filename,
|
|
637
|
+
nxpath=entry.nxpath,
|
|
638
|
+
slice_params=[slice_params]) \
|
|
639
|
+
for entry in nxdata]
|
|
640
|
+
else:
|
|
641
|
+
signal_slice_params = []
|
|
642
|
+
for a in nxdata.nxaxes:
|
|
643
|
+
if a.nxname.startswith('scan_'):
|
|
644
|
+
slice_params = {}
|
|
645
|
+
else:
|
|
646
|
+
value = scanparser.pars[a.nxname]
|
|
647
|
+
try:
|
|
648
|
+
index = np.where(a.nxdata == value)[0][0]
|
|
649
|
+
except:
|
|
650
|
+
index = np.argmin(np.abs(a.nxdata - value))
|
|
651
|
+
self.logger.warning(
|
|
652
|
+
f'Nearest match for coordinate value {a.nxname}: '
|
|
653
|
+
f'{a.nxdata[index]} (actual value: {value})')
|
|
654
|
+
slice_params = dict(start=index, end=index+1)
|
|
655
|
+
signal_slice_params.append(slice_params)
|
|
656
|
+
nxfield_params.append(dict(
|
|
657
|
+
filename=filename,
|
|
658
|
+
nxpath=os.path.join(nxdata.nxpath, a.nxname),
|
|
659
|
+
slice_params=[slice_params]))
|
|
660
|
+
for name, entry in nxdata.entries.items():
|
|
661
|
+
if entry in nxdata.nxaxes:
|
|
662
|
+
continue
|
|
663
|
+
nxfield_params.append(dict(
|
|
664
|
+
filename=filename, nxpath=entry.nxpath,
|
|
665
|
+
slice_params=signal_slice_params))
|
|
666
|
+
|
|
667
|
+
# Return the "sliced" NXdata
|
|
668
|
+
reader = NXdataReader()
|
|
669
|
+
reader.logger = self.logger
|
|
670
|
+
return reader.read(name=nxdata.nxname, nxfield_params=nxfield_params,
|
|
671
|
+
signal_name=signal_name, axes_names=axes_names,
|
|
672
|
+
attrs=attrs)
|
|
673
|
+
|
|
2
674
|
|
|
3
675
|
if __name__ == '__main__':
|
|
4
676
|
from CHAP.reader import main
|