ChessAnalysisPipeline 0.0.17.dev3__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.
- CHAP/TaskManager.py +216 -0
- CHAP/__init__.py +27 -0
- CHAP/common/__init__.py +57 -0
- CHAP/common/models/__init__.py +8 -0
- CHAP/common/models/common.py +124 -0
- CHAP/common/models/integration.py +659 -0
- CHAP/common/models/map.py +1291 -0
- CHAP/common/processor.py +2869 -0
- CHAP/common/reader.py +658 -0
- CHAP/common/utils.py +110 -0
- CHAP/common/writer.py +730 -0
- CHAP/edd/__init__.py +23 -0
- CHAP/edd/models.py +876 -0
- CHAP/edd/processor.py +3069 -0
- CHAP/edd/reader.py +1023 -0
- CHAP/edd/select_material_params_gui.py +348 -0
- CHAP/edd/utils.py +1572 -0
- CHAP/edd/writer.py +26 -0
- CHAP/foxden/__init__.py +19 -0
- CHAP/foxden/models.py +71 -0
- CHAP/foxden/processor.py +124 -0
- CHAP/foxden/reader.py +224 -0
- CHAP/foxden/utils.py +80 -0
- CHAP/foxden/writer.py +168 -0
- CHAP/giwaxs/__init__.py +11 -0
- CHAP/giwaxs/models.py +491 -0
- CHAP/giwaxs/processor.py +776 -0
- CHAP/giwaxs/reader.py +8 -0
- CHAP/giwaxs/writer.py +8 -0
- CHAP/inference/__init__.py +7 -0
- CHAP/inference/processor.py +69 -0
- CHAP/inference/reader.py +8 -0
- CHAP/inference/writer.py +8 -0
- CHAP/models.py +227 -0
- CHAP/pipeline.py +479 -0
- CHAP/processor.py +125 -0
- CHAP/reader.py +124 -0
- CHAP/runner.py +277 -0
- CHAP/saxswaxs/__init__.py +7 -0
- CHAP/saxswaxs/processor.py +8 -0
- CHAP/saxswaxs/reader.py +8 -0
- CHAP/saxswaxs/writer.py +8 -0
- CHAP/server.py +125 -0
- CHAP/sin2psi/__init__.py +7 -0
- CHAP/sin2psi/processor.py +8 -0
- CHAP/sin2psi/reader.py +8 -0
- CHAP/sin2psi/writer.py +8 -0
- CHAP/tomo/__init__.py +15 -0
- CHAP/tomo/models.py +210 -0
- CHAP/tomo/processor.py +3862 -0
- CHAP/tomo/reader.py +9 -0
- CHAP/tomo/writer.py +59 -0
- CHAP/utils/__init__.py +6 -0
- CHAP/utils/converters.py +188 -0
- CHAP/utils/fit.py +2947 -0
- CHAP/utils/general.py +2655 -0
- CHAP/utils/material.py +274 -0
- CHAP/utils/models.py +595 -0
- CHAP/utils/parfile.py +224 -0
- CHAP/writer.py +122 -0
- MLaaS/__init__.py +0 -0
- MLaaS/ktrain.py +205 -0
- MLaaS/mnist_img.py +83 -0
- MLaaS/tfaas_client.py +371 -0
- chessanalysispipeline-0.0.17.dev3.dist-info/LICENSE +60 -0
- chessanalysispipeline-0.0.17.dev3.dist-info/METADATA +29 -0
- chessanalysispipeline-0.0.17.dev3.dist-info/RECORD +70 -0
- chessanalysispipeline-0.0.17.dev3.dist-info/WHEEL +5 -0
- chessanalysispipeline-0.0.17.dev3.dist-info/entry_points.txt +2 -0
- chessanalysispipeline-0.0.17.dev3.dist-info/top_level.txt +2 -0
CHAP/common/writer.py
ADDED
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
File : writer.py
|
|
4
|
+
Author : Valentin Kuznetsov <vkuznet AT gmail dot com>
|
|
5
|
+
Description: Module for Writers used in multiple experiment-specific workflows.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# System modules
|
|
9
|
+
import os
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
# Third party modules
|
|
13
|
+
import numpy as np
|
|
14
|
+
from pydantic import (
|
|
15
|
+
conint,
|
|
16
|
+
constr,
|
|
17
|
+
model_validator,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Local modules
|
|
21
|
+
from CHAP import Writer
|
|
22
|
+
from CHAP.pipeline import PipelineItem
|
|
23
|
+
from CHAP.writer import validate_writer_model
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def validate_model(model):
|
|
27
|
+
if model.filename is not None:
|
|
28
|
+
validate_writer_model(model)
|
|
29
|
+
return model
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def write_matplotlibfigure(data, filename, savefig_kw, force_overwrite=False):
|
|
33
|
+
"""Write a Matplotlib figure to file.
|
|
34
|
+
|
|
35
|
+
:param data: The figure to write to file
|
|
36
|
+
:type data: matplotlib.figure.Figure
|
|
37
|
+
:param filename: File name.
|
|
38
|
+
:type filename: str
|
|
39
|
+
:param savefig_kw: Keyword args to pass to
|
|
40
|
+
matplotlib.figure.Figure.savefig.
|
|
41
|
+
:type savefig_kw: dict, optional
|
|
42
|
+
:param force_overwrite: Flag to allow data to be overwritten if it
|
|
43
|
+
already exists, defaults to `False`.
|
|
44
|
+
:type force_overwrite: bool, optional
|
|
45
|
+
"""
|
|
46
|
+
# Third party modules
|
|
47
|
+
from matplotlib.figure import Figure
|
|
48
|
+
|
|
49
|
+
if not isinstance(data, Figure):
|
|
50
|
+
raise TypeError('Cannot write object of type'
|
|
51
|
+
f'{type(data)} as a matplotlib Figure.')
|
|
52
|
+
|
|
53
|
+
if os.path.isfile(filename) and not force_overwrite:
|
|
54
|
+
raise FileExistsError(f'{filename} already exists')
|
|
55
|
+
|
|
56
|
+
if savefig_kw is None:
|
|
57
|
+
data.savefig(filename)
|
|
58
|
+
else:
|
|
59
|
+
data.savefig(filename, **savefig_kw)
|
|
60
|
+
|
|
61
|
+
def write_nexus(data, filename, force_overwrite=False):
|
|
62
|
+
"""Write a NeXus object to file.
|
|
63
|
+
|
|
64
|
+
:param data: The data to write to file
|
|
65
|
+
:type data: nexusformat.nexus.NXobject
|
|
66
|
+
:param filename: File name.
|
|
67
|
+
:type filename: str
|
|
68
|
+
:param force_overwrite: Flag to allow data to be overwritten if it
|
|
69
|
+
already exists, defaults to `False`.
|
|
70
|
+
:type force_overwrite: bool, optional
|
|
71
|
+
"""
|
|
72
|
+
# Third party modules
|
|
73
|
+
from nexusformat.nexus import NXobject
|
|
74
|
+
|
|
75
|
+
if not isinstance(data, NXobject):
|
|
76
|
+
raise TypeError('Cannot write object of type'
|
|
77
|
+
f'{type(data).__name__} as a NeXus file.')
|
|
78
|
+
|
|
79
|
+
mode = 'w' if force_overwrite else 'w-'
|
|
80
|
+
data.save(filename, mode=mode)
|
|
81
|
+
|
|
82
|
+
def write_tif(data, filename, force_overwrite=False):
|
|
83
|
+
"""Write a tif image to file.
|
|
84
|
+
|
|
85
|
+
:param data: The data to write to file
|
|
86
|
+
:type data: numpy.ndarray
|
|
87
|
+
:param filename: File name.
|
|
88
|
+
:type filename: str
|
|
89
|
+
:param force_overwrite: Flag to allow data to be overwritten if it
|
|
90
|
+
already exists, defaults to `False`.
|
|
91
|
+
:type force_overwrite: bool, optional
|
|
92
|
+
"""
|
|
93
|
+
# Third party modules
|
|
94
|
+
from imageio import imwrite
|
|
95
|
+
|
|
96
|
+
data = np.asarray(data)
|
|
97
|
+
if data.ndim != 2:
|
|
98
|
+
raise TypeError('Cannot write object of type'
|
|
99
|
+
f'{type(data).__name__} as a tif file.')
|
|
100
|
+
|
|
101
|
+
if os.path.isfile(filename) and not force_overwrite:
|
|
102
|
+
raise FileExistsError(f'{filename} already exists')
|
|
103
|
+
|
|
104
|
+
imwrite(filename, data)
|
|
105
|
+
|
|
106
|
+
def write_txt(data, filename, force_overwrite=False, append=False):
|
|
107
|
+
"""Write plain text to file.
|
|
108
|
+
|
|
109
|
+
:param data: The data to write to file
|
|
110
|
+
:type data: Union[str, list[str]]
|
|
111
|
+
:param filename: File name.
|
|
112
|
+
:type filename: str
|
|
113
|
+
:param force_overwrite: Flag to allow data to be overwritten if it
|
|
114
|
+
already exists, defaults to `False`.
|
|
115
|
+
:type force_overwrite: bool, optional
|
|
116
|
+
:param append: Flag to allow data to be appended to the file if it
|
|
117
|
+
already exists, defaults to `False`.
|
|
118
|
+
:type append: bool, optional
|
|
119
|
+
"""
|
|
120
|
+
# Local modules
|
|
121
|
+
from CHAP.utils.general import is_str_series
|
|
122
|
+
|
|
123
|
+
if not isinstance(data, str) and not is_str_series(data, log=False):
|
|
124
|
+
raise TypeError('input data must be a str or a tuple or list of str '
|
|
125
|
+
f'instead of {type(data)} ({data})')
|
|
126
|
+
|
|
127
|
+
if not force_overwrite and not append and os.path.isfile(filename):
|
|
128
|
+
raise FileExistsError(f'{filename} already exists')
|
|
129
|
+
|
|
130
|
+
if append:
|
|
131
|
+
with open(filename, 'a') as f:
|
|
132
|
+
if isinstance(data, str):
|
|
133
|
+
f.write(data)
|
|
134
|
+
else:
|
|
135
|
+
f.write('\n'.join(data))
|
|
136
|
+
else:
|
|
137
|
+
with open(filename, 'w') as f:
|
|
138
|
+
if isinstance(data, str):
|
|
139
|
+
f.write(data)
|
|
140
|
+
else:
|
|
141
|
+
f.write('\n'.join(data))
|
|
142
|
+
|
|
143
|
+
def write_yaml(data, filename, force_overwrite=False):
|
|
144
|
+
"""Write data to a YAML file.
|
|
145
|
+
|
|
146
|
+
:param data: The data to write to file
|
|
147
|
+
:type data: Union[dict, list]
|
|
148
|
+
:param filename: File name.
|
|
149
|
+
:type filename: str
|
|
150
|
+
:param force_overwrite: Flag to allow data to be overwritten if it
|
|
151
|
+
already exists, defaults to `False`.
|
|
152
|
+
:type force_overwrite: bool, optional
|
|
153
|
+
"""
|
|
154
|
+
# Third party modules
|
|
155
|
+
import yaml
|
|
156
|
+
|
|
157
|
+
if not isinstance(data, (dict, list)):
|
|
158
|
+
raise TypeError('input data must be a dict or list.')
|
|
159
|
+
|
|
160
|
+
if os.path.isfile(filename) and not force_overwrite:
|
|
161
|
+
raise FileExistsError(f'{filename} already exists')
|
|
162
|
+
|
|
163
|
+
with open(filename, 'w') as f:
|
|
164
|
+
yaml.dump(data, f, sort_keys=False)
|
|
165
|
+
|
|
166
|
+
def write_filetree(data, outputdir='.', force_overwrite=False):
|
|
167
|
+
"""Write data to a file tree.
|
|
168
|
+
|
|
169
|
+
:param data: The data to write to files
|
|
170
|
+
:type data: nexusformat.nexus.NXobject
|
|
171
|
+
:param outputdir: Output directory.
|
|
172
|
+
:type filename: str, optional
|
|
173
|
+
:param force_overwrite: Flag to allow data to be overwritten if it
|
|
174
|
+
already exists, defaults to `False`.
|
|
175
|
+
:type force_overwrite: bool, optional
|
|
176
|
+
"""
|
|
177
|
+
# System modules
|
|
178
|
+
from os import makedirs
|
|
179
|
+
|
|
180
|
+
# Third party modules
|
|
181
|
+
from nexusformat.nexus import (
|
|
182
|
+
NXentry,
|
|
183
|
+
NXgroup,
|
|
184
|
+
NXobject,
|
|
185
|
+
NXroot,
|
|
186
|
+
NXsubentry,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if not isinstance(data, NXobject):
|
|
190
|
+
raise TypeError('Cannot write object of type'
|
|
191
|
+
f'{type(data).__name__} as a file tree to disk.')
|
|
192
|
+
|
|
193
|
+
# FIX: Right now this can bomb if MultiplePipelineItem
|
|
194
|
+
# is called simultaneously from multiple nodes in MPI
|
|
195
|
+
if not os.path.isdir(outputdir):
|
|
196
|
+
makedirs(outputdir)
|
|
197
|
+
|
|
198
|
+
for k, v in data.items():
|
|
199
|
+
if isinstance(v, NXsubentry) and 'schema' in v.attrs:
|
|
200
|
+
schema = v.attrs['schema']
|
|
201
|
+
filename = os.path.join(outputdir, v.attrs['filename'])
|
|
202
|
+
if schema == 'txt':
|
|
203
|
+
write_txt(list(v.data), filename, force_overwrite)
|
|
204
|
+
elif schema == 'json':
|
|
205
|
+
write_txt(str(v.data), filename, force_overwrite)
|
|
206
|
+
elif schema in ('yml', 'yaml'):
|
|
207
|
+
from json import loads
|
|
208
|
+
write_yaml(loads(v.data.nxdata), filename, force_overwrite)
|
|
209
|
+
elif schema in ('tif', 'tiff'):
|
|
210
|
+
write_tif(v.data, filename, force_overwrite)
|
|
211
|
+
elif schema == 'h5':
|
|
212
|
+
if any(isinstance(vv, NXsubentry) for vv in v.values()):
|
|
213
|
+
nxbase = NXroot()
|
|
214
|
+
else:
|
|
215
|
+
nxbase = NXentry()
|
|
216
|
+
for kk, vv in v.attrs.items():
|
|
217
|
+
if kk not in ('schema', 'filename'):
|
|
218
|
+
nxbase.attrs[kk] = vv
|
|
219
|
+
for kk, vv in v.items():
|
|
220
|
+
if isinstance(vv, NXsubentry):
|
|
221
|
+
nxentry = NXentry()
|
|
222
|
+
nxbase[vv.nxname] = nxentry
|
|
223
|
+
for kkk, vvv in vv.items():
|
|
224
|
+
nxentry[kkk] = vvv
|
|
225
|
+
else:
|
|
226
|
+
nxbase[kk] = vv
|
|
227
|
+
write_nexus(nxbase, filename, force_overwrite)
|
|
228
|
+
else:
|
|
229
|
+
raise TypeError(f'Files of type {schema} not yet implemented')
|
|
230
|
+
elif isinstance(v, NXgroup):
|
|
231
|
+
write_filetree(v, os.path.join(outputdir, k), force_overwrite)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class ExtractArchiveWriter(Writer):
|
|
235
|
+
"""Writer for tar files from binary data."""
|
|
236
|
+
def write(self, data):
|
|
237
|
+
"""Take a .tar archive represented as bytes contained in `data`
|
|
238
|
+
and write the extracted archive to files.
|
|
239
|
+
|
|
240
|
+
:param data: The data to write to archive.
|
|
241
|
+
:type data: list[PipelineData]
|
|
242
|
+
:return: The achived data.
|
|
243
|
+
:rtype: bytes
|
|
244
|
+
"""
|
|
245
|
+
# System modules
|
|
246
|
+
from io import BytesIO
|
|
247
|
+
import tarfile
|
|
248
|
+
|
|
249
|
+
data = self.unwrap_pipelinedata(data)[-1]
|
|
250
|
+
|
|
251
|
+
with tarfile.open(fileobj=BytesIO(data)) as tar:
|
|
252
|
+
tar.extractall(path=self.filename)
|
|
253
|
+
|
|
254
|
+
return data
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class FileTreeWriter(PipelineItem):
|
|
258
|
+
"""Writer for a file tree in NeXus format.
|
|
259
|
+
|
|
260
|
+
:ivar force_overwrite: Flag to allow data to be overwritten if it
|
|
261
|
+
already exists, defaults to `False`. Note that the existence
|
|
262
|
+
of files prior to executing the pipeline is not possible since
|
|
263
|
+
the filename(s) of the data are unknown during pipeline
|
|
264
|
+
validation.
|
|
265
|
+
:type force_overwrite: bool, optional
|
|
266
|
+
"""
|
|
267
|
+
force_overwrite: Optional[bool] = False
|
|
268
|
+
|
|
269
|
+
def write(self, data):
|
|
270
|
+
"""Write a NeXus format object contained in `data` to a
|
|
271
|
+
directory tree stuctured like the NeXus tree.
|
|
272
|
+
|
|
273
|
+
:param data: The data to write to disk.
|
|
274
|
+
:type data: list[PipelineData]
|
|
275
|
+
:raises RuntimeError: If `filename` already exists and
|
|
276
|
+
`force_overwrite` is `False`.
|
|
277
|
+
:return: The data written to disk.
|
|
278
|
+
:rtype: Union[nexusformat.nexus.NXroot,
|
|
279
|
+
nexusformat.nexus.NXentry]
|
|
280
|
+
"""
|
|
281
|
+
# Third party modules
|
|
282
|
+
from nexusformat.nexus import (
|
|
283
|
+
NXentry,
|
|
284
|
+
NXroot,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
data = self.unwrap_pipelinedata(data)[-1]
|
|
288
|
+
if isinstance(data, NXroot):
|
|
289
|
+
if 'default' in data.attrs:
|
|
290
|
+
nxentry = data[data.attrs['default']]
|
|
291
|
+
else:
|
|
292
|
+
nxentry = [v for v in data.values()
|
|
293
|
+
if isinstance(data, NXentry)]
|
|
294
|
+
if len(nxentry) == 1:
|
|
295
|
+
nxentry = nxentry[0]
|
|
296
|
+
else:
|
|
297
|
+
raise TypeError('Cannot write object of type '
|
|
298
|
+
f'{type(data).__name__} as a file tree '
|
|
299
|
+
'to disk.')
|
|
300
|
+
elif isinstance(data, NXentry):
|
|
301
|
+
nxentry = data
|
|
302
|
+
else:
|
|
303
|
+
raise TypeError('Cannot write object of type '
|
|
304
|
+
f'{type(data).__name__} as a file tree to disk.')
|
|
305
|
+
|
|
306
|
+
write_filetree(nxentry, self.outputdir, self.force_overwrite)
|
|
307
|
+
|
|
308
|
+
return data
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class H5Writer(Writer):
|
|
312
|
+
"""Writer for H5 files from an nexusformat.nexus.NXdata object."""
|
|
313
|
+
def write(self, data):
|
|
314
|
+
"""Write the NeXus object contained in `data` to hdf5 file.
|
|
315
|
+
|
|
316
|
+
:param data: The data to write to file.
|
|
317
|
+
:type data: list[PipelineData]
|
|
318
|
+
:raises RuntimeError: If `filename` already exists and
|
|
319
|
+
`force_overwrite` is `False`.
|
|
320
|
+
:return: The data written to file.
|
|
321
|
+
:rtype: nexusformat.nexus.NXobject
|
|
322
|
+
"""
|
|
323
|
+
# Third party modules
|
|
324
|
+
from h5py import File
|
|
325
|
+
from nexusformat.nexus import NXdata
|
|
326
|
+
|
|
327
|
+
data = self.unwrap_pipelinedata(data)[-1]
|
|
328
|
+
if not isinstance(data, NXdata):
|
|
329
|
+
raise ValueError('Invalid data parameter {(data)}')
|
|
330
|
+
|
|
331
|
+
mode = 'w' if self.force_overwrite else 'w-'
|
|
332
|
+
with File(self.filename, mode) as f:
|
|
333
|
+
f[data.signal] = data.nxsignal
|
|
334
|
+
for i, axes in enumerate(data.attrs['axes']):
|
|
335
|
+
f[axes] = data[axes]
|
|
336
|
+
f[data.signal].dims[i].label = \
|
|
337
|
+
f'{axes} ({data[axes].units})' \
|
|
338
|
+
if 'units' in data[axes].attrs else axes
|
|
339
|
+
f[axes].make_scale(axes)
|
|
340
|
+
f[data.signal].dims[i].attach_scale(f[axes])
|
|
341
|
+
|
|
342
|
+
return data
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class ImageWriter(PipelineItem):
|
|
346
|
+
"""Writer for saving image files.
|
|
347
|
+
|
|
348
|
+
:ivar filename: Name of file to write to.
|
|
349
|
+
:type filename: str, optional
|
|
350
|
+
:ivar force_overwrite: Flag to allow data in `filename` to be
|
|
351
|
+
overwritten if it already exists, defaults to `False`.
|
|
352
|
+
:type force_overwrite: bool, optional
|
|
353
|
+
:ivar remove: Flag to remove the dictionary from `data`,
|
|
354
|
+
defaults to `False`.
|
|
355
|
+
:type remove: bool, optional
|
|
356
|
+
"""
|
|
357
|
+
filename: Optional[str] = None
|
|
358
|
+
force_overwrite: Optional[bool] = False
|
|
359
|
+
remove: Optional[bool] = True
|
|
360
|
+
|
|
361
|
+
_validate_filename = model_validator(mode="after")(validate_model)
|
|
362
|
+
|
|
363
|
+
def write(self, data):
|
|
364
|
+
"""Write the image(s) contained in `data` to file.
|
|
365
|
+
|
|
366
|
+
:param data: The data to write to file.
|
|
367
|
+
:type data: list[PipelineData]
|
|
368
|
+
:raises RuntimeError: If a file already exists and
|
|
369
|
+
`force_overwrite` is `False`.
|
|
370
|
+
:return: The data written to disk.
|
|
371
|
+
:rtype: list, dict, matplotlib.animation.FuncAnimation,
|
|
372
|
+
numpy.ndarray
|
|
373
|
+
"""
|
|
374
|
+
# System modules
|
|
375
|
+
from io import BytesIO
|
|
376
|
+
|
|
377
|
+
# Third party modules
|
|
378
|
+
from matplotlib.animation import (
|
|
379
|
+
ArtistAnimation,
|
|
380
|
+
FuncAnimation,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Local modules
|
|
384
|
+
from CHAP.utils.general import save_iobuf_fig
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
ddata = self.get_data(
|
|
388
|
+
data, schema='common.write.ImageWriter', remove=self.remove)
|
|
389
|
+
except ValueError:
|
|
390
|
+
self.logger.warning(
|
|
391
|
+
'Unable to find match with schema `common.write.ImageWriter`: '
|
|
392
|
+
'return without writing')
|
|
393
|
+
return None
|
|
394
|
+
if isinstance(ddata, list):
|
|
395
|
+
for (buf, fileformat), basename in ddata:
|
|
396
|
+
self.filename = f'{basename}.{fileformat}'
|
|
397
|
+
if not os.path.isabs(self.filename):
|
|
398
|
+
self.filename = os.path.join(self.outputdir, self.filename)
|
|
399
|
+
if isinstance(buf, (ArtistAnimation, FuncAnimation)):
|
|
400
|
+
buf.save(self.filename)
|
|
401
|
+
else:
|
|
402
|
+
save_iobuf_fig(
|
|
403
|
+
buf, self.filename,
|
|
404
|
+
force_overwrite=self.force_overwrite)
|
|
405
|
+
return ddata
|
|
406
|
+
|
|
407
|
+
if isinstance(ddata, dict):
|
|
408
|
+
fileformat = ddata['fileformat']
|
|
409
|
+
image_data = ddata['image_data']
|
|
410
|
+
else:
|
|
411
|
+
image_data = ddata
|
|
412
|
+
basename, ext = os.path.splitext(self.filename)
|
|
413
|
+
if ext[1:] != fileformat:
|
|
414
|
+
self.filename = f'{self.filename}.{fileformat}'
|
|
415
|
+
if not os.path.isabs(self.filename):
|
|
416
|
+
self.filename = os.path.join(self.outputdir, self.filename)
|
|
417
|
+
if os.path.isfile(self.filename) and not self.force_overwrite:
|
|
418
|
+
raise FileExistsError(f'{self.filename} already exists')
|
|
419
|
+
if isinstance(image_data, BytesIO):
|
|
420
|
+
save_iobuf_fig(
|
|
421
|
+
image_data, self.filename,
|
|
422
|
+
force_overwrite=self.force_overwrite)
|
|
423
|
+
elif isinstance(image_data, np.ndarray):
|
|
424
|
+
if image_data.ndim == 2:
|
|
425
|
+
# Third party modules
|
|
426
|
+
from imageio import imwrite
|
|
427
|
+
|
|
428
|
+
imwrite(self.filename, image_data)
|
|
429
|
+
elif image_data.ndim == 3:
|
|
430
|
+
# Third party modules
|
|
431
|
+
from tifffile import imwrite
|
|
432
|
+
|
|
433
|
+
kwargs = {'bigtiff': True}
|
|
434
|
+
imwrite(self.filename, image_data, **kwargs)
|
|
435
|
+
elif isinstance(image_data, (ArtistAnimation, FuncAnimation)):
|
|
436
|
+
image_data.save(self.filename)
|
|
437
|
+
else:
|
|
438
|
+
raise ValueError(f'Invalid image input type {type(image_data)}')
|
|
439
|
+
return ddata
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class MatplotlibAnimationWriter(Writer):
|
|
443
|
+
"""Writer for saving matplotlib animations.
|
|
444
|
+
|
|
445
|
+
:ivar fps: Movie frame rate (frames per second), defaults to `1`.
|
|
446
|
+
:type fps: int, optional
|
|
447
|
+
"""
|
|
448
|
+
fps: Optional[conint(gt=0)] = 1
|
|
449
|
+
|
|
450
|
+
def write(self, data):
|
|
451
|
+
"""Write the matplotlib.animation.ArtistAnimation object
|
|
452
|
+
contained in `data` to file.
|
|
453
|
+
|
|
454
|
+
:param data: The data to write to file.
|
|
455
|
+
:type data: list[PipelineData]
|
|
456
|
+
:return: The original animation.
|
|
457
|
+
:rtype: matplotlib.animation.ArtistAnimation
|
|
458
|
+
"""
|
|
459
|
+
data = self.unwrap_pipelinedata(data)[-1]
|
|
460
|
+
extension = os.path.splitext(self.filename)[1]
|
|
461
|
+
if not extension:
|
|
462
|
+
data.save(f'{self.filename}.gif', fps=self.fps)
|
|
463
|
+
elif extension == '.gif':
|
|
464
|
+
data.save(self.filename, fps=self.fps)
|
|
465
|
+
elif extension == '.mp4':
|
|
466
|
+
data.save(self.filename, writer='ffmpeg', fps=self.fps)
|
|
467
|
+
|
|
468
|
+
return data
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
class MatplotlibFigureWriter(Writer):
|
|
472
|
+
"""Writer for saving matplotlib figures to image files.
|
|
473
|
+
|
|
474
|
+
:ivar savefig_kw: Keyword args to pass to
|
|
475
|
+
matplotlib.figure.Figure.savefig.
|
|
476
|
+
:type savefig_kw: dict, optional
|
|
477
|
+
"""
|
|
478
|
+
savefig_kw: Optional[dict] = None
|
|
479
|
+
|
|
480
|
+
def write(self, data):
|
|
481
|
+
"""Write the matplotlib.figure.Figure contained in `data` to
|
|
482
|
+
file.
|
|
483
|
+
|
|
484
|
+
:param data: The data to write to file.
|
|
485
|
+
:type data: list[PipelineData]
|
|
486
|
+
:raises RuntimeError: If `filename` already exists and
|
|
487
|
+
`force_overwrite` is `False`.
|
|
488
|
+
:return: The original figure object.
|
|
489
|
+
:rtype: matplotlib.figure.Figure
|
|
490
|
+
"""
|
|
491
|
+
data = self.unwrap_pipelinedata(data)[-1]
|
|
492
|
+
write_matplotlibfigure(
|
|
493
|
+
data, self.filename, self.savefig_kw, self.force_overwrite)
|
|
494
|
+
|
|
495
|
+
return data
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class NexusWriter(Writer):
|
|
499
|
+
"""Writer for NeXus files from `NXobject`-s.
|
|
500
|
+
|
|
501
|
+
:ivar nxpath: Path to a specific location in the NeXus file tree
|
|
502
|
+
to write to (ignored if `filename` does not yet exist).
|
|
503
|
+
:type nxpath: str, optional
|
|
504
|
+
"""
|
|
505
|
+
nxpath: Optional[constr(strip_whitespace=True, min_length=1)] = None
|
|
506
|
+
|
|
507
|
+
def write(self, data):
|
|
508
|
+
"""Write the NeXus object contained in `data` to file.
|
|
509
|
+
|
|
510
|
+
:param data: The data to write to file.
|
|
511
|
+
:type data: list[PipelineData]
|
|
512
|
+
:raises RuntimeError: If `filename` already exists and
|
|
513
|
+
`force_overwrite` is `False`.
|
|
514
|
+
:return: The data written to file.
|
|
515
|
+
:rtype: nexusformat.nexus.NXobject
|
|
516
|
+
"""
|
|
517
|
+
# Third party modules
|
|
518
|
+
from nexusformat.nexus import (
|
|
519
|
+
NXFile,
|
|
520
|
+
NXentry,
|
|
521
|
+
NXroot,
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
nxobject = self.get_data(data, remove=self.remove)
|
|
525
|
+
|
|
526
|
+
nxname = nxobject.nxname
|
|
527
|
+
if not os.path.isfile(self.filename) and self.nxpath is not None:
|
|
528
|
+
self.logger.warning(
|
|
529
|
+
f'{self.filename} does not yet exist, ignoring nxpath '
|
|
530
|
+
f'argument ({self.nxpath})')
|
|
531
|
+
self.nxpath = None
|
|
532
|
+
if self.nxpath is None:
|
|
533
|
+
nxclass = nxobject.nxclass
|
|
534
|
+
if nxclass == 'NXroot':
|
|
535
|
+
nxroot = nxobject
|
|
536
|
+
elif nxclass == 'NXentry':
|
|
537
|
+
nxroot = NXroot(nxobject)
|
|
538
|
+
nxroot[nxname].set_default()
|
|
539
|
+
else:
|
|
540
|
+
nxroot = NXroot(NXentry(nxobject))
|
|
541
|
+
if nxclass == 'NXdata':
|
|
542
|
+
nxroot.entry[nxname].set_default()
|
|
543
|
+
nxroot.entry.set_default()
|
|
544
|
+
write_nexus(nxroot, self.filename, self.force_overwrite)
|
|
545
|
+
else:
|
|
546
|
+
nxfile = NXFile(self.filename, 'rw')
|
|
547
|
+
root = nxfile.readfile()
|
|
548
|
+
if nxfile.get(self.nxpath) is None:
|
|
549
|
+
if nxfile.get(os.path.dirname(self.nxpath)) is not None:
|
|
550
|
+
self.nxpath, nxname = os.path.split(self.nxpath)
|
|
551
|
+
else:
|
|
552
|
+
self.logger.warning(
|
|
553
|
+
f'Path "{self.nxpath}" not present in {self.filename}. '
|
|
554
|
+
f'Using {root.NXentry[0].nxpath} instead.')
|
|
555
|
+
self.nxpath = root.NXentry[0].nxpath
|
|
556
|
+
full_nxpath = os.path.join(self.nxpath, nxname)
|
|
557
|
+
self.logger.debug(f'Full path for object to write: {full_nxpath}')
|
|
558
|
+
if nxfile.get(full_nxpath) is not None:
|
|
559
|
+
self.logger.debug(
|
|
560
|
+
f'{full_nxpath} already exists in {self.filename}')
|
|
561
|
+
if self.force_overwrite:
|
|
562
|
+
self.logger.warning(
|
|
563
|
+
'Deleting existing NXobject at '
|
|
564
|
+
f'{full_nxpath, nxname} in {self.filename}')
|
|
565
|
+
del root[full_nxpath]
|
|
566
|
+
try:
|
|
567
|
+
root[full_nxpath] = nxobject
|
|
568
|
+
except Exception as exc:
|
|
569
|
+
nxfile.close()
|
|
570
|
+
raise exc
|
|
571
|
+
nxfile.close()
|
|
572
|
+
return data
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class PyfaiResultsWriter(Writer):
|
|
576
|
+
"""Writer for results of one or more pyFAI integrations. Able to
|
|
577
|
+
handle multiple output formats. Currently supported formats are:
|
|
578
|
+
.npz, .nxs.
|
|
579
|
+
"""
|
|
580
|
+
def write(self, data):
|
|
581
|
+
"""Save pyFAI integration results to a file. Format is
|
|
582
|
+
determined automatically form the extension of `filename`.
|
|
583
|
+
|
|
584
|
+
:param data: The data to write to file.
|
|
585
|
+
:type data: Union[list[PipelineData],
|
|
586
|
+
list[pyFAI.containers.IntegrateResult]]
|
|
587
|
+
"""
|
|
588
|
+
from pyFAI.containers import Integrate1dResult, Integrate2dResult
|
|
589
|
+
|
|
590
|
+
try:
|
|
591
|
+
results = self.unwrap_pipelinedata(data)[0]
|
|
592
|
+
except Exception:
|
|
593
|
+
results = data
|
|
594
|
+
if not isinstance(results, list):
|
|
595
|
+
results = [results]
|
|
596
|
+
if (not all([isinstance(r, Integrate1dResult) for r in results])
|
|
597
|
+
and not all(
|
|
598
|
+
[isinstance(r, Integrate2dResult) for r in results])):
|
|
599
|
+
raise Exception(
|
|
600
|
+
'Bad input data: all items must have the same type -- either '
|
|
601
|
+
'all pyFAI.containers.Integrate1dResult, or all '
|
|
602
|
+
'pyFAI.containers.Integrate2dResult.')
|
|
603
|
+
|
|
604
|
+
if os.path.isfile(self.filename):
|
|
605
|
+
if self.force_overwrite:
|
|
606
|
+
self.logger.warning(f'Removing existing file {self.filename}')
|
|
607
|
+
os.remove(self.filename)
|
|
608
|
+
else:
|
|
609
|
+
raise Exception(f'{self.filename} already exists.')
|
|
610
|
+
_, ext = os.path.splitext(self.filename)
|
|
611
|
+
if ext.lower() == '.npz':
|
|
612
|
+
self.write_npz(results, self.filename)
|
|
613
|
+
elif ext.lower() == '.nxs':
|
|
614
|
+
self.write_nxs(results, self.filename)
|
|
615
|
+
else:
|
|
616
|
+
raise Exception(f'Unsupported file format: {ext}')
|
|
617
|
+
self.logger.info(f'Wrote to {self.filename}')
|
|
618
|
+
return results
|
|
619
|
+
|
|
620
|
+
def write_npz(self, results, filename):
|
|
621
|
+
"""Save `results` to the .npz file, `filename`."""
|
|
622
|
+
|
|
623
|
+
data = {'radial': results[0].radial,
|
|
624
|
+
'intensity': [r.intensity for r in results]}
|
|
625
|
+
if hasattr(results[0], 'azimuthal'):
|
|
626
|
+
# 2d results
|
|
627
|
+
data['azimuthal'] = results[0].azimuthal
|
|
628
|
+
if all([r.sigma for r in results]):
|
|
629
|
+
# errors were included
|
|
630
|
+
data['sigma'] = [r.sigma for r in results]
|
|
631
|
+
|
|
632
|
+
np.savez(filename, **data)
|
|
633
|
+
|
|
634
|
+
def write_nxs(self, results, filename):
|
|
635
|
+
"""Save `results` to the .nxs file, `filename`."""
|
|
636
|
+
raise NotImplementedError
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
class TXTWriter(Writer):
|
|
640
|
+
"""Writer for plain text files from string or tuples or lists of
|
|
641
|
+
strings.
|
|
642
|
+
|
|
643
|
+
:ivar append: Flag to allow data in `filename` to be be appended,
|
|
644
|
+
defaults to `False`.
|
|
645
|
+
:type append: bool, optional
|
|
646
|
+
"""
|
|
647
|
+
append: Optional[bool] = False
|
|
648
|
+
|
|
649
|
+
def write(self, data):
|
|
650
|
+
"""Write a string or tuple or list of strings contained in
|
|
651
|
+
`data` to file.
|
|
652
|
+
|
|
653
|
+
:param data: The data to write to file.
|
|
654
|
+
:type data: list[PipelineData]
|
|
655
|
+
:raises TypeError: If the object contained in `data` is not a
|
|
656
|
+
`str`, `tuple[str]` or `list[str]`.
|
|
657
|
+
:raises RuntimeError: If `filename` already exists and
|
|
658
|
+
`force_overwrite` is `False`.
|
|
659
|
+
:return: The data written to file.
|
|
660
|
+
:rtype: str, tuple[str], list[str]
|
|
661
|
+
"""
|
|
662
|
+
data = self.unwrap_pipelinedata(data)[-1]
|
|
663
|
+
write_txt(data, self.filename, self.force_overwrite, self.append)
|
|
664
|
+
|
|
665
|
+
return data
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class YAMLWriter(Writer):
|
|
669
|
+
"""Writer for YAML files from `dict`-s."""
|
|
670
|
+
def write(self, data):
|
|
671
|
+
"""Write the last matching dictionary contained in `data` to
|
|
672
|
+
file (the schema mush match is a schema is provided).
|
|
673
|
+
|
|
674
|
+
:param data: The data to write to file.
|
|
675
|
+
:type data: list[PipelineData]
|
|
676
|
+
:raises TypeError: If the object contained in `data` is not a
|
|
677
|
+
`dict`.
|
|
678
|
+
:raises RuntimeError: If `filename` already exists and
|
|
679
|
+
`force_overwrite` is `False`.
|
|
680
|
+
:return: The data pipeline.
|
|
681
|
+
:rtype: list[PipelineData]
|
|
682
|
+
"""
|
|
683
|
+
# Third party modules
|
|
684
|
+
from pydantic import BaseModel
|
|
685
|
+
|
|
686
|
+
# Local modules
|
|
687
|
+
from CHAP.models import CHAPBaseModel
|
|
688
|
+
|
|
689
|
+
def get_dict(data):
|
|
690
|
+
if isinstance(data, dict):
|
|
691
|
+
return data
|
|
692
|
+
if isinstance(data, (BaseModel, CHAPBaseModel)):
|
|
693
|
+
try:
|
|
694
|
+
return data.model_dump()
|
|
695
|
+
except Exception:
|
|
696
|
+
pass
|
|
697
|
+
return None
|
|
698
|
+
|
|
699
|
+
schema = self.get_schema()
|
|
700
|
+
yaml_dict = None
|
|
701
|
+
if schema is not None:
|
|
702
|
+
for i, d in reversed(list(enumerate(data))):
|
|
703
|
+
if schema == d['schema']:
|
|
704
|
+
yaml_dict = get_dict(d['data'])
|
|
705
|
+
if yaml_dict is not None:
|
|
706
|
+
if self.remove:
|
|
707
|
+
data.pop(i)
|
|
708
|
+
break
|
|
709
|
+
if yaml_dict is None:
|
|
710
|
+
if schema is not None:
|
|
711
|
+
self.logger.warning(
|
|
712
|
+
f'Unable to find match with schema {schema}: '
|
|
713
|
+
'try finding a dictionary without matching schema')
|
|
714
|
+
for i, d in reversed(list(enumerate(data))):
|
|
715
|
+
yaml_dict = get_dict(d['data'])
|
|
716
|
+
if yaml_dict is not None:
|
|
717
|
+
if self.remove:
|
|
718
|
+
data.pop(i)
|
|
719
|
+
break
|
|
720
|
+
write_yaml(yaml_dict, self.filename, self.force_overwrite)
|
|
721
|
+
self.status = 'written' # Right now does nothing yet, but could
|
|
722
|
+
# add a sort of modification flag later
|
|
723
|
+
return data
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
if __name__ == '__main__':
|
|
727
|
+
# Local modules
|
|
728
|
+
from CHAP.writer import main
|
|
729
|
+
|
|
730
|
+
main()
|