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.
Files changed (70) hide show
  1. CHAP/TaskManager.py +216 -0
  2. CHAP/__init__.py +27 -0
  3. CHAP/common/__init__.py +57 -0
  4. CHAP/common/models/__init__.py +8 -0
  5. CHAP/common/models/common.py +124 -0
  6. CHAP/common/models/integration.py +659 -0
  7. CHAP/common/models/map.py +1291 -0
  8. CHAP/common/processor.py +2869 -0
  9. CHAP/common/reader.py +658 -0
  10. CHAP/common/utils.py +110 -0
  11. CHAP/common/writer.py +730 -0
  12. CHAP/edd/__init__.py +23 -0
  13. CHAP/edd/models.py +876 -0
  14. CHAP/edd/processor.py +3069 -0
  15. CHAP/edd/reader.py +1023 -0
  16. CHAP/edd/select_material_params_gui.py +348 -0
  17. CHAP/edd/utils.py +1572 -0
  18. CHAP/edd/writer.py +26 -0
  19. CHAP/foxden/__init__.py +19 -0
  20. CHAP/foxden/models.py +71 -0
  21. CHAP/foxden/processor.py +124 -0
  22. CHAP/foxden/reader.py +224 -0
  23. CHAP/foxden/utils.py +80 -0
  24. CHAP/foxden/writer.py +168 -0
  25. CHAP/giwaxs/__init__.py +11 -0
  26. CHAP/giwaxs/models.py +491 -0
  27. CHAP/giwaxs/processor.py +776 -0
  28. CHAP/giwaxs/reader.py +8 -0
  29. CHAP/giwaxs/writer.py +8 -0
  30. CHAP/inference/__init__.py +7 -0
  31. CHAP/inference/processor.py +69 -0
  32. CHAP/inference/reader.py +8 -0
  33. CHAP/inference/writer.py +8 -0
  34. CHAP/models.py +227 -0
  35. CHAP/pipeline.py +479 -0
  36. CHAP/processor.py +125 -0
  37. CHAP/reader.py +124 -0
  38. CHAP/runner.py +277 -0
  39. CHAP/saxswaxs/__init__.py +7 -0
  40. CHAP/saxswaxs/processor.py +8 -0
  41. CHAP/saxswaxs/reader.py +8 -0
  42. CHAP/saxswaxs/writer.py +8 -0
  43. CHAP/server.py +125 -0
  44. CHAP/sin2psi/__init__.py +7 -0
  45. CHAP/sin2psi/processor.py +8 -0
  46. CHAP/sin2psi/reader.py +8 -0
  47. CHAP/sin2psi/writer.py +8 -0
  48. CHAP/tomo/__init__.py +15 -0
  49. CHAP/tomo/models.py +210 -0
  50. CHAP/tomo/processor.py +3862 -0
  51. CHAP/tomo/reader.py +9 -0
  52. CHAP/tomo/writer.py +59 -0
  53. CHAP/utils/__init__.py +6 -0
  54. CHAP/utils/converters.py +188 -0
  55. CHAP/utils/fit.py +2947 -0
  56. CHAP/utils/general.py +2655 -0
  57. CHAP/utils/material.py +274 -0
  58. CHAP/utils/models.py +595 -0
  59. CHAP/utils/parfile.py +224 -0
  60. CHAP/writer.py +122 -0
  61. MLaaS/__init__.py +0 -0
  62. MLaaS/ktrain.py +205 -0
  63. MLaaS/mnist_img.py +83 -0
  64. MLaaS/tfaas_client.py +371 -0
  65. chessanalysispipeline-0.0.17.dev3.dist-info/LICENSE +60 -0
  66. chessanalysispipeline-0.0.17.dev3.dist-info/METADATA +29 -0
  67. chessanalysispipeline-0.0.17.dev3.dist-info/RECORD +70 -0
  68. chessanalysispipeline-0.0.17.dev3.dist-info/WHEEL +5 -0
  69. chessanalysispipeline-0.0.17.dev3.dist-info/entry_points.txt +2 -0
  70. 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()