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
@@ -0,0 +1,659 @@
1
+ """Map integration related Pydantic model classes."""
2
+
3
+ # System modules
4
+ import copy
5
+ from functools import cache
6
+ import os
7
+ from typing import Literal, Optional
8
+
9
+ # Third party modules
10
+ import numpy as np
11
+ from pydantic import (
12
+ FilePath,
13
+ confloat,
14
+ conint,
15
+ conlist,
16
+ constr,
17
+ field_validator,
18
+ )
19
+ from pyFAI import load as pyfai_load
20
+ from pyFAI.multi_geometry import MultiGeometry
21
+ from pyFAI.units import AZIMUTHAL_UNITS, RADIAL_UNITS
22
+
23
+ # Local modules
24
+ from CHAP.models import CHAPBaseModel
25
+
26
+ class Detector(CHAPBaseModel):
27
+ """Detector class to represent a single detector used in the
28
+ experiment.
29
+
30
+ :param prefix: Prefix of the detector in the SPEC file.
31
+ :type prefix: str
32
+ :param poni_file: Path to the poni file.
33
+ :type poni_file: str
34
+ :param mask_file: Optional path to the mask file.
35
+ :type mask_file: str, optional
36
+ """
37
+ prefix: constr(strip_whitespace=True, min_length=1)
38
+ poni_file: FilePath
39
+ mask_file: Optional[FilePath] = None
40
+
41
+ @field_validator('poni_file')
42
+ @classmethod
43
+ def validate_poni_file(cls, poni_file):
44
+ """Validate the poni file by checking if it's a valid PONI
45
+ file.
46
+
47
+ :param poni_file: Path to the poni file.
48
+ :type poni_file: str
49
+ :raises ValueError: If poni_file is not a valid PONI file.
50
+ :returns: Absolute path to the poni file.
51
+ :rtype: str
52
+ """
53
+ poni_file = os.path.abspath(poni_file)
54
+ try:
55
+ azimuthal_integrator(poni_file)
56
+ except Exception as exc:
57
+ raise ValueError(f'{poni_file} is not a valid PONI file') from exc
58
+ return poni_file
59
+
60
+ @field_validator('mask_file')
61
+ @classmethod
62
+ def validate_mask_file(cls, mask_file, values):
63
+ """Validate the mask file. If a mask file is provided, it
64
+ checks if it's a valid TIFF file.
65
+
66
+ :param mask_file: Path to the mask file.
67
+ :type mask_file: str or None
68
+ :param values: A dictionary of the Detector fields.
69
+ :type values: dict
70
+ :raises ValueError: If mask_file is provided and it's not a
71
+ valid TIFF file.
72
+ :raises ValueError: If `'poni_file'` is not provided in
73
+ `values`.
74
+ :returns: Absolute path to the mask file or None.
75
+ :rtype: Union[str, None]
76
+ """
77
+ if mask_file is None:
78
+ return mask_file
79
+
80
+ mask_file = os.path.abspath(mask_file)
81
+ poni_file = values.get('poni_file')
82
+ if poni_file is None:
83
+ raise ValueError(
84
+ 'Cannot validate mask file without a PONI file.')
85
+ try:
86
+ get_mask_array(mask_file, poni_file)
87
+ except BaseException as exc:
88
+ raise ValueError(
89
+ f'Unable to open {mask_file} as a TIFF file') from exc
90
+ return mask_file
91
+
92
+ @property
93
+ def azimuthal_integrator(self):
94
+ """Return the azimuthal integrator associated with this
95
+ detector.
96
+ """
97
+ return azimuthal_integrator(self.poni_file)
98
+
99
+ @property
100
+ def mask_array(self):
101
+ """Return the mask array assocated with this detector."""
102
+ return get_mask_array(self.mask_file, self.poni_file)
103
+
104
+
105
+ @cache
106
+ def azimuthal_integrator(poni_file):
107
+ """Return the azimuthal integrator from a PONI file.
108
+
109
+ :param poni_file: Path to a PONI file.
110
+ :type poni_file: str
111
+ :return: Azimuthal integrator.
112
+ :rtype: pyFAI.azimuthal_integrator.AzimuthalIntegrator
113
+ """
114
+ if not isinstance(poni_file, str):
115
+ poni_file = str(poni_file)
116
+ return pyfai_load(poni_file)
117
+
118
+
119
+ @cache
120
+ def get_mask_array(mask_file, poni_file):
121
+ """Return a mask array associated with a detector loaded from a
122
+ tiff file.
123
+
124
+ :param mask_file: Path to a .tiff file.
125
+ :type mask_file: str
126
+ :param poni_file: Path to a PONI file.
127
+ :type poni_file: str
128
+ :return: The mask array loaded from `mask_file`.
129
+ :rtype: numpy.ndarray
130
+ """
131
+ if mask_file is not None:
132
+ # Third party modules
133
+ from pyspec.file.tiff import TiffFile
134
+
135
+ if not isinstance(mask_file, str):
136
+ mask_file = str(mask_file)
137
+
138
+ with TiffFile(mask_file) as tiff:
139
+ mask_array = tiff.asarray()
140
+ else:
141
+ mask_array = np.zeros(azimuthal_integrator(poni_file).detector.shape)
142
+ return mask_array
143
+
144
+
145
+ class IntegrationConfig(CHAPBaseModel):
146
+ """Class representing the configuration for a raw detector data
147
+ integration.
148
+
149
+ :ivar tool_type: Integration tool type; always set to
150
+ `"integration"`.
151
+ :type tool_type: str, optional
152
+ :ivar title: Integration title.
153
+ :type title: str
154
+ :ivar integration_type: Integration type.
155
+ :type integration_type: Literal['azimuthal', 'radial', 'cake']
156
+ :ivar detectors: List of detectors used in the integration.
157
+ :type detectors: List[Detector]
158
+ :ivar radial_units: Radial units for the integration, defaults to
159
+ `'q_A^-1'`.
160
+ :type radial_units: str, optional
161
+ :ivar radial_min: Minimum radial value for the integration range.
162
+ :type radial_min: float, optional
163
+ :ivar radial_max: Maximum radial value for the integration range.
164
+ :type radial_max: float, optional
165
+ :ivar radial_npt: Number of points in the radial range for the
166
+ integration, defaults to `1800`.
167
+ :type radial_npt: int, optional
168
+ :ivar azimuthal_units: Azimuthal units for the integration,
169
+ defaults to `chi_deg`.
170
+ :type azimuthal_units: str, optional
171
+ :ivar azimuthal_min: Minimum azimuthal value for the integration
172
+ range, defaults to `-180`.
173
+ :type azimuthal_min: float, optional
174
+ :ivar azimuthal_max: Maximum azimuthal value for the integration
175
+ range, defaults to `180`.
176
+ :type azimuthal_max: float, optional
177
+ :ivar azimuthal_npt: Number of points in the azimuthal range for
178
+ the integration, defaults to `3600`.
179
+ :type azimuthal_npt: int, optional
180
+ :ivar error_model: Integration error model.
181
+ :type error_model: Literal['poisson', 'azimuthal'], optional
182
+ :ivar sequence_index: Sequence index for the correction NXprocess
183
+ object in the Nexus file.
184
+ :type sequence_index: int, optional
185
+ """
186
+ tool_type: Literal['integration'] = 'integration'
187
+ title: constr(strip_whitespace=True, min_length=1)
188
+ integration_type: Literal['azimuthal', 'radial', 'cake']
189
+ detectors: conlist(item_type=Detector, min_length=1)
190
+ radial_units: str = 'q_A^-1'
191
+ radial_min: confloat(ge=0)
192
+ radial_max: confloat(gt=0)
193
+ radial_npt: conint(gt=0) = 1800
194
+ azimuthal_units: str = 'chi_deg'
195
+ azimuthal_min: confloat(ge=-180) = -180
196
+ azimuthal_max: confloat(le=360) = 180
197
+ azimuthal_npt: conint(gt=0) = 3600
198
+ error_model: Optional[Literal['poisson', 'azimuthal']] = None
199
+ sequence_index: Optional[conint(gt=0)] = None
200
+
201
+ @field_validator('radial_units')
202
+ @classmethod
203
+ def validate_radial_units(cls, radial_units):
204
+ """Validate the radial units for the integration.
205
+
206
+ :param radial_units: Unvalidated radial units for the
207
+ integration.
208
+ :type radial_units: str
209
+ :raises ValueError: If radial units are not one of the
210
+ recognized radial units.
211
+ :return: Validated radial units.
212
+ :rtype: str
213
+ """
214
+ if radial_units in RADIAL_UNITS.keys():
215
+ return radial_units
216
+ raise ValueError(
217
+ f'Invalid radial units: {radial_units}. '
218
+ f'Must be one of {", ".join(RADIAL_UNITS.keys())}')
219
+
220
+ @field_validator('azimuthal_units')
221
+ @classmethod
222
+ def validate_azimuthal_units(cls, azimuthal_units):
223
+ """Validate that `azimuthal_units` is one of the keys in the
224
+ `pyFAI.units.AZIMUTHAL_UNITS` dictionary.
225
+
226
+ :param azimuthal_units: The string representing the unit to be
227
+ validated.
228
+ :type azimuthal_units: str
229
+ :raises ValueError: If `azimuthal_units` is not one of the
230
+ keys in `pyFAI.units.AZIMUTHAL_UNITS`.
231
+ :return: The original supplied value, if is one of the keys in
232
+ `pyFAI.units.AZIMUTHAL_UNITS`.
233
+ :rtype: str
234
+ """
235
+ if azimuthal_units in AZIMUTHAL_UNITS.keys():
236
+ return azimuthal_units
237
+ raise ValueError(
238
+ f'Invalid azimuthal units: {azimuthal_units}. '
239
+ f'Must be one of {", ".join(AZIMUTHAL_UNITS.keys())}')
240
+
241
+ # FIX
242
+ # def validate_range_max(self, range_name):
243
+ # """Validate the maximum value of an integration range.
244
+ #
245
+ # :param range_name: The name of the integration range
246
+ # (e.g. radial, azimuthal).
247
+ # :type range_name: str
248
+ # :return: The callable that performs the validation.
249
+ # :rtype: callable
250
+ # """
251
+ # def _validate_range_max(cls, range_max, info):
252
+ # """Check if the maximum value of the integration range is
253
+ # greater than its minimum value.
254
+ #
255
+ # :param range_max: The maximum value of the integration
256
+ # range.
257
+ # :type range_max: float
258
+ # :param info: Pydantic validator info object.
259
+ # :type info: pydantic_core._pydantic_core.ValidationInfo
260
+ # :raises ValueError: If the maximum value of the
261
+ # integration range is not greater than its minimum
262
+ # value.
263
+ # :return: The validated maximum range value.
264
+ # :rtype: float
265
+ # """
266
+ # range_min = info.data.get(f'{range_name}_min')
267
+ # if range_min < range_max:
268
+ # return range_max
269
+ # raise ValueError(
270
+ # 'Maximum value of integration range must be '
271
+ # 'greater than minimum value of integration range '
272
+ # f'({range_name}_min={range_min}).')
273
+ # return _validate_range_max
274
+ #
275
+ # _validate_radial_max = field_validator(
276
+ # 'radial_max')(validate_range_max('radial'))
277
+ # _validate_azimuthal_max = field_validator(
278
+ # 'azimuthal_max')(validate_range_max('azimuthal'))
279
+
280
+ def validate_for_map_config(self, map_config):
281
+ """Validate the existence of the detector data file for all
282
+ scan points in `map_config`.
283
+
284
+ :param map_config: The `MapConfig` instance to validate
285
+ against.
286
+ :type map_config: MapConfig
287
+ :raises RuntimeError: If a detector data file could not be
288
+ found for a scan point occurring in `map_config`.
289
+ """
290
+ for detector in self.detectors:
291
+ for scans in map_config.spec_scans:
292
+ for scan_number in scans.scan_numbers:
293
+ scanparser = scans.get_scanparser(scan_number)
294
+ for scan_step_index in range(scanparser.spec_scan_npts):
295
+ # Make sure the detector data file exists for
296
+ # all scan points
297
+ try:
298
+ scanparser.get_detector_data_file(
299
+ detector.prefix, scan_step_index)
300
+ except Exception as exc:
301
+ raise RuntimeError(
302
+ 'Could not find data file for detector prefix '
303
+ f'{detector.prefix} '
304
+ f'on scan number {scan_number} '
305
+ f'in spec file {scans.spec_file}') from exc
306
+
307
+ def get_azimuthal_adjustments(self):
308
+ """To enable a continuous range of integration in the
309
+ azimuthal direction for radial and cake integration, obtain
310
+ adjusted values for this `IntegrationConfig`'s `azimuthal_min`
311
+ and `azimuthal_max` values, the angle amount by which those
312
+ values were adjusted, and the proper location of the
313
+ discontinuity in the azimuthal direction.
314
+
315
+ :return: Adjusted chi_min, adjusted chi_max, chi_offset,
316
+ chi_discontinuity.
317
+ :rtype: tuple[float, float, float, float]
318
+ """
319
+ return get_azimuthal_adjustments(
320
+ self.azimuthal_min, self.azimuthal_max)
321
+
322
+ def get_azimuthal_integrators(self):
323
+ """Get a list of AzimuthalIntegrator`\ s that correspond to the
324
+ detector configurations in this instance of
325
+ `IntegrationConfig`.
326
+
327
+ The returned `AzimuthalIntegrator`\ s are (if need be)
328
+ artificially rotated in the azimuthal direction to achieve a
329
+ continuous range of integration in the azimuthal direction.
330
+
331
+ :returns: A list of `AzimuthalIntegrator`\ s appropriate for use
332
+ by this `IntegrationConfig` tool.
333
+ :rtype: list[pyFAI.azimuthalIntegrator.AzimuthalIntegrator]
334
+ """
335
+ chi_offset = self.get_azimuthal_adjustments()[2]
336
+ return get_azimuthal_integrators(
337
+ tuple([detector.poni_file for detector in self.detectors]),
338
+ chi_offset=chi_offset)
339
+
340
+ def get_multi_geometry_integrator(self):
341
+ """Get a `MultiGeometry` integrator suitable for use by this
342
+ instance of `IntegrationConfig`.
343
+
344
+ :return: A `MultiGeometry` integrator.
345
+ :rtype: pyFAI.multi_geometry.MultiGeometry
346
+ """
347
+ poni_files = tuple([detector.poni_file for detector in self.detectors])
348
+ radial_range = (self.radial_min, self.radial_max)
349
+ azimuthal_range = (self.azimuthal_min, self.azimuthal_max)
350
+ return get_multi_geometry_integrator(
351
+ poni_files, self.radial_units, radial_range, azimuthal_range)
352
+
353
+ def get_azimuthally_integrated_data(
354
+ self, spec_scans, scan_number, scan_step_index):
355
+ """Return azimuthally-integrated data for the scan step
356
+ specified.
357
+
358
+ :param spec_scans: An instance of `SpecScans` containing the
359
+ scan step requested.
360
+ :type spec_scans: SpecScans
361
+ :param scan_number: The number of the scan containing the scan
362
+ step requested.
363
+ :type scan_number: int
364
+ :param scan_step_index: The index of the scan step requested.
365
+ :type scan_step_index: int
366
+ :return: A 1D array of azimuthally-integrated raw detector
367
+ intensities.
368
+ :rtype: np.ndarray
369
+ """
370
+ detector_data = spec_scans.get_detector_data(self.detectors,
371
+ scan_number,
372
+ scan_step_index)
373
+ integrator = self.get_multi_geometry_integrator()
374
+ lst_mask = [detector.mask_array for detector in self.detectors]
375
+ result = integrator.integrate1d(detector_data,
376
+ lst_mask=lst_mask,
377
+ npt=self.radial_npt,
378
+ error_model=self.error_model)
379
+ if result.sigma is None:
380
+ return result.intensity
381
+ return result.intensity, result.sigma
382
+
383
+ def get_radially_integrated_data(
384
+ self, spec_scans, scan_number, scan_step_index):
385
+ """Return radially-integrated data for the scan step
386
+ specified.
387
+
388
+ :param spec_scans: An instance of `SpecScans` containing the
389
+ scan step requested.
390
+ :type spec_scans: SpecScans
391
+ :param scan_number: The number of the scan containing the scan
392
+ step requested.
393
+ :type scan_number: int
394
+ :param scan_step_index: The index of the scan step requested.
395
+ :type scan_step_index: int
396
+ :return: A 1D array of radially-integrated raw detector
397
+ intensities.
398
+ :rtype: np.ndarray
399
+ """
400
+ # Handle idiosyncracies of azimuthal ranges in pyFAI Adjust
401
+ # chi ranges to get a continuous range of iintegrated data
402
+ chi_min, chi_max, _, _ = self.get_azimuthal_adjustments()
403
+ # Perform radial integration on a detector-by-detector basis.
404
+ intensity_each_detector = []
405
+ variance_each_detector = []
406
+ integrators = self.get_azimuthal_integrators()
407
+ for integrator, detector in zip(integrators, self.detectors):
408
+ detector_data = spec_scans.get_detector_data(
409
+ [detector], scan_number, scan_step_index)[0]
410
+ result = integrator.integrate_radial(
411
+ detector_data,
412
+ self.azimuthal_npt,
413
+ unit=self.azimuthal_units,
414
+ azimuth_range=(chi_min, chi_max),
415
+ radial_unit=self.radial_units,
416
+ radial_range=(self.radial_min, self.radial_max),
417
+ mask=detector.mask_array) # , error_model=self.error_model)
418
+ intensity_each_detector.append(result.intensity)
419
+ if result.sigma is not None:
420
+ variance_each_detector.append(result.sigma**2)
421
+ # Add the individual detectors' integrated intensities
422
+ # together
423
+ intensity = np.nansum(intensity_each_detector, axis=0)
424
+ # Ignore data at values of chi for which there was no data
425
+ intensity = np.where(intensity == 0, np.nan, intensity)
426
+ if len(intensity_each_detector) != len(variance_each_detector):
427
+ return intensity
428
+
429
+ # Get the standard deviation of the summed detectors'
430
+ # intensities
431
+ sigma = np.sqrt(np.nansum(variance_each_detector, axis=0))
432
+ return intensity, sigma
433
+
434
+ def get_cake_integrated_data(
435
+ self, spec_scans, scan_number, scan_step_index):
436
+ """Return cake-integrated data for the scan step specified.
437
+
438
+ :param spec_scans: An instance of `SpecScans` containing the
439
+ scan step requested.
440
+ :type spec_scans: SpecScans
441
+ :param scan_number: The number of the scan containing the scan
442
+ step requested.
443
+ :type scan_number: int
444
+ :param scan_step_index: The index of the scan step requested.
445
+ :type scan_step_index: int
446
+ :return: A 2D array of cake-integrated raw detector
447
+ intensities.
448
+ :rtype: np.ndarray
449
+ """
450
+ detector_data = spec_scans.get_detector_data(
451
+ self.detectors, scan_number, scan_step_index)
452
+ integrator = self.get_multi_geometry_integrator()
453
+ lst_mask = [detector.mask_array for detector in self.detectors]
454
+ result = integrator.integrate2d(
455
+ detector_data,
456
+ lst_mask=lst_mask,
457
+ npt_rad=self.radial_npt,
458
+ npt_azim=self.azimuthal_npt,
459
+ method='bbox',
460
+ error_model=self.error_model)
461
+ if result.sigma is None:
462
+ return result.intensity
463
+ return result.intensity, result.sigma
464
+
465
+ def get_integrated_data(
466
+ self, spec_scans, scan_number, scan_step_index):
467
+ """Return integrated data for the scan step specified.
468
+
469
+ :param spec_scans: An instance of `SpecScans` containing the
470
+ scan step requested.
471
+ :type spec_scans: SpecScans
472
+ :param scan_number: The number of the scan containing the scan
473
+ step requested.
474
+ :type scan_number: int
475
+ :param scan_step_index: The index of the scan step requested.
476
+ :type scan_step_index: int
477
+ :return: An array of integrated raw detector intensities.
478
+ :rtype: np.ndarray
479
+ """
480
+ if self.integration_type == 'azimuthal':
481
+ return self.get_azimuthally_integrated_data(spec_scans,
482
+ scan_number,
483
+ scan_step_index)
484
+ if self.integration_type == 'radial':
485
+ return self.get_radially_integrated_data(spec_scans,
486
+ scan_number,
487
+ scan_step_index)
488
+ if self.integration_type == 'cake':
489
+ return self.get_cake_integrated_data(spec_scans,
490
+ scan_number,
491
+ scan_step_index)
492
+ return None
493
+
494
+ @property
495
+ def integrated_data_coordinates(self):
496
+ """Return a dictionary of coordinate arrays for navigating the
497
+ dimension(s) of the integrated data produced by this instance
498
+ of `IntegrationConfig`.
499
+
500
+ :return: A dictionary with either one or two keys: 'azimuthal'
501
+ and/or 'radial', each of which points to a 1-D `numpy`
502
+ array of coordinate values.
503
+ :rtype: dict[str, np.ndarray]
504
+ """
505
+ if self.integration_type == 'azimuthal':
506
+ return get_integrated_data_coordinates(
507
+ radial_range=(self.radial_min, self.radial_max),
508
+ radial_npt=self.radial_npt)
509
+ if self.integration_type == 'radial':
510
+ return get_integrated_data_coordinates(
511
+ azimuthal_range=(self.azimuthal_min, self.azimuthal_max),
512
+ azimuthal_npt=self.azimuthal_npt)
513
+ if self.integration_type == 'cake':
514
+ return get_integrated_data_coordinates(
515
+ radial_range=(self.radial_min, self.radial_max),
516
+ radial_npt=self.radial_npt,
517
+ azimuthal_range=(self.azimuthal_min, self.azimuthal_max),
518
+ azimuthal_npt=self.azimuthal_npt)
519
+ return None
520
+
521
+ @property
522
+ def integrated_data_dims(self):
523
+ """Return a tuple of the coordinate labels for the integrated
524
+ data produced by this instance of `IntegrationConfig`.
525
+ """
526
+ directions = list(self.integrated_data_coordinates.keys())
527
+ dim_names = [getattr(self, f'{direction}_units')
528
+ for direction in directions]
529
+ return dim_names
530
+
531
+ @property
532
+ def integrated_data_shape(self):
533
+ """Return a tuple representing the shape of the integrated
534
+ data produced by this instance of `IntegrationConfig` for a
535
+ single scan step.
536
+ """
537
+ return tuple([len(coordinate_values)
538
+ for coordinate_name, coordinate_values
539
+ in self.integrated_data_coordinates.items()])
540
+
541
+
542
+ @cache
543
+ def get_azimuthal_adjustments(chi_min, chi_max):
544
+ """Fix chi discontinuity at 180 degrees and return the adjusted
545
+ chi range, offset, and discontinuity.
546
+
547
+ If the discontinuity is crossed, obtain the offset to artificially
548
+ rotate detectors to achieve a continuous azimuthal integration
549
+ range.
550
+
551
+ :param chi_min: The minimum value of the azimuthal range.
552
+ :type chi_min: float
553
+ :param chi_max: The maximum value of the azimuthal range.
554
+ :type chi_max: float
555
+ :return: The following four values: the adjusted minimum value of
556
+ the azimuthal range, the adjusted maximum value of the
557
+ azimuthal range, the value by which the chi angle was
558
+ adjusted, the position of the chi discontinuity.
559
+ :rtype: tuple[float, float, float, float]
560
+ """
561
+ # Fix chi discontinuity at 180 degrees for now.
562
+ chi_disc = 180
563
+ # If the discontinuity is crossed, artificially rotate the
564
+ # detectors to achieve a continuous azimuthal integration range
565
+ if chi_min < chi_disc < chi_max:
566
+ chi_offset = chi_max - chi_disc
567
+ else:
568
+ chi_offset = 0
569
+ return chi_min-chi_offset, chi_max-chi_offset, chi_offset, chi_disc
570
+
571
+
572
+ @cache
573
+ def get_azimuthal_integrators(poni_files, chi_offset=0):
574
+ """Return a list of `AzimuthalIntegrator` objects generated from
575
+ PONI files.
576
+
577
+ :param poni_files: Tuple of strings, each string being a path to a
578
+ PONI file.
579
+ :type poni_files: tuple
580
+ :param chi_offset: The angle in degrees by which the
581
+ `AzimuthalIntegrator` objects will be rotated, defaults to `0`.
582
+ :type chi_offset: float, optional
583
+ :return: List of `AzimuthalIntegrator` objects.
584
+ :rtype: list[pyFAI.azimuthalIntegrator.AzimuthalIntegrator]
585
+ """
586
+ ais = []
587
+ for poni_file in poni_files:
588
+ ai = copy.deepcopy(azimuthal_integrator(poni_file))
589
+ ai.rot3 += chi_offset * np.pi/180
590
+ ais.append(ai)
591
+ return ais
592
+
593
+
594
+ @cache
595
+ def get_multi_geometry_integrator(
596
+ poni_files, radial_unit, radial_range, azimuthal_range):
597
+ """Return a `MultiGeometry` instance that can be used for
598
+ azimuthal or cake integration.
599
+
600
+ :param poni_files: Tuple of PONI files that describe the detectors
601
+ to be integrated.
602
+ :type poni_files: tuple
603
+ :param radial_unit: Unit to use for radial integration range.
604
+ :type radial_unit: str
605
+ :param radial_range: Radial integration range.
606
+ :type radial_range: tuple[float, float]
607
+ :param azimuthal_range: Azimuthal integration range.
608
+ :type azimuthal_range: tuple[float, float]
609
+ :return: `MultiGeometry` instance that can be used for azimuthal
610
+ or cake integration.
611
+ :rtype: pyFAI.multi_geometry.MultiGeometry
612
+ """
613
+ chi_min, chi_max, chi_offset, chi_disc = \
614
+ get_azimuthal_adjustments(*azimuthal_range)
615
+ ais = copy.deepcopy(get_azimuthal_integrators(poni_files,
616
+ chi_offset=chi_offset))
617
+ multi_geometry = MultiGeometry(
618
+ ais,
619
+ unit=radial_unit,
620
+ radial_range=radial_range,
621
+ azimuth_range=(chi_min, chi_max),
622
+ wavelength=sum([ai.wavelength for ai in ais])/len(ais),
623
+ chi_disc=chi_disc)
624
+ return multi_geometry
625
+
626
+
627
+ @cache
628
+ def get_integrated_data_coordinates(
629
+ azimuthal_range=None, azimuthal_npt=None, radial_range=None,
630
+ radial_npt=None):
631
+ """Return a dictionary of coordinate arrays for the specified
632
+ radial and/or azimuthal integration ranges.
633
+
634
+ :param azimuthal_range: Tuple specifying the range of azimuthal
635
+ angles over which to generate coordinates, in the format (min,
636
+ max).
637
+ :type azimuthal_range: tuple[float, float], optional
638
+ :param azimuthal_npt: Number of azimuthal coordinate points to
639
+ generate.
640
+ :type azimuthal_npt: int, optional
641
+ :param radial_range: Tuple specifying the range of radial
642
+ distances over which to generate coordinates, in the format
643
+ (min, max).
644
+ :type radial_range: tuple[float, float], optional
645
+ :param radial_npt: Number of radial coordinate points to generate.
646
+ :type radial_npt: int, optional
647
+ :return: A dictionary with either one or two keys: 'azimuthal'
648
+ and/or 'radial', each of which points to a 1-D `numpy` array
649
+ of coordinate values.
650
+ :rtype: dict[str, np.ndarray]
651
+ """
652
+ integrated_data_coordinates = {}
653
+ if azimuthal_range is not None and azimuthal_npt is not None:
654
+ integrated_data_coordinates['azimuthal'] = np.linspace(
655
+ *azimuthal_range, azimuthal_npt)
656
+ if radial_range is not None and radial_npt is not None:
657
+ integrated_data_coordinates['radial'] = np.linspace(
658
+ *radial_range, radial_npt)
659
+ return integrated_data_coordinates