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
|
@@ -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
|