ChessAnalysisPipeline 0.0.14__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 +13 -0
- CHAP/common/models/integration.py +29 -26
- CHAP/common/models/map.py +395 -224
- CHAP/common/processor.py +1725 -93
- CHAP/common/reader.py +265 -28
- CHAP/common/writer.py +191 -18
- CHAP/edd/__init__.py +9 -2
- CHAP/edd/models.py +886 -665
- CHAP/edd/processor.py +2592 -936
- CHAP/edd/reader.py +889 -0
- CHAP/edd/utils.py +846 -292
- CHAP/foxden/__init__.py +6 -0
- CHAP/foxden/processor.py +42 -0
- CHAP/foxden/writer.py +65 -0
- 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 +48 -10
- CHAP/runner.py +161 -72
- CHAP/tomo/models.py +31 -29
- CHAP/tomo/processor.py +169 -118
- CHAP/utils/__init__.py +1 -0
- CHAP/utils/fit.py +1292 -1315
- CHAP/utils/general.py +411 -53
- CHAP/utils/models.py +594 -0
- CHAP/utils/parfile.py +10 -2
- ChessAnalysisPipeline-0.0.16.dist-info/LICENSE +60 -0
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/METADATA +1 -1
- ChessAnalysisPipeline-0.0.16.dist-info/RECORD +62 -0
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/WHEEL +1 -1
- CHAP/utils/scanparsers.py +0 -1431
- ChessAnalysisPipeline-0.0.14.dist-info/LICENSE +0 -21
- ChessAnalysisPipeline-0.0.14.dist-info/RECORD +0 -54
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/top_level.txt +0 -0
CHAP/common/reader.py
CHANGED
|
@@ -8,7 +8,9 @@ Description: Module for Writers used in multiple experiment-specific
|
|
|
8
8
|
|
|
9
9
|
# System modules
|
|
10
10
|
from os.path import (
|
|
11
|
+
isabs,
|
|
11
12
|
isfile,
|
|
13
|
+
join,
|
|
12
14
|
splitext,
|
|
13
15
|
)
|
|
14
16
|
from sys import modules
|
|
@@ -36,6 +38,35 @@ class BinaryFileReader(Reader):
|
|
|
36
38
|
return data
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
class FabioImageReader(Reader):
|
|
42
|
+
"""Reader for images using the python package
|
|
43
|
+
[`fabio`](https://fabio.readthedocs.io/en/main/).
|
|
44
|
+
"""
|
|
45
|
+
def read(self, filename, frame=None, inputdir='.'):
|
|
46
|
+
"""Return the data from the image file(s) provided.
|
|
47
|
+
|
|
48
|
+
:param filename: The image filename, or glob pattern for image
|
|
49
|
+
filenames, to read.
|
|
50
|
+
:type filename: str
|
|
51
|
+
:param frame: The index of a specific frame to read from the
|
|
52
|
+
file(s), defaults to `None`.
|
|
53
|
+
:type filename: int, optional
|
|
54
|
+
:returns: Image data as a numpy array (or list of numpy
|
|
55
|
+
arrays, if a glob pattern matching more than one file was
|
|
56
|
+
provided).
|
|
57
|
+
"""
|
|
58
|
+
from glob import glob
|
|
59
|
+
import fabio
|
|
60
|
+
|
|
61
|
+
filenames = glob(filename)
|
|
62
|
+
data = []
|
|
63
|
+
for f in filenames:
|
|
64
|
+
image = fabio.open(f, frame=frame)
|
|
65
|
+
data.append(image.data)
|
|
66
|
+
image.close()
|
|
67
|
+
return data
|
|
68
|
+
|
|
69
|
+
|
|
39
70
|
class H5Reader(Reader):
|
|
40
71
|
"""Reader for h5 files.
|
|
41
72
|
"""
|
|
@@ -97,6 +128,8 @@ class MapReader(Reader):
|
|
|
97
128
|
# Local modules
|
|
98
129
|
from CHAP.common.models.map import MapConfig
|
|
99
130
|
|
|
131
|
+
raise RuntimeError('MapReader is obsolete, use MapProcessor')
|
|
132
|
+
|
|
100
133
|
if filename is not None:
|
|
101
134
|
if map_config is not None:
|
|
102
135
|
raise RuntimeError('Specify either filename or map_config '
|
|
@@ -130,13 +163,14 @@ class MapReader(Reader):
|
|
|
130
163
|
attrs={'spec_file': str(scans.spec_file)})
|
|
131
164
|
|
|
132
165
|
# Add sample metadata
|
|
133
|
-
nxentry[map_config.sample.name] = NXsample(
|
|
166
|
+
nxentry[map_config.sample.name] = NXsample(
|
|
167
|
+
**map_config.sample.dict())
|
|
134
168
|
|
|
135
169
|
# Set up default data group
|
|
136
170
|
nxentry.data = NXdata()
|
|
137
171
|
if map_config.map_type == 'structured':
|
|
138
172
|
nxentry.data.attrs['axes'] = map_config.dims
|
|
139
|
-
for i, dim in enumerate(map_config.independent_dimensions
|
|
173
|
+
for i, dim in enumerate(map_config.independent_dimensions):
|
|
140
174
|
nxentry.data[dim.label] = NXfield(
|
|
141
175
|
value=map_config.coords[dim.label],
|
|
142
176
|
units=dim.units,
|
|
@@ -214,9 +248,149 @@ class NexusReader(Reader):
|
|
|
214
248
|
return nxobject
|
|
215
249
|
|
|
216
250
|
|
|
251
|
+
class NXdataReader(Reader):
|
|
252
|
+
"""Reader for constructing an NXdata object from components"""
|
|
253
|
+
def read(self, name, nxfield_params, signal_name, axes_names, attrs={},
|
|
254
|
+
inputdir='.'):
|
|
255
|
+
"""Return a basic NXdata object constructed from components.
|
|
256
|
+
|
|
257
|
+
:param name: The name of the NXdata group.
|
|
258
|
+
:type name: str
|
|
259
|
+
:param nxfield_params: List of sets of parameters for
|
|
260
|
+
`NXfieldReader` specifying the NXfields belonging to the
|
|
261
|
+
NXdata.
|
|
262
|
+
:type nxfield_params: list[dict]
|
|
263
|
+
:param signal_name: Name of the signal for the NXdata (must be
|
|
264
|
+
one of the names of the NXfields indicated in `nxfields`)
|
|
265
|
+
:type signal: str
|
|
266
|
+
:param axes_names: Name or names of the coordinate axes
|
|
267
|
+
NXfields associated with the signal (must be names of
|
|
268
|
+
NXfields indicated in `nxfields`)
|
|
269
|
+
:type axes_names: Union[str, list[str]]
|
|
270
|
+
:param attrs: Optional dictionary of additional attributes for
|
|
271
|
+
the NXdata
|
|
272
|
+
:type attrs: dict
|
|
273
|
+
:param inputdir: Input directory to use for `NXfieldReader`s,
|
|
274
|
+
defaults to `"."`
|
|
275
|
+
:type inputdir: str
|
|
276
|
+
:returns: A new NXdata object
|
|
277
|
+
:rtype: nexusformat.nexus.NXdata
|
|
278
|
+
"""
|
|
279
|
+
from nexusformat.nexus import NXdata
|
|
280
|
+
|
|
281
|
+
# Read in NXfields
|
|
282
|
+
nxfields = [NXfieldReader().read(**params, inputdir=inputdir)
|
|
283
|
+
for params in nxfield_params]
|
|
284
|
+
nxfields = {nxfield.nxname: nxfield for nxfield in nxfields}
|
|
285
|
+
|
|
286
|
+
# Get signal NXfield
|
|
287
|
+
try:
|
|
288
|
+
nxsignal = nxfields[signal_name]
|
|
289
|
+
except:
|
|
290
|
+
raise ValueError(
|
|
291
|
+
'`signal_name` must be the name of one of the NXfields '
|
|
292
|
+
+ 'indicated in `nxfields`: '
|
|
293
|
+
+ ', '.join(nxfields.keys())
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Get axes NXfield(s)
|
|
297
|
+
if isinstance(axes_names, str):
|
|
298
|
+
axes_names = [axes_names]
|
|
299
|
+
try:
|
|
300
|
+
nxaxes = [nxfields[axis_name] for axis_name in axes_names]
|
|
301
|
+
except:
|
|
302
|
+
raise ValueError(
|
|
303
|
+
'`axes_names` must contain only names of NXfields indicated '
|
|
304
|
+
+ 'in `nxfields`: ' + ', '.join(nxfields.keys())
|
|
305
|
+
)
|
|
306
|
+
for i, nxaxis in enumerate(nxaxes):
|
|
307
|
+
if len(nxaxis) != nxsignal.shape[i]:
|
|
308
|
+
raise ValueError(
|
|
309
|
+
f'Shape mismatch on signal dimension {i}: signal '
|
|
310
|
+
+ f'"{nxsignal.nxname}" has {nxsignal.shape[i]} values, '
|
|
311
|
+
+ f'but axis "{nxaxis.nxname}" has {len(nxaxis)} values.')
|
|
312
|
+
|
|
313
|
+
result = NXdata(signal=nxsignal, axes=nxaxes, name=name, attrs=attrs,
|
|
314
|
+
**nxfields)
|
|
315
|
+
self.logger.info(result.tree)
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class NXfieldReader(Reader):
|
|
320
|
+
"""Reader for an NXfield with options to modify certain attributes."""
|
|
321
|
+
def read(self, filename, nxpath, nxname=None, update_attrs=None,
|
|
322
|
+
slice_params=None, inputdir='.'):
|
|
323
|
+
"""Return a copy of the indicated NXfield from the file. Name
|
|
324
|
+
and attributes of the returned copy may be modified with the
|
|
325
|
+
`nxname` and `update_attrs` keyword arguments.
|
|
326
|
+
|
|
327
|
+
:param filename: Name of the NeXus file containing the NXfield to read.
|
|
328
|
+
:type filename: str
|
|
329
|
+
:param nxpath: Path in `nxfile` pointing to the NXfield to read.
|
|
330
|
+
:type nxpath: str
|
|
331
|
+
:param nxname: Optional new name for the returned NXfield,
|
|
332
|
+
defaults to None
|
|
333
|
+
:type nxname: str, optional
|
|
334
|
+
:param update_attrs: Optional dictonary used to add to /
|
|
335
|
+
update the original NXfield's attributes, defaults to None
|
|
336
|
+
:type update_attrs: dict, optional
|
|
337
|
+
:param slice_params: Parameters for returning just a slice of
|
|
338
|
+
the full field data. Slice parameters are provided in a
|
|
339
|
+
list dictionaries with integer values for any / all of the
|
|
340
|
+
following keys: `"start"`, `"end"`, `"step"`. Default
|
|
341
|
+
values used are: `"start"` - `0`, `"end"` -- `None`,
|
|
342
|
+
`"step"` -- `1`. The order of the list must correspond to
|
|
343
|
+
the order of the field's axes. Defaults to `None`.
|
|
344
|
+
:type slice_params: list[dict[str, int]], optional
|
|
345
|
+
:param inputdir: Directory containing `nxfile`, defaults to `"."`
|
|
346
|
+
:type inputdir: str
|
|
347
|
+
:returns: A copy of the indicated NXfield (with name and
|
|
348
|
+
attributes optionally modified).
|
|
349
|
+
:rtype: nexusformat.nexus.NXfield
|
|
350
|
+
"""
|
|
351
|
+
# Third party modules
|
|
352
|
+
from nexusformat.nexus import nxload, NXfield
|
|
353
|
+
|
|
354
|
+
# Local modules
|
|
355
|
+
from CHAP.utils.general import nxcopy
|
|
356
|
+
|
|
357
|
+
if not isabs(filename):
|
|
358
|
+
filename = join(inputdir, filename)
|
|
359
|
+
nxroot = nxload(filename)
|
|
360
|
+
nxfield = nxroot[nxpath]
|
|
361
|
+
|
|
362
|
+
if nxname is None:
|
|
363
|
+
nxname = nxfield.nxname
|
|
364
|
+
|
|
365
|
+
attrs = nxfield.attrs
|
|
366
|
+
if update_attrs is not None:
|
|
367
|
+
attrs.update(update_attrs)
|
|
368
|
+
|
|
369
|
+
if slice_params is None:
|
|
370
|
+
value = nxfield.nxdata
|
|
371
|
+
else:
|
|
372
|
+
if len(slice_params) < nxfield.ndim:
|
|
373
|
+
slice_params.extend([{}] * (nxfield.ndim - len(slice_params)))
|
|
374
|
+
if len(slice_params) > nxfield.ndim:
|
|
375
|
+
slice_params = slice_params[0:nxfield.ndim]
|
|
376
|
+
slices = ()
|
|
377
|
+
default_slice = {'start': 0, 'end': None, 'step': 1}
|
|
378
|
+
for s in slice_params:
|
|
379
|
+
for k, v in default_slice.items():
|
|
380
|
+
if k not in s:
|
|
381
|
+
s[k] = v
|
|
382
|
+
slices = (*slices, slice(s['start'], s['end'], s['step']))
|
|
383
|
+
value = nxfield.nxdata[slices]
|
|
384
|
+
|
|
385
|
+
nxfield = NXfield(value=value, name=nxname, attrs=attrs)
|
|
386
|
+
self.logger.debug(f'Result -- nxfield.tree =\n{nxfield.tree}')
|
|
387
|
+
|
|
388
|
+
return nxfield
|
|
389
|
+
|
|
390
|
+
|
|
217
391
|
class SpecReader(Reader):
|
|
218
392
|
"""Reader for CHESS SPEC scans"""
|
|
219
|
-
def read(self, filename=None,
|
|
393
|
+
def read(self, filename=None, config=None, detector_names=None,
|
|
220
394
|
inputdir=None):
|
|
221
395
|
"""Take a SPEC configuration filename or dictionary and return
|
|
222
396
|
the raw data as a Nexus NXentry object.
|
|
@@ -225,15 +399,17 @@ class SpecReader(Reader):
|
|
|
225
399
|
to read from to pass onto the constructor of
|
|
226
400
|
`CHAP.common.models.map.SpecConfig`, defaults to `None`.
|
|
227
401
|
:type filename: str, optional
|
|
228
|
-
:param
|
|
402
|
+
:param config: A SPEC configuration to be passed directly
|
|
229
403
|
to the constructor of `CHAP.common.models.map.SpecConfig`,
|
|
230
404
|
defaults to `None`.
|
|
231
|
-
:type
|
|
232
|
-
:param detector_names: Detector prefixes to include raw
|
|
233
|
-
for in the returned NeXus NXentry object,
|
|
234
|
-
|
|
405
|
+
:type config: dict, optional
|
|
406
|
+
:param detector_names: Detector names/prefixes to include raw
|
|
407
|
+
data for in the returned NeXus NXentry object,
|
|
408
|
+
defaults to `None`.
|
|
409
|
+
:type detector_names: Union(int, str, list[int], list[str]),
|
|
410
|
+
optional
|
|
235
411
|
:return: The data from the provided SPEC configuration.
|
|
236
|
-
:rtype: nexusformat.nexus.
|
|
412
|
+
:rtype: nexusformat.nexus.NXroot
|
|
237
413
|
"""
|
|
238
414
|
# Third party modules
|
|
239
415
|
from json import dumps
|
|
@@ -242,14 +418,15 @@ class SpecReader(Reader):
|
|
|
242
418
|
NXdata,
|
|
243
419
|
NXentry,
|
|
244
420
|
NXfield,
|
|
421
|
+
NXroot,
|
|
245
422
|
)
|
|
246
423
|
|
|
247
424
|
# Local modules
|
|
248
425
|
from CHAP.common.models.map import SpecConfig
|
|
249
426
|
|
|
250
427
|
if filename is not None:
|
|
251
|
-
if
|
|
252
|
-
raise RuntimeError('Specify either filename or
|
|
428
|
+
if config is not None:
|
|
429
|
+
raise RuntimeError('Specify either filename or config '
|
|
253
430
|
'in common.SpecReader, not both')
|
|
254
431
|
# Read the map configuration from file
|
|
255
432
|
if not isfile(filename):
|
|
@@ -260,22 +437,62 @@ class SpecReader(Reader):
|
|
|
260
437
|
else:
|
|
261
438
|
raise RuntimeError('input file has a non-implemented '
|
|
262
439
|
f'extension ({filename})')
|
|
263
|
-
|
|
264
|
-
elif not isinstance(
|
|
265
|
-
raise RuntimeError('Invalid parameter
|
|
266
|
-
f'common.SpecReader ({
|
|
440
|
+
config = reader.read(filename)
|
|
441
|
+
elif not isinstance(config, dict):
|
|
442
|
+
raise RuntimeError('Invalid parameter config in '
|
|
443
|
+
f'common.SpecReader ({config})')
|
|
267
444
|
|
|
268
445
|
# Validate the SPEC configuration provided by constructing a
|
|
269
446
|
# SpecConfig
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
#
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
447
|
+
config = SpecConfig(**config, inputdir=inputdir)
|
|
448
|
+
|
|
449
|
+
# Validate the detector names/prefixes
|
|
450
|
+
if config.experiment_type == 'EDD':
|
|
451
|
+
if detector_names is not None:
|
|
452
|
+
if isinstance(detector_names, (int, str)):
|
|
453
|
+
detector_names = [str(detector_names)]
|
|
454
|
+
for i, detector_name in enumerate(detector_names):
|
|
455
|
+
if isinstance(detector_name, int):
|
|
456
|
+
detector_names[i] = str(detector_name)
|
|
457
|
+
elif not isinstance(detector_name, str):
|
|
458
|
+
raise ValueError('Invalid "detector_names" parameter '
|
|
459
|
+
f'({detector_names})')
|
|
460
|
+
else:
|
|
461
|
+
# Local modules
|
|
462
|
+
from CHAP.utils.general import is_str_series
|
|
463
|
+
|
|
464
|
+
if detector_names is None:
|
|
465
|
+
raise ValueError(
|
|
466
|
+
'Missing "detector_names" parameter')
|
|
467
|
+
if isinstance(detector_names, str):
|
|
468
|
+
detector_names = [detector_names]
|
|
469
|
+
if not is_str_series(detector_names, log=False):
|
|
470
|
+
raise ValueError(
|
|
471
|
+
'Invalid "detector_names" parameter ({detector_names})')
|
|
472
|
+
|
|
473
|
+
# Create the NXroot object
|
|
474
|
+
nxroot = NXroot()
|
|
475
|
+
nxentry = NXentry(name=config.experiment_type)
|
|
476
|
+
nxroot[nxentry.nxname] = nxentry
|
|
477
|
+
nxentry.set_default()
|
|
478
|
+
|
|
479
|
+
# Set up NXentry and add misc. CHESS-specific metadata as well
|
|
480
|
+
# as all spec_motors, scan_columns, and smb_pars, and the
|
|
481
|
+
# detector info and raw detector data
|
|
482
|
+
nxentry.config = dumps(config.dict())
|
|
483
|
+
nxentry.attrs['station'] = config.station
|
|
484
|
+
if config.experiment_type == 'EDD':
|
|
485
|
+
if detector_names is None:
|
|
486
|
+
detector_indices = None
|
|
487
|
+
else:
|
|
488
|
+
nxentry.detector_names = detector_names
|
|
489
|
+
detector_indices = [int(d) for d in detector_names]
|
|
490
|
+
else:
|
|
491
|
+
if detector_names is not None:
|
|
492
|
+
nxentry.detector_names = detector_names
|
|
277
493
|
nxentry.spec_scans = NXcollection()
|
|
278
|
-
|
|
494
|
+
# nxpaths = []
|
|
495
|
+
for scans in config.spec_scans:
|
|
279
496
|
nxscans = NXcollection()
|
|
280
497
|
nxentry.spec_scans[f'{scans.scanparsers[0].scan_name}'] = nxscans
|
|
281
498
|
nxscans.attrs['spec_file'] = str(scans.spec_file)
|
|
@@ -283,25 +500,45 @@ class SpecReader(Reader):
|
|
|
283
500
|
for scan_number in scans.scan_numbers:
|
|
284
501
|
scanparser = scans.get_scanparser(scan_number)
|
|
285
502
|
nxscans[scan_number] = NXcollection()
|
|
286
|
-
|
|
503
|
+
try:
|
|
287
504
|
nxscans[scan_number].spec_motors = dumps(
|
|
288
505
|
{k:float(v) for k,v
|
|
289
506
|
in scanparser.spec_positioner_values.items()})
|
|
290
|
-
|
|
507
|
+
except:
|
|
508
|
+
pass
|
|
509
|
+
try:
|
|
291
510
|
nxscans[scan_number].scan_columns = dumps(
|
|
292
511
|
{k:list(v) for k,v
|
|
293
512
|
in scanparser.spec_scan_data.items() if len(v)})
|
|
294
|
-
|
|
513
|
+
except:
|
|
514
|
+
pass
|
|
515
|
+
try:
|
|
295
516
|
nxscans[scan_number].smb_pars = dumps(
|
|
296
517
|
{k:v for k,v in scanparser.pars.items()})
|
|
297
|
-
|
|
518
|
+
except:
|
|
519
|
+
pass
|
|
520
|
+
if config.experiment_type == 'EDD':
|
|
298
521
|
nxdata = NXdata()
|
|
299
522
|
nxscans[scan_number].data = nxdata
|
|
523
|
+
# nxpaths.append(
|
|
524
|
+
# f'spec_scans/{nxscans.nxname}/{scan_number}/data')
|
|
525
|
+
nxdata.data = NXfield(
|
|
526
|
+
value=scanparser.get_detector_data(detector_indices))
|
|
527
|
+
else:
|
|
528
|
+
nxdata = NXdata()
|
|
529
|
+
nxscans[scan_number].data = nxdata
|
|
530
|
+
# nxpaths.append(
|
|
531
|
+
# f'spec_scans/{nxscans.nxname}/{scan_number}/data')
|
|
300
532
|
for detector_name in detector_names:
|
|
301
533
|
nxdata[detector_name] = NXfield(
|
|
302
534
|
value=scanparser.get_detector_data(detector_name))
|
|
303
535
|
|
|
304
|
-
|
|
536
|
+
if config.experiment_type == 'EDD' and detector_names is None:
|
|
537
|
+
nxentry.detector_names = [
|
|
538
|
+
str(i) for i in range(nxdata.data.shape[1])]
|
|
539
|
+
|
|
540
|
+
#return nxroot, nxpaths
|
|
541
|
+
return nxroot
|
|
305
542
|
|
|
306
543
|
|
|
307
544
|
class URLReader(Reader):
|
CHAP/common/writer.py
CHANGED
|
@@ -89,13 +89,15 @@ def write_yaml(data, filename, force_overwrite=False):
|
|
|
89
89
|
|
|
90
90
|
def write_filetree(data, outputdir, force_overwrite=False):
|
|
91
91
|
# System modules
|
|
92
|
-
from os import
|
|
92
|
+
from os import makedirs
|
|
93
93
|
|
|
94
94
|
# Third party modules
|
|
95
95
|
from nexusformat.nexus import (
|
|
96
96
|
NXentry,
|
|
97
|
+
NXsubentry,
|
|
97
98
|
NXgroup,
|
|
98
99
|
NXobject,
|
|
100
|
+
NXroot,
|
|
99
101
|
NXsubentry,
|
|
100
102
|
)
|
|
101
103
|
|
|
@@ -103,8 +105,10 @@ def write_filetree(data, outputdir, force_overwrite=False):
|
|
|
103
105
|
raise TypeError('Cannot write object of type'
|
|
104
106
|
f'{type(data).__name__} as a file tree to disk.')
|
|
105
107
|
|
|
108
|
+
# FIX: Right now this can bomb if MultiplePipelineItem
|
|
109
|
+
# is called simultaneously from multiple nodes in MPI
|
|
106
110
|
if not os_path.isdir(outputdir):
|
|
107
|
-
|
|
111
|
+
makedirs(outputdir)
|
|
108
112
|
|
|
109
113
|
for k, v in data.items():
|
|
110
114
|
if isinstance(v, NXsubentry) and 'schema' in v.attrs:
|
|
@@ -114,15 +118,28 @@ def write_filetree(data, outputdir, force_overwrite=False):
|
|
|
114
118
|
write_txt(list(v.data), filename, force_overwrite)
|
|
115
119
|
elif schema == 'json':
|
|
116
120
|
write_txt(str(v.data), filename, force_overwrite)
|
|
121
|
+
elif schema == 'yml' or schema == 'yaml':
|
|
122
|
+
from json import loads
|
|
123
|
+
write_yaml(loads(v.data.nxdata), filename, force_overwrite)
|
|
117
124
|
elif schema == 'tif' or schema == 'tiff':
|
|
118
125
|
write_tif(v.data, filename, force_overwrite)
|
|
119
126
|
elif schema == 'h5':
|
|
120
|
-
|
|
127
|
+
if any(isinstance(vv, NXsubentry) for vv in v.values()):
|
|
128
|
+
nxbase = NXroot()
|
|
129
|
+
else:
|
|
130
|
+
nxbase = NXentry()
|
|
121
131
|
for kk, vv in v.attrs.items():
|
|
122
|
-
|
|
132
|
+
if kk not in ('schema', 'filename'):
|
|
133
|
+
nxbase.attrs[kk] = vv
|
|
123
134
|
for kk, vv in v.items():
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
if isinstance(vv, NXsubentry):
|
|
136
|
+
nxentry = NXentry()
|
|
137
|
+
nxbase[vv.nxname] = nxentry
|
|
138
|
+
for kkk, vvv in vv.items():
|
|
139
|
+
nxentry[kkk] = vvv
|
|
140
|
+
else:
|
|
141
|
+
nxbase[kk] = vv
|
|
142
|
+
write_nexus(nxbase, filename, force_overwrite)
|
|
126
143
|
else:
|
|
127
144
|
raise TypeError(f'Files of type {schema} not yet implemented')
|
|
128
145
|
elif isinstance(v, NXgroup):
|
|
@@ -204,6 +221,44 @@ class FileTreeWriter(Writer):
|
|
|
204
221
|
return data
|
|
205
222
|
|
|
206
223
|
|
|
224
|
+
class H5Writer(Writer):
|
|
225
|
+
"""Writer for H5 files from an `nexusformat.nexus.NXdata` object"""
|
|
226
|
+
def write(self, data, filename, force_overwrite=False):
|
|
227
|
+
"""Write the NeXus object contained in `data` to hdf5 file.
|
|
228
|
+
|
|
229
|
+
:param data: The data to write to file.
|
|
230
|
+
:type data: CHAP.pipeline.PipelineData
|
|
231
|
+
:param filename: The name of the file to write to.
|
|
232
|
+
:param force_overwrite: Flag to allow data in `filename` to be
|
|
233
|
+
overwritten if it already exists, defaults to `False`.
|
|
234
|
+
:type force_overwrite: bool, optional
|
|
235
|
+
:raises RuntimeError: If `filename` already exists and
|
|
236
|
+
`force_overwrite` is `False`.
|
|
237
|
+
:return: The data written to file.
|
|
238
|
+
:rtype: nexusformat.nexus.NXobject
|
|
239
|
+
"""
|
|
240
|
+
# Third party modules
|
|
241
|
+
from h5py import File
|
|
242
|
+
from nexusformat.nexus import NXdata
|
|
243
|
+
|
|
244
|
+
data = self.unwrap_pipelinedata(data)[-1]
|
|
245
|
+
if not isinstance(data, NXdata):
|
|
246
|
+
raise ValueError('Invalid data parameter {(data)}')
|
|
247
|
+
|
|
248
|
+
mode = 'w' if force_overwrite else 'w-'
|
|
249
|
+
with File(filename, mode) as f:
|
|
250
|
+
f[data.signal] = data.nxsignal
|
|
251
|
+
for i, axes in enumerate(data.attrs['axes']):
|
|
252
|
+
f[axes] = data[axes]
|
|
253
|
+
f[data.signal].dims[i].label = \
|
|
254
|
+
f'{axes} ({data[axes].units})' \
|
|
255
|
+
if 'units' in data[axes].attrs else axes
|
|
256
|
+
f[axes].make_scale(axes)
|
|
257
|
+
f[data.signal].dims[i].attach_scale(f[axes])
|
|
258
|
+
|
|
259
|
+
return data
|
|
260
|
+
|
|
261
|
+
|
|
207
262
|
class MatplotlibAnimationWriter(Writer):
|
|
208
263
|
"""Writer for saving matplotlib animations."""
|
|
209
264
|
def write(self, data, filename, fps=1):
|
|
@@ -261,7 +316,7 @@ class MatplotlibFigureWriter(Writer):
|
|
|
261
316
|
|
|
262
317
|
class NexusWriter(Writer):
|
|
263
318
|
"""Writer for NeXus files from `NXobject`-s"""
|
|
264
|
-
def write(self, data, filename, force_overwrite=False):
|
|
319
|
+
def write(self, data, filename, nxpath=None, force_overwrite=False):
|
|
265
320
|
"""Write the NeXus object contained in `data` to file.
|
|
266
321
|
|
|
267
322
|
:param data: The data to write to file.
|
|
@@ -275,26 +330,138 @@ class NexusWriter(Writer):
|
|
|
275
330
|
:return: The data written to file.
|
|
276
331
|
:rtype: nexusformat.nexus.NXobject
|
|
277
332
|
"""
|
|
333
|
+
# System modules
|
|
334
|
+
import os
|
|
335
|
+
|
|
336
|
+
# Third party modules
|
|
278
337
|
from nexusformat.nexus import (
|
|
338
|
+
NXFile,
|
|
279
339
|
NXentry,
|
|
340
|
+
NXobject,
|
|
280
341
|
NXroot,
|
|
281
342
|
)
|
|
343
|
+
|
|
282
344
|
data = self.unwrap_pipelinedata(data)[-1]
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if nxclass == 'NXentry':
|
|
286
|
-
data = NXroot(data)
|
|
287
|
-
data[nxname].set_default()
|
|
288
|
-
elif nxclass != 'NXroot':
|
|
289
|
-
data = NXroot(NXentry(data))
|
|
290
|
-
if nxclass == 'NXdata':
|
|
291
|
-
data.entry[nxname].set_default()
|
|
292
|
-
data.entry.set_default()
|
|
293
|
-
write_nexus(data, filename, force_overwrite)
|
|
345
|
+
if not isinstance(data, NXobject):
|
|
346
|
+
return data
|
|
294
347
|
|
|
348
|
+
nxname = data.nxname
|
|
349
|
+
if not os.path.isfile(filename) and nxpath is not None:
|
|
350
|
+
self.logger.warning(
|
|
351
|
+
f'{filename} does not yet exist. Argument for nxpath '
|
|
352
|
+
'({nxpath}) will be ignored.')
|
|
353
|
+
nxpath = None
|
|
354
|
+
if nxpath is None:
|
|
355
|
+
nxclass = data.nxclass
|
|
356
|
+
if nxclass == 'NXentry':
|
|
357
|
+
data = NXroot(data)
|
|
358
|
+
data[nxname].set_default()
|
|
359
|
+
elif nxclass != 'NXroot':
|
|
360
|
+
data = NXroot(NXentry(data))
|
|
361
|
+
if nxclass == 'NXdata':
|
|
362
|
+
data.entry[nxname].set_default()
|
|
363
|
+
data.entry.set_default()
|
|
364
|
+
write_nexus(data, filename, force_overwrite)
|
|
365
|
+
else:
|
|
366
|
+
nxfile = NXFile(filename, 'rw')
|
|
367
|
+
root = nxfile.readfile()
|
|
368
|
+
if nxfile.get(nxpath) is None:
|
|
369
|
+
nxpath = root.NXentry[0].nxpath
|
|
370
|
+
self.logger.warning(
|
|
371
|
+
f'Path "{nxpath}" not present in {filename}. '
|
|
372
|
+
+ f'Using {nxpath} instead.')
|
|
373
|
+
full_nxpath = os.path.join(nxpath, nxname)
|
|
374
|
+
self.logger.debug(f'Full path for object to write: {full_nxpath}')
|
|
375
|
+
if nxfile.get(full_nxpath) is not None:
|
|
376
|
+
self.logger.debug(
|
|
377
|
+
f'{os.path.join(nxpath, nxname)} already exists in '
|
|
378
|
+
f'{filename}')
|
|
379
|
+
if force_overwrite:
|
|
380
|
+
self.logger.warning(
|
|
381
|
+
'Deleting existing NXobject at '
|
|
382
|
+
+ f'{os.path.join(nxpath, nxname)} in {filename}')
|
|
383
|
+
del root[full_nxpath]
|
|
384
|
+
try:
|
|
385
|
+
root[full_nxpath] = data
|
|
386
|
+
except Exception as e:
|
|
387
|
+
nxfile.close()
|
|
388
|
+
raise e
|
|
389
|
+
nxfile.close()
|
|
295
390
|
return data
|
|
296
391
|
|
|
297
392
|
|
|
393
|
+
class PyfaiResultsWriter(Writer):
|
|
394
|
+
"""Writer for results of one or more pyFAI integrations. Able to
|
|
395
|
+
handle multiple output formats. Currently supported formats are:
|
|
396
|
+
.npz, .nxs.
|
|
397
|
+
"""
|
|
398
|
+
def write(self, data, filename, force_overwrite=False):
|
|
399
|
+
"""Save pyFAI integration results to a file. Format is
|
|
400
|
+
determined automatically form the extension of `filename`.
|
|
401
|
+
|
|
402
|
+
:param data: Integration results to save.
|
|
403
|
+
:type data: Union[PipelineData,
|
|
404
|
+
list[pyFAI.containers.IntegrateResult]]
|
|
405
|
+
:param filename: Name of the file to which results will be
|
|
406
|
+
saved. Format of output is determined ffrom the
|
|
407
|
+
extension. Currently supported formats are: `.npz`,
|
|
408
|
+
`.nxs`
|
|
409
|
+
:type filename: str
|
|
410
|
+
"""
|
|
411
|
+
import os
|
|
412
|
+
|
|
413
|
+
from pyFAI.containers import Integrate1dResult, Integrate2dResult
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
results = self.unwrap_pipelinedata(data)[0]
|
|
417
|
+
except:
|
|
418
|
+
results = data
|
|
419
|
+
if not isinstance(results, list):
|
|
420
|
+
results = [results]
|
|
421
|
+
if not all([isinstance(r, Integrate1dResult) for r in results]) \
|
|
422
|
+
and not all([isinstance(r, Integrate2dResult) for r in results]):
|
|
423
|
+
raise Exception(
|
|
424
|
+
'Bad input data: all items must have the same type -- either '
|
|
425
|
+
+ 'all pyFAI.containers.Integrate1dResult, or all '
|
|
426
|
+
+ 'pyFAI.containers.Integrate2dResult.')
|
|
427
|
+
|
|
428
|
+
if os.path.isfile(filename):
|
|
429
|
+
if force_overwrite:
|
|
430
|
+
self.logger.warning(f'Removing existing file {filename}')
|
|
431
|
+
os.remove(filename)
|
|
432
|
+
else:
|
|
433
|
+
raise Exception(f'{filename} already exists.')
|
|
434
|
+
_, ext = os.path.splitext(filename)
|
|
435
|
+
if ext.lower() == '.npz':
|
|
436
|
+
self.write_npz(results, filename)
|
|
437
|
+
elif ext.lower() == '.nxs':
|
|
438
|
+
self.write_nxs(results, filename)
|
|
439
|
+
else:
|
|
440
|
+
raise Exception(f'Unsupported file format: {ext}')
|
|
441
|
+
self.logger.info(f'Wrote to {filename}')
|
|
442
|
+
return results
|
|
443
|
+
|
|
444
|
+
def write_npz(self, results, filename):
|
|
445
|
+
"""Save `results` to the .npz file, `filename`"""
|
|
446
|
+
import numpy as np
|
|
447
|
+
|
|
448
|
+
data = {'radial': results[0].radial,
|
|
449
|
+
'intensity': [r.intensity for r in results]}
|
|
450
|
+
if hasattr(results[0], 'azimuthal'):
|
|
451
|
+
# 2d results
|
|
452
|
+
data['azimuthal'] = results[0].azimuthal
|
|
453
|
+
if all([r.sigma for r in results]):
|
|
454
|
+
# errors were included
|
|
455
|
+
data['sigma'] = [r.sigma for r in results]
|
|
456
|
+
|
|
457
|
+
np.savez(filename, **data)
|
|
458
|
+
|
|
459
|
+
def write_nxs(results, filename):
|
|
460
|
+
"""Save `results` to the .nxs file, `filename`"""
|
|
461
|
+
raise NotImplementedError
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
298
465
|
class TXTWriter(Writer):
|
|
299
466
|
"""Writer for plain text files from string or tuples or lists of
|
|
300
467
|
strings."""
|
|
@@ -345,6 +512,12 @@ class YAMLWriter(Writer):
|
|
|
345
512
|
:rtype: dict
|
|
346
513
|
"""
|
|
347
514
|
data = self.unwrap_pipelinedata(data)[-1]
|
|
515
|
+
try:
|
|
516
|
+
from pydantic import BaseModel
|
|
517
|
+
if isinstance(data, BaseModel):
|
|
518
|
+
data = data.dict()
|
|
519
|
+
except:
|
|
520
|
+
pass
|
|
348
521
|
write_yaml(data, filename, force_overwrite)
|
|
349
522
|
return data
|
|
350
523
|
|
CHAP/edd/__init__.py
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"""This subpackage contains `PipelineItems` unique to EDD data
|
|
2
2
|
processing workflows.
|
|
3
3
|
"""
|
|
4
|
-
|
|
4
|
+
from CHAP.edd.reader import (EddMapReader,
|
|
5
|
+
EddMPIMapReader,
|
|
6
|
+
ScanToMapReader,
|
|
7
|
+
SetupNXdataReader,
|
|
8
|
+
UpdateNXdataReader,
|
|
9
|
+
NXdataSliceReader)
|
|
5
10
|
from CHAP.edd.processor import (DiffractionVolumeLengthProcessor,
|
|
6
11
|
LatticeParameterRefinementProcessor,
|
|
7
|
-
|
|
12
|
+
MCAEnergyCalibrationProcessor,
|
|
13
|
+
MCATthCalibrationProcessor,
|
|
8
14
|
MCADataProcessor,
|
|
9
15
|
MCAEnergyCalibrationProcessor,
|
|
16
|
+
MCACalibratedDataPlotter,
|
|
10
17
|
StrainAnalysisProcessor)
|
|
11
18
|
# from CHAP.edd.writer import
|
|
12
19
|
|