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/edd/models.py
ADDED
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
"""EDD Pydantic model classes."""
|
|
2
|
+
|
|
3
|
+
# System modules
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
import os
|
|
6
|
+
from typing import (
|
|
7
|
+
Dict,
|
|
8
|
+
Literal,
|
|
9
|
+
Optional,
|
|
10
|
+
Union,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Third party modules
|
|
14
|
+
import numpy as np
|
|
15
|
+
from hexrd.material import Material
|
|
16
|
+
#from CHAP.utils.material import Material
|
|
17
|
+
from pydantic import (
|
|
18
|
+
Field,
|
|
19
|
+
FilePath,
|
|
20
|
+
PrivateAttr,
|
|
21
|
+
confloat,
|
|
22
|
+
conint,
|
|
23
|
+
conlist,
|
|
24
|
+
constr,
|
|
25
|
+
field_validator,
|
|
26
|
+
model_validator,
|
|
27
|
+
)
|
|
28
|
+
from scipy.interpolate import interp1d
|
|
29
|
+
from typing_extensions import Annotated
|
|
30
|
+
|
|
31
|
+
# Local modules
|
|
32
|
+
from CHAP.models import CHAPBaseModel
|
|
33
|
+
from CHAP.common.models.map import Detector
|
|
34
|
+
from CHAP.utils.models import Multipeak
|
|
35
|
+
#from CHAP.utils.parfile import ParFile
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Baseline configuration class
|
|
39
|
+
|
|
40
|
+
class BaselineConfig(CHAPBaseModel):
|
|
41
|
+
"""Baseline model configuration class.
|
|
42
|
+
|
|
43
|
+
:ivar lam: The &lambda (smoothness) parameter (the balance
|
|
44
|
+
between the residual of the data and the baseline and the
|
|
45
|
+
smoothness of the baseline). The suggested range is between
|
|
46
|
+
100 and 10^8, defaults to `10^6`.
|
|
47
|
+
:type lam: float, optional
|
|
48
|
+
:ivar max_iter: The maximum number of iterations,
|
|
49
|
+
defaults to `100`.
|
|
50
|
+
:type max_iter: int, optional
|
|
51
|
+
:ivar tol: The convergence tolerence, defaults to `1.e-6`.
|
|
52
|
+
:type tol: float, optional
|
|
53
|
+
"""
|
|
54
|
+
attrs: Optional[dict] = {}
|
|
55
|
+
lam: confloat(gt=0, allow_inf_nan=False) = 1.e6
|
|
56
|
+
max_iter: conint(gt=0) = 100
|
|
57
|
+
tol: confloat(gt=0, allow_inf_nan=False) = 1.e-6
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Fit configuration class
|
|
61
|
+
|
|
62
|
+
class FitConfig(CHAPBaseModel):
|
|
63
|
+
"""Fit parameters configuration class for peak fitting.
|
|
64
|
+
|
|
65
|
+
:ivar background: Background model for peak fitting, defaults
|
|
66
|
+
to `constant`.
|
|
67
|
+
:type background: str, list[str], optional
|
|
68
|
+
:ivar baseline: Automated baseline subtraction configuration,
|
|
69
|
+
defaults to `False`.
|
|
70
|
+
:type baseline: Union(bool, BaselineConfig), optional
|
|
71
|
+
:ivar centers_range: Peak centers range for peak fitting.
|
|
72
|
+
The allowed range for the peak centers will be the initial
|
|
73
|
+
values ± `centers_range` (in MCA channels for calibration
|
|
74
|
+
or keV for strain analysis). Defaults to `20` for calibration
|
|
75
|
+
and `2.0` for strain analysis.
|
|
76
|
+
:type centers_range: float, optional
|
|
77
|
+
:ivar energy_mask_ranges: List of MCA energy mask ranges in keV
|
|
78
|
+
for selecting the data to be included after applying a mask
|
|
79
|
+
(bounds are inclusive). Specify either energy_mask_ranges or
|
|
80
|
+
mask_ranges, not both.
|
|
81
|
+
:type energy_mask_ranges: list[[float, float]], optional
|
|
82
|
+
:ivar fwhm_min: Minimum FWHM for peak fitting (in MCA channels
|
|
83
|
+
for calibration or keV for strain analysis). Defaults to `3`
|
|
84
|
+
for calibration and `0.25` for strain analysis.
|
|
85
|
+
:type fwhm_min: float, optional
|
|
86
|
+
:ivar fwhm_max: Maximum FWHM for peak fitting (in MCA channels
|
|
87
|
+
for calibration or keV for strain analysis). Defaults to `25`
|
|
88
|
+
for calibration and `2.0` for strain analysis.
|
|
89
|
+
:type fwhm_max: float, optional
|
|
90
|
+
:ivar mask_ranges: List of MCA channel bin ranges for selecting
|
|
91
|
+
the data to be included in the energy calibration after
|
|
92
|
+
applying a mask (bounds are inclusive). Specify for
|
|
93
|
+
energy calibration only.
|
|
94
|
+
:type mask_ranges: list[[int, int]], optional
|
|
95
|
+
:ivar backgroundpeaks: Additional background peaks (their
|
|
96
|
+
associated fit parameters in units of keV).
|
|
97
|
+
:type backgroundpeaks: CHAP.utils.models.Multipeak, optional
|
|
98
|
+
"""
|
|
99
|
+
background: Optional[conlist(item_type=constr(
|
|
100
|
+
strict=True, strip_whitespace=True, to_lower=True))] = ['constant']
|
|
101
|
+
baseline: Optional[Union[bool, BaselineConfig]] = None
|
|
102
|
+
centers_range: Optional[confloat(gt=0, allow_inf_nan=False)] = 20
|
|
103
|
+
energy_mask_ranges: Optional[conlist(
|
|
104
|
+
min_length=1,
|
|
105
|
+
item_type=conlist(
|
|
106
|
+
min_length=2,
|
|
107
|
+
max_length=2,
|
|
108
|
+
item_type=confloat(allow_inf_nan=False)))] = None
|
|
109
|
+
fwhm_min: Optional[confloat(gt=0, allow_inf_nan=False)] = 3
|
|
110
|
+
fwhm_max: Optional[confloat(gt=0, allow_inf_nan=False)] = 25
|
|
111
|
+
mask_ranges: Optional[conlist(
|
|
112
|
+
min_length=1,
|
|
113
|
+
item_type=conlist(
|
|
114
|
+
min_length=2,
|
|
115
|
+
max_length=2,
|
|
116
|
+
item_type=conint(ge=0)))] = None
|
|
117
|
+
backgroundpeaks: Optional[Multipeak] = None
|
|
118
|
+
|
|
119
|
+
@field_validator('background', mode='before')
|
|
120
|
+
@classmethod
|
|
121
|
+
def validate_background(cls, background):
|
|
122
|
+
"""Validate the background model.
|
|
123
|
+
|
|
124
|
+
:ivar background: Background model for peak fitting.
|
|
125
|
+
:type background: str, list[str], optional
|
|
126
|
+
:return: List of validated background models.
|
|
127
|
+
:rtype: list[str]
|
|
128
|
+
"""
|
|
129
|
+
if background is None:
|
|
130
|
+
return background
|
|
131
|
+
if isinstance(background, str):
|
|
132
|
+
return [background]
|
|
133
|
+
return sorted(background)
|
|
134
|
+
|
|
135
|
+
@field_validator('baseline', mode='before')
|
|
136
|
+
@classmethod
|
|
137
|
+
def validate_baseline(cls, baseline):
|
|
138
|
+
"""Validate the baseline configuration.
|
|
139
|
+
|
|
140
|
+
:ivar baseline: Automated baseline subtraction configuration.
|
|
141
|
+
:type baseline: Union(bool, BaselineConfig), optional
|
|
142
|
+
:return: Validated baseline subtraction configuration.
|
|
143
|
+
:rtype: bool, BaselineConfig
|
|
144
|
+
"""
|
|
145
|
+
if isinstance(baseline, bool) and baseline:
|
|
146
|
+
return BaselineConfig()
|
|
147
|
+
return baseline
|
|
148
|
+
|
|
149
|
+
@field_validator('energy_mask_ranges', mode='before')
|
|
150
|
+
@classmethod
|
|
151
|
+
def validate_energy_mask_ranges(cls, energy_mask_ranges):
|
|
152
|
+
"""Validate the mask ranges for selecting the data to include.
|
|
153
|
+
|
|
154
|
+
:ivar energy_mask_ranges: List of MCA energy mask ranges in keV
|
|
155
|
+
for selecting the data to be included after applying a mask
|
|
156
|
+
(bounds are inclusive).
|
|
157
|
+
:type energy_mask_ranges: list[[float, float]], optional
|
|
158
|
+
:return: Validated energy mask ranges.
|
|
159
|
+
:rtype: list[[float, float]]
|
|
160
|
+
"""
|
|
161
|
+
if energy_mask_ranges:
|
|
162
|
+
return sorted([sorted(v) for v in energy_mask_ranges])
|
|
163
|
+
return energy_mask_ranges
|
|
164
|
+
|
|
165
|
+
@field_validator('mask_ranges', mode='before')
|
|
166
|
+
@classmethod
|
|
167
|
+
def validate_mask_ranges(cls, mask_ranges):
|
|
168
|
+
"""Validate the mask ranges for selecting the data to include.
|
|
169
|
+
|
|
170
|
+
:ivar mask_ranges: List of MCA channel bin ranges for selecting
|
|
171
|
+
the data to be included after applying a mask
|
|
172
|
+
(bounds are inclusive).
|
|
173
|
+
:type mask_ranges: list[[int, int]], optional
|
|
174
|
+
:return: Validated mask ranges.
|
|
175
|
+
:rtype: list[[int, int]]
|
|
176
|
+
"""
|
|
177
|
+
if mask_ranges:
|
|
178
|
+
return sorted([sorted(v) for v in mask_ranges])
|
|
179
|
+
return mask_ranges
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# Material configuration class
|
|
183
|
+
|
|
184
|
+
class MaterialConfig(CHAPBaseModel):
|
|
185
|
+
"""Sample material parameters configuration class.
|
|
186
|
+
|
|
187
|
+
:ivar material_name: Sample material name.
|
|
188
|
+
:type material_name: str, optional
|
|
189
|
+
:ivar lattice_parameters: Lattice spacing(s) in angstroms.
|
|
190
|
+
:type lattice_parameters: float, list[float], optional
|
|
191
|
+
:ivar sgnum: Space group of the material.
|
|
192
|
+
:type sgnum: int, optional
|
|
193
|
+
"""
|
|
194
|
+
#RV FIX create a getter for lattice_parameters that always returns a list?
|
|
195
|
+
material_name: Optional[constr(strip_whitespace=True, min_length=1)] = None
|
|
196
|
+
lattice_parameters: Optional[Union[
|
|
197
|
+
confloat(gt=0, allow_inf_nan=False),
|
|
198
|
+
conlist(
|
|
199
|
+
min_length=1, max_length=6,
|
|
200
|
+
item_type=confloat(gt=0, allow_inf_nan=False))]] = None
|
|
201
|
+
sgnum: Optional[conint(ge=0)] = None
|
|
202
|
+
|
|
203
|
+
_material: Optional[Material]
|
|
204
|
+
|
|
205
|
+
@model_validator(mode='after')
|
|
206
|
+
def validate_materialconfig_after(self):
|
|
207
|
+
"""Create and validate the private attribute _material.
|
|
208
|
+
|
|
209
|
+
:return: The validated list of class properties.
|
|
210
|
+
:rtype: MaterialConfig
|
|
211
|
+
"""
|
|
212
|
+
# Local modules
|
|
213
|
+
from CHAP.edd.utils import make_material
|
|
214
|
+
# from CHAP.utils.material import Material
|
|
215
|
+
|
|
216
|
+
self._material = make_material(
|
|
217
|
+
self.material_name, self.sgnum, self.lattice_parameters)
|
|
218
|
+
# self._material = Material.make_material(
|
|
219
|
+
# self.material_name, sgnum=self.sgnum,
|
|
220
|
+
# lattice_parameters_angstroms=self.lattice_parameters,
|
|
221
|
+
# pos=['4a', '8c'])
|
|
222
|
+
#pos=[(0,0,0), (1/4, 1/4, 1/4), (3/4, 3/4, 3/4)])
|
|
223
|
+
self.lattice_parameters = list([
|
|
224
|
+
x.getVal('angstrom') if x.isLength() else x.getVal('radians')
|
|
225
|
+
for x in self._material._lparms])
|
|
226
|
+
return self
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# Detector configuration classes
|
|
230
|
+
|
|
231
|
+
class MCADetectorCalibration(Detector, FitConfig):
|
|
232
|
+
"""Class representing metadata required to configure a single MCA
|
|
233
|
+
detector element to perform detector calibration.
|
|
234
|
+
|
|
235
|
+
:ivar energy_calibration_coeffs: Detector channel index to energy
|
|
236
|
+
polynomial conversion coefficients ([a, b, c] with
|
|
237
|
+
E_i = a*i^2 + b*i + c).
|
|
238
|
+
:type energy_calibration_coeffs:
|
|
239
|
+
list[float, float, float], optional
|
|
240
|
+
:ivar num_bins: Number of MCA channels.
|
|
241
|
+
:type num_bins: int, optional
|
|
242
|
+
:ivar tth_max: Detector rotation about lab frame x axis.
|
|
243
|
+
:type tth_max: float, optional
|
|
244
|
+
:ivar tth_tol: Minimum resolvable difference in 2&theta between
|
|
245
|
+
two unique Bragg peaks,
|
|
246
|
+
:type tth_tol: float, optional
|
|
247
|
+
:ivar tth_calibrated: Calibrated value for 2&theta.
|
|
248
|
+
:type tth_calibrated: float, optional
|
|
249
|
+
:ivar tth_initial_guess: Initial guess for 2&theta superseding
|
|
250
|
+
the global one in MCATthCalibrationConfig.
|
|
251
|
+
:type tth_initial_guess: float, optional
|
|
252
|
+
"""
|
|
253
|
+
processor_type: Literal['calibration']
|
|
254
|
+
energy_calibration_coeffs: Optional[conlist(
|
|
255
|
+
min_length=3, max_length=3,
|
|
256
|
+
item_type=confloat(allow_inf_nan=False))] = None
|
|
257
|
+
num_bins: Optional[conint(gt=0)] = None
|
|
258
|
+
tth_max: Optional[confloat(gt=0, allow_inf_nan=False)] = None
|
|
259
|
+
tth_tol: Optional[confloat(gt=0, allow_inf_nan=False)] = None
|
|
260
|
+
tth_calibrated: Optional[confloat(gt=0, allow_inf_nan=False)] = None
|
|
261
|
+
tth_initial_guess: Optional[confloat(gt=0, allow_inf_nan=False)] = None
|
|
262
|
+
|
|
263
|
+
_energy_calibration_mask_ranges: conlist(
|
|
264
|
+
min_length=1,
|
|
265
|
+
item_type=conlist(
|
|
266
|
+
min_length=2,
|
|
267
|
+
max_length=2,
|
|
268
|
+
item_type=conint(ge=0))) = PrivateAttr()
|
|
269
|
+
_hkl_indices: list = PrivateAttr()
|
|
270
|
+
|
|
271
|
+
# def add_calibration(self, calibration):
|
|
272
|
+
# """Finalize values for some fields using a calibration
|
|
273
|
+
# MCADetectorCalibration corresponding to the same detector.
|
|
274
|
+
#
|
|
275
|
+
# :param calibration: Existing calibration configuration.
|
|
276
|
+
# :type calibration: MCADetectorCalibration
|
|
277
|
+
# """
|
|
278
|
+
# raise RuntimeError('To do')
|
|
279
|
+
# for field in ['energy_calibration_coeffs', 'num_bins',
|
|
280
|
+
# '_energy_calibration_mask_ranges']:
|
|
281
|
+
# setattr(self, field, deepcopy(getattr(calibration, field)))
|
|
282
|
+
# if self.tth_calibrated is not None:
|
|
283
|
+
# self.logger.warning(
|
|
284
|
+
# 'Ignoring tth_calibrated in calibration configuration')
|
|
285
|
+
# self.tth_calibrated = None
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def energies(self):
|
|
289
|
+
"""Return calibrated bin energies."""
|
|
290
|
+
a, b, c = tuple(self.energy_calibration_coeffs)
|
|
291
|
+
channel_bins = np.arange(self.num_bins)
|
|
292
|
+
return (a*channel_bins + b)*channel_bins + c
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def hkl_indices(self):
|
|
296
|
+
"""Return the hkl_indices consistent with the selected energy
|
|
297
|
+
ranges (include_energy_ranges).
|
|
298
|
+
"""
|
|
299
|
+
if hasattr(self, '_hkl_indices'):
|
|
300
|
+
return self._hkl_indices
|
|
301
|
+
return []
|
|
302
|
+
|
|
303
|
+
@hkl_indices.setter
|
|
304
|
+
def hkl_indices(self, value):
|
|
305
|
+
"""Set the private attribute `hkl_indices`."""
|
|
306
|
+
self._hkl_indices = value
|
|
307
|
+
|
|
308
|
+
def convert_mask_ranges(self, mask_ranges):
|
|
309
|
+
"""Given a list of mask ranges in channel bins, set the
|
|
310
|
+
corresponding list of channel energy mask ranges.
|
|
311
|
+
|
|
312
|
+
:param mask_ranges: A list of mask ranges to
|
|
313
|
+
convert to energy mask ranges.
|
|
314
|
+
:type mask_ranges: list[[int,int]]
|
|
315
|
+
"""
|
|
316
|
+
energies = self.energies
|
|
317
|
+
self.energy_mask_ranges = [
|
|
318
|
+
[float(energies[i]) for i in range_]
|
|
319
|
+
for range_ in sorted([sorted(v) for v in mask_ranges])]
|
|
320
|
+
|
|
321
|
+
def get_mask_ranges(self):
|
|
322
|
+
"""Return the value of `mask_ranges` if set or convert the
|
|
323
|
+
`energy_mask_ranges` from channel energies to channel indices.
|
|
324
|
+
"""
|
|
325
|
+
if self.mask_ranges:
|
|
326
|
+
return self.mask_ranges
|
|
327
|
+
if self.energy_mask_ranges is None:
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
# Local modules
|
|
331
|
+
from CHAP.utils.general import (
|
|
332
|
+
index_nearest_down,
|
|
333
|
+
index_nearest_up,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
mask_ranges = []
|
|
337
|
+
energies = self.energies
|
|
338
|
+
for e_min, e_max in self.energy_mask_ranges:
|
|
339
|
+
mask_ranges.append(
|
|
340
|
+
[index_nearest_down(energies, e_min),
|
|
341
|
+
index_nearest_up(energies, e_max)])
|
|
342
|
+
return mask_ranges
|
|
343
|
+
|
|
344
|
+
def mca_mask(self):
|
|
345
|
+
"""Get a boolean mask array to use on this MCA element's data.
|
|
346
|
+
Note that the bounds of the mask ranges are inclusive.
|
|
347
|
+
|
|
348
|
+
:return: Boolean mask array.
|
|
349
|
+
:rtype: numpy.ndarray
|
|
350
|
+
"""
|
|
351
|
+
mask = np.asarray([False] * self.num_bins)
|
|
352
|
+
mask_ranges = self.get_mask_ranges()
|
|
353
|
+
channel_bins = np.arange(self.num_bins, dtype=np.int32)
|
|
354
|
+
for (min_, max_) in mask_ranges:
|
|
355
|
+
mask = np.logical_or(
|
|
356
|
+
mask,
|
|
357
|
+
np.logical_and(channel_bins >= min_, channel_bins <= max_))
|
|
358
|
+
return mask
|
|
359
|
+
|
|
360
|
+
def set_energy_calibration_mask_ranges(self):
|
|
361
|
+
self._energy_calibration_mask_ranges = deepcopy(self.mask_ranges)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class MCADetectorDiffractionVolumeLength(MCADetectorCalibration):
|
|
365
|
+
"""Class representing metadata required to perform a diffraction
|
|
366
|
+
volume length measurement for a single MCA detector element.
|
|
367
|
+
|
|
368
|
+
:ivar dvl: Measured diffraction volume length.
|
|
369
|
+
:type dvl: float, optional
|
|
370
|
+
:ivar fit_amplitude: Amplitude of the Gaussian fit.
|
|
371
|
+
:type fit_amplitude: float, optional
|
|
372
|
+
:ivar fit_center: Center of the Gaussian fit.
|
|
373
|
+
:type fit_center: float, optional
|
|
374
|
+
:ivar fit_sigma: Sigma of the Gaussian fit.
|
|
375
|
+
:type fit_sigma: float, optional
|
|
376
|
+
"""
|
|
377
|
+
processor_type: Literal['diffractionvolumelength']
|
|
378
|
+
dvl: Optional[confloat(gt=0, allow_inf_nan=False)] = None
|
|
379
|
+
fit_amplitude: Optional[float] = None
|
|
380
|
+
fit_center: Optional[float] = None
|
|
381
|
+
fit_sigma: Optional[float] = None
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class MCADetectorStrainAnalysis(MCADetectorCalibration):
|
|
385
|
+
"""Class representing metadata required to perform a strain
|
|
386
|
+
analysis.
|
|
387
|
+
|
|
388
|
+
:ivar centers_range: Peak centers range for peak fitting.
|
|
389
|
+
The allowed range for the peak centers will be the initial
|
|
390
|
+
values ± `centers_range` (in keV), defaults to `2.0`.
|
|
391
|
+
:type centers_range: float, optional
|
|
392
|
+
:ivar fwhm_min: Minimum FWHM for peak fitting (in keV),
|
|
393
|
+
defaults to `0.25`.
|
|
394
|
+
:type fwhm_min: float, optional
|
|
395
|
+
:ivar fwhm_max: Maximum FWHM for peak fitting (in keV),
|
|
396
|
+
defaults to `2.0`.
|
|
397
|
+
:ivar peak_models: Peak model for peak fitting,
|
|
398
|
+
defaults to `'gaussian'`.
|
|
399
|
+
:type peak_models: Literal['gaussian', 'lorentzian']],
|
|
400
|
+
list[Literal['gaussian', 'lorentzian']]], optional
|
|
401
|
+
:ivar rel_height_cutoff: Relative peak height cutoff for
|
|
402
|
+
peak fitting (any peak with a height smaller than
|
|
403
|
+
`rel_height_cutoff` times the maximum height of all peaks
|
|
404
|
+
gets removed from the fit model), defaults to `None`.
|
|
405
|
+
:type rel_height_cutoff: float, optional
|
|
406
|
+
:ivar tth_file: Path to the file with the 2&theta map.
|
|
407
|
+
:type tth_file: FilePath, optional
|
|
408
|
+
:ivar tth_map: Map of the 2&theta values.
|
|
409
|
+
:type tth_map: numpy.ndarray, optional
|
|
410
|
+
"""
|
|
411
|
+
centers_range: Optional[confloat(gt=0, allow_inf_nan=False)] = 2
|
|
412
|
+
fwhm_min: Optional[confloat(gt=0, allow_inf_nan=False)] = 0.25
|
|
413
|
+
fwhm_max: Optional[confloat(gt=0, allow_inf_nan=False)] = 2.0
|
|
414
|
+
processor_type: Literal['strainanalysis']
|
|
415
|
+
peak_models: Union[
|
|
416
|
+
conlist(min_length=1, item_type=Literal['gaussian', 'lorentzian']),
|
|
417
|
+
Literal['gaussian', 'lorentzian']] = 'gaussian'
|
|
418
|
+
rel_height_cutoff: Optional[
|
|
419
|
+
confloat(gt=0, lt=1.0, allow_inf_nan=False)] = None
|
|
420
|
+
# tth_file: Optional[FilePath] = None
|
|
421
|
+
# tth_map: Optional[np.ndarray] = None
|
|
422
|
+
|
|
423
|
+
_calibration_energy_mask_ranges: conlist(
|
|
424
|
+
min_length=1,
|
|
425
|
+
item_type=conlist(
|
|
426
|
+
min_length=2,
|
|
427
|
+
max_length=2,
|
|
428
|
+
item_type=confloat(allow_inf_nan=False))) = PrivateAttr()
|
|
429
|
+
|
|
430
|
+
def add_calibration(self, calibration):
|
|
431
|
+
"""Finalize values for some fields using a tth calibration
|
|
432
|
+
MCADetectorStrainAnalysis corresponding to the same detector.
|
|
433
|
+
|
|
434
|
+
:param calibration: Existing calibration configuration to use
|
|
435
|
+
by MCAElementStrainAnalysisConfig.
|
|
436
|
+
:type calibration: MCADetectorStrainAnalysis
|
|
437
|
+
"""
|
|
438
|
+
for field in ['energy_calibration_coeffs', 'num_bins',
|
|
439
|
+
'tth_calibrated']:
|
|
440
|
+
setattr(self, field, deepcopy(getattr(calibration, field)))
|
|
441
|
+
if self.energy_mask_ranges is None:
|
|
442
|
+
self.energy_mask_ranges = deepcopy(calibration.energy_mask_ranges)
|
|
443
|
+
self._calibration_energy_mask_ranges = deepcopy(
|
|
444
|
+
calibration.energy_mask_ranges)
|
|
445
|
+
|
|
446
|
+
def get_calibration_mask_ranges(self):
|
|
447
|
+
"""Return the `_calibration_energy_mask_ranges` converted from
|
|
448
|
+
channel energies to channel indices.
|
|
449
|
+
"""
|
|
450
|
+
if not hasattr(self, '_calibration_energy_mask_ranges'):
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
# Local modules
|
|
454
|
+
from CHAP.utils.general import (
|
|
455
|
+
index_nearest_down,
|
|
456
|
+
index_nearest_up,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
energy_mask_ranges = []
|
|
460
|
+
energies = self.energies
|
|
461
|
+
for e_min, e_max in self._calibration_energy_mask_ranges:
|
|
462
|
+
energy_mask_ranges.append(
|
|
463
|
+
[index_nearest_down(energies, e_min),
|
|
464
|
+
index_nearest_up(energies, e_max)])
|
|
465
|
+
return energy_mask_ranges
|
|
466
|
+
|
|
467
|
+
def get_tth_map(self, map_shape):
|
|
468
|
+
"""Return the map of 2&theta values to use -- may vary at each
|
|
469
|
+
point in the map.
|
|
470
|
+
|
|
471
|
+
:param map_shape: The shape of the suplied 2&theta map.
|
|
472
|
+
:return: Map of 2&theta values.
|
|
473
|
+
:rtype: numpy.ndarray
|
|
474
|
+
"""
|
|
475
|
+
if getattr(self, 'tth_map', None) is not None:
|
|
476
|
+
if self.tth_map.shape != map_shape:
|
|
477
|
+
raise ValueError(
|
|
478
|
+
'Invalid "tth_map" field shape '
|
|
479
|
+
f'{self.tth_map.shape} (expected {map_shape})')
|
|
480
|
+
return self.tth_map
|
|
481
|
+
return np.full(map_shape, self.tth_calibrated)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
MCADetector = Annotated[
|
|
485
|
+
Union[
|
|
486
|
+
MCADetectorCalibration,
|
|
487
|
+
MCADetectorDiffractionVolumeLength,
|
|
488
|
+
MCADetectorStrainAnalysis],
|
|
489
|
+
Field(discriminator='processor_type')
|
|
490
|
+
]
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
class MCADetectorConfig(FitConfig):
|
|
494
|
+
"""Class representing metadata required to configure a full MCA
|
|
495
|
+
detector.
|
|
496
|
+
|
|
497
|
+
:ivar detectors: List of individual MCA detector elements.
|
|
498
|
+
:type detectors: list[MCADetector], optional
|
|
499
|
+
"""
|
|
500
|
+
processor_type: Literal[
|
|
501
|
+
'calibration', 'diffractionvolumelength', 'strainanalysis']
|
|
502
|
+
detectors: Optional[conlist(min_length=1, item_type=MCADetector)] = []
|
|
503
|
+
|
|
504
|
+
_exclude = set(vars(FitConfig()).keys())
|
|
505
|
+
|
|
506
|
+
@model_validator(mode='before')
|
|
507
|
+
@classmethod
|
|
508
|
+
def validate_mcadetectorconfig_before(cls, data):
|
|
509
|
+
if isinstance(data, dict):
|
|
510
|
+
processor_type = data.get('processor_type').lower()
|
|
511
|
+
if 'detectors' in data:
|
|
512
|
+
detectors = data.pop('detectors')
|
|
513
|
+
for d in detectors:
|
|
514
|
+
d['processor_type'] = processor_type
|
|
515
|
+
attrs = d.pop('attrs', {})
|
|
516
|
+
if 'default_fields' in attrs:
|
|
517
|
+
attrs.pop('default_fields')
|
|
518
|
+
if attrs:
|
|
519
|
+
d['attrs'] = attrs
|
|
520
|
+
data['detectors'] = detectors
|
|
521
|
+
return data
|
|
522
|
+
|
|
523
|
+
@model_validator(mode='after')
|
|
524
|
+
def validate_mcadetectorconfig_after(self):
|
|
525
|
+
if self.detectors:
|
|
526
|
+
self.update_detectors()
|
|
527
|
+
return self
|
|
528
|
+
|
|
529
|
+
def update_detectors(self):
|
|
530
|
+
"""Update individual detector parameters with any non-default
|
|
531
|
+
values from the global detector configuration.
|
|
532
|
+
"""
|
|
533
|
+
for k, v in self:
|
|
534
|
+
if k in self.model_fields_set:
|
|
535
|
+
for d in self.detectors:
|
|
536
|
+
if hasattr(d, k):
|
|
537
|
+
setattr(d, k, deepcopy(v))
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# Processor configuration classes
|
|
541
|
+
|
|
542
|
+
class DiffractionVolumeLengthConfig(FitConfig):
|
|
543
|
+
"""Class representing metadata required to perform a diffraction
|
|
544
|
+
volume length calculation for an EDD setup using a steel-foil
|
|
545
|
+
raster scan.
|
|
546
|
+
|
|
547
|
+
:ivar max_energy_kev: Maximum channel energy of the MCA in
|
|
548
|
+
keV, defaults to `200.0`.
|
|
549
|
+
:type max_energy_kev: float, optional
|
|
550
|
+
:ivar measurement_mode: Placeholder for recording whether the
|
|
551
|
+
measured DVL value was obtained through the automated
|
|
552
|
+
calculation or a manual selection, defaults to `'auto'`.
|
|
553
|
+
:type measurement_mode: Literal['manual', 'auto'], optional
|
|
554
|
+
:ivar sample_thickness: Thickness of scanned foil sample. Quantity
|
|
555
|
+
must be provided in the same units as the values of the
|
|
556
|
+
scanning motor.
|
|
557
|
+
:type sample_thickness: float
|
|
558
|
+
:ivar sigma_to_dvl_factor: The DVL is obtained by fitting a reduced
|
|
559
|
+
form of the MCA detector data. `sigma_to_dvl_factor` is a
|
|
560
|
+
scalar value that converts the standard deviation of the
|
|
561
|
+
gaussian fit to the measured DVL, defaults to `3.5`.
|
|
562
|
+
:type sigma_to_dvl_factor: Literal[2.0, 3.5, 4.0], optional
|
|
563
|
+
"""
|
|
564
|
+
max_energy_kev: Optional[confloat(gt=0, allow_inf_nan=False)] = 200.0
|
|
565
|
+
measurement_mode: Optional[Literal['manual', 'auto']] = 'auto'
|
|
566
|
+
sample_thickness: Optional[confloat(gt=0, allow_inf_nan=False)] = None
|
|
567
|
+
sigma_to_dvl_factor: Optional[Literal[2.0, 3.5, 4.0]] = 3.5
|
|
568
|
+
|
|
569
|
+
_exclude = set(vars(FitConfig()).keys())
|
|
570
|
+
|
|
571
|
+
@model_validator(mode='after')
|
|
572
|
+
def validate_diffractionvolumelengthconfig_after(self):
|
|
573
|
+
"""Update the configuration with costum defaults after the
|
|
574
|
+
normal native pydantic validation.
|
|
575
|
+
|
|
576
|
+
:return: Updated energy calibration configuration class.
|
|
577
|
+
:rtype: DiffractionVolumeLengthConfig
|
|
578
|
+
"""
|
|
579
|
+
if self.measurement_mode == 'manual':
|
|
580
|
+
self._exclude |= {'sigma_to_dvl_factor'}
|
|
581
|
+
return self
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class MCACalibrationConfig(CHAPBaseModel):
|
|
585
|
+
"""Base class representing metadata required to perform an energy
|
|
586
|
+
or 2&theta calibration of an MCA detector.
|
|
587
|
+
|
|
588
|
+
:ivar flux_file: File name of the csv flux file containing station
|
|
589
|
+
beam energy in eV (column 0) versus flux (column 1).
|
|
590
|
+
:type flux_file: str, optional
|
|
591
|
+
:ivar materials: Material configurations for the calibration,
|
|
592
|
+
defaults to [`Ceria`].
|
|
593
|
+
:type materials: list[MaterialConfig], optional
|
|
594
|
+
:ivar peak_energies: Theoretical locations of the fluorescence
|
|
595
|
+
peaks in keV to use for calibrating the MCA channel energies.
|
|
596
|
+
:type peak_energies: list[float], optional for energy calibration
|
|
597
|
+
:ivar scan_step_indices: Optional scan step indices to use for the
|
|
598
|
+
calibration. If not specified, the calibration will be
|
|
599
|
+
performed on the average of all MCA spectra for the scan.
|
|
600
|
+
:type scan_step_indices: int, str, list[int], optional
|
|
601
|
+
|
|
602
|
+
Note: Fluorescence data:
|
|
603
|
+
https://physics.nist.gov/PhysRefData/XrayTrans/Html/search.html
|
|
604
|
+
"""
|
|
605
|
+
flux_file: Optional[FilePath] = None
|
|
606
|
+
materials: Optional[conlist(item_type=MaterialConfig)] = [MaterialConfig(
|
|
607
|
+
material_name='CeO2', lattice_parameters=5.41153, sgnum=225)]
|
|
608
|
+
peak_energies: Optional[conlist(
|
|
609
|
+
min_length=2, item_type=confloat(gt=0, allow_inf_nan=False))] = [
|
|
610
|
+
34.279, 34.720, 39.258, 40.233]
|
|
611
|
+
scan_step_indices: Optional[Annotated[conlist(
|
|
612
|
+
min_length=1, item_type=conint(ge=0)),
|
|
613
|
+
Field(validate_default=True)]] = None
|
|
614
|
+
|
|
615
|
+
@model_validator(mode='before')
|
|
616
|
+
@classmethod
|
|
617
|
+
def validate_mcacalibrationconfig_before(cls, data):
|
|
618
|
+
"""Ensure that a valid configuration was provided and finalize
|
|
619
|
+
flux_file filepath.
|
|
620
|
+
|
|
621
|
+
:param data: Pydantic validator data object.
|
|
622
|
+
:type data: MCACalibrationConfig,
|
|
623
|
+
pydantic_core._pydantic_core.ValidationInfo
|
|
624
|
+
:return: The currently validated list of class properties.
|
|
625
|
+
:rtype: dict
|
|
626
|
+
"""
|
|
627
|
+
if isinstance(data, dict):
|
|
628
|
+
inputdir = data.get('inputdir')
|
|
629
|
+
if inputdir is not None:
|
|
630
|
+
flux_file = data.get('flux_file')
|
|
631
|
+
if flux_file is not None and not os.path.isabs(flux_file):
|
|
632
|
+
data['flux_file'] = os.path.join(inputdir, flux_file)
|
|
633
|
+
return data
|
|
634
|
+
|
|
635
|
+
@field_validator('scan_step_indices', mode='before')
|
|
636
|
+
@classmethod
|
|
637
|
+
def validate_scan_step_indices(cls, scan_step_indices):
|
|
638
|
+
"""Validate the specified list of scan numbers.
|
|
639
|
+
|
|
640
|
+
:ivar scan_step_indices: Optional scan step indices to use for
|
|
641
|
+
the calibration. If not specified, the calibration will be
|
|
642
|
+
performed on the average of all MCA spectra for the scan.
|
|
643
|
+
:type scan_step_indices: int, str, list[int], optional
|
|
644
|
+
:raises ValueError: Invalid experiment type.
|
|
645
|
+
:return: List of step indices.
|
|
646
|
+
:rtype: list[int]
|
|
647
|
+
"""
|
|
648
|
+
if isinstance(scan_step_indices, int):
|
|
649
|
+
scan_step_indices = [scan_step_indices]
|
|
650
|
+
elif isinstance(scan_step_indices, str):
|
|
651
|
+
# Local modules
|
|
652
|
+
from CHAP.utils.general import string_to_list
|
|
653
|
+
|
|
654
|
+
scan_step_indices = string_to_list(scan_step_indices)
|
|
655
|
+
return scan_step_indices
|
|
656
|
+
|
|
657
|
+
def flux_file_energy_range(self):
|
|
658
|
+
"""Get the energy range in the flux correction file.
|
|
659
|
+
|
|
660
|
+
:return: The energy range in the flux correction file.
|
|
661
|
+
:rtype: tuple(float, float)
|
|
662
|
+
"""
|
|
663
|
+
if self.flux_file is None:
|
|
664
|
+
return None
|
|
665
|
+
flux = np.loadtxt(self.flux_file)
|
|
666
|
+
energies = flux[:,0]/1.e3
|
|
667
|
+
return energies.min(), energies.max()
|
|
668
|
+
|
|
669
|
+
def flux_correction_interpolation_function(self):
|
|
670
|
+
"""Get an interpolation function to correct MCA data for the
|
|
671
|
+
relative energy flux of the incident beam.
|
|
672
|
+
|
|
673
|
+
:return: Energy flux correction interpolation function.
|
|
674
|
+
:rtype: scipy.interpolate._polyint._Interpolator1D
|
|
675
|
+
"""
|
|
676
|
+
if self.flux_file is None:
|
|
677
|
+
return None
|
|
678
|
+
flux = np.loadtxt(self.flux_file)
|
|
679
|
+
energies = flux[:,0]/1.e3
|
|
680
|
+
relative_intensities = flux[:,1]/np.max(flux[:,1])
|
|
681
|
+
interpolation_function = interp1d(energies, relative_intensities)
|
|
682
|
+
return interpolation_function
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
class MCAEnergyCalibrationConfig(MCACalibrationConfig):
|
|
686
|
+
"""Base class representing metadata required to perform an energy
|
|
687
|
+
calibration of an MCA detector.
|
|
688
|
+
|
|
689
|
+
:ivar max_energy_kev: Maximum channel energy of the MCA in
|
|
690
|
+
keV, defaults to `200.0`.
|
|
691
|
+
:type max_energy_kev: float, optional
|
|
692
|
+
:ivar max_peak_index: Index of the peak in `peak_energies`
|
|
693
|
+
with the highest amplitude, defaults to `1` (the second peak)
|
|
694
|
+
for CeO2 calibration. Required for any other materials.
|
|
695
|
+
:type max_peak_index: int, optional
|
|
696
|
+
"""
|
|
697
|
+
max_energy_kev: Optional[confloat(gt=0, allow_inf_nan=False)] = 200.0
|
|
698
|
+
max_peak_index: Optional[
|
|
699
|
+
Annotated[conint(ge=0), Field(validate_default=True)]] = None
|
|
700
|
+
|
|
701
|
+
@model_validator(mode='before')
|
|
702
|
+
@classmethod
|
|
703
|
+
def validate_mcaenergycalibrationconfig_before(cls, data):
|
|
704
|
+
if isinstance(data, dict):
|
|
705
|
+
detectors = data.pop('detectors', None)
|
|
706
|
+
if detectors is not None:
|
|
707
|
+
data['detector_config'] = {'detectors': detectors}
|
|
708
|
+
return data
|
|
709
|
+
|
|
710
|
+
@model_validator(mode='after')
|
|
711
|
+
def validate_mcaenergycalibrationconfig_after(self):
|
|
712
|
+
"""Validate the detector (energy) mask ranges and update any
|
|
713
|
+
detector configuration parameters not superseded by their
|
|
714
|
+
individual values.
|
|
715
|
+
|
|
716
|
+
:return: Updated energy calibration configuration class.
|
|
717
|
+
:rtype: MCAEnergyCalibrationConfig
|
|
718
|
+
"""
|
|
719
|
+
if self.peak_energies is None:
|
|
720
|
+
raise ValueError('peak_energies is required')
|
|
721
|
+
if not 0 <= self.max_peak_index < len(self.peak_energies):
|
|
722
|
+
raise ValueError('max_peak_index out of bounds')
|
|
723
|
+
return self
|
|
724
|
+
|
|
725
|
+
@field_validator('max_peak_index', mode='before')
|
|
726
|
+
@classmethod
|
|
727
|
+
def validate_max_peak_index(cls, max_peak_index, info):
|
|
728
|
+
if max_peak_index is None:
|
|
729
|
+
materials = info.data.get('materials', [])
|
|
730
|
+
if len(materials) != 1 or materials[0].material_name != 'CeO2':
|
|
731
|
+
raise ValueError('max_peak_index is required unless the '
|
|
732
|
+
'calibration material is CeO2')
|
|
733
|
+
max_peak_index = 1
|
|
734
|
+
return max_peak_index
|
|
735
|
+
|
|
736
|
+
class MCATthCalibrationConfig(MCACalibrationConfig):
|
|
737
|
+
"""Class representing metadata required to perform a 2&theta
|
|
738
|
+
calibration of an MCA detector.
|
|
739
|
+
|
|
740
|
+
:ivar calibration_method: Type of calibration method,
|
|
741
|
+
defaults to `'direct_fit_bragg'`.
|
|
742
|
+
:type calibration_method:
|
|
743
|
+
Literal['direct_fit_bragg', 'direct_fit_tth_ecc'], optional
|
|
744
|
+
:ivar detectors: List of individual MCA detector element
|
|
745
|
+
calibration configurations.
|
|
746
|
+
:ivar quadratic_energy_calibration: Adds a quadratic term to
|
|
747
|
+
the detector channel index to energy conversion, defaults
|
|
748
|
+
to `False` (linear only).
|
|
749
|
+
:type quadratic_energy_calibration: bool, optional
|
|
750
|
+
:ivar tth_initial_guess: Initial guess for 2&theta.
|
|
751
|
+
:type tth_initial_guess: float, optional
|
|
752
|
+
"""
|
|
753
|
+
calibration_method: Optional[Literal[
|
|
754
|
+
'direct_fit_bragg', 'direct_fit_tth_ecc']] = 'direct_fit_bragg'
|
|
755
|
+
quadratic_energy_calibration: Optional[bool] = False
|
|
756
|
+
tth_initial_guess: Optional[
|
|
757
|
+
confloat(gt=0, allow_inf_nan=False)] = Field(None, exclude=True)
|
|
758
|
+
|
|
759
|
+
def flux_file_energy_range(self):
|
|
760
|
+
"""Get the energy range in the flux corection file.
|
|
761
|
+
|
|
762
|
+
:return: The energy range in the flux corection file.
|
|
763
|
+
:rtype: tuple(float, float)
|
|
764
|
+
"""
|
|
765
|
+
if self.flux_file is None:
|
|
766
|
+
return None
|
|
767
|
+
flux = np.loadtxt(self.flux_file)
|
|
768
|
+
energies = flux[:,0]/1.e3
|
|
769
|
+
return energies.min(), energies.max()
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
class StrainAnalysisConfig(MCACalibrationConfig):
|
|
773
|
+
"""Class representing input parameters required to perform a
|
|
774
|
+
strain analysis.
|
|
775
|
+
|
|
776
|
+
:ivar find_peaks: Exclude peaks where the average spectrum is
|
|
777
|
+
below the `rel_height_cutoff` cutoff relative to the
|
|
778
|
+
maximum value of the average spectrum, defaults to `True`.
|
|
779
|
+
:type find_peaks: bool, optional
|
|
780
|
+
:ivar oversampling: FIX
|
|
781
|
+
:type oversampling: FIX
|
|
782
|
+
:ivar rel_height_cutoff: Used to excluded peaks based on the
|
|
783
|
+
`find_peak` parameter as well as for peak fitting exclusion
|
|
784
|
+
of the individual detector spectra (see the strain detector
|
|
785
|
+
configuration `CHAP.edd.models.MCADetectorStrainAnalysis).
|
|
786
|
+
Defaults to `None`.
|
|
787
|
+
:type rel_height_cutoff: float, optional
|
|
788
|
+
:ivar skip_animation: Skip the animation and plotting of
|
|
789
|
+
the strain analysis fits, defaults to `False`.
|
|
790
|
+
:type skip_animation: bool, optional
|
|
791
|
+
:ivar sum_axes: Whether to sum over the fly axis or not
|
|
792
|
+
for EDD scan types not 0, defaults to `True`.
|
|
793
|
+
:type sum_axes: Union[bool, list[str]], optional
|
|
794
|
+
"""
|
|
795
|
+
find_peaks: Optional[bool] = True
|
|
796
|
+
num_proc: Optional[conint(gt=0)] = max(1, os.cpu_count()//4)
|
|
797
|
+
oversampling: Optional[
|
|
798
|
+
Annotated[Dict, Field(validate_default=True)]] = {'num': 10}
|
|
799
|
+
rel_height_cutoff: Optional[
|
|
800
|
+
confloat(gt=0, lt=1.0, allow_inf_nan=False)] = None
|
|
801
|
+
skip_animation: Optional[bool] = False
|
|
802
|
+
sum_axes: Optional[
|
|
803
|
+
Union[bool, conlist(min_length=1, item_type=str)]] = True
|
|
804
|
+
|
|
805
|
+
# FIX tth_file/tth_map not updated
|
|
806
|
+
# @field_validator('detectors')
|
|
807
|
+
# @classmethod
|
|
808
|
+
# def validate_detectors(cls, detectors, info):
|
|
809
|
+
# """Validate detector element tth_file field. It may only be
|
|
810
|
+
# used if StrainAnalysisConfig used par_file.
|
|
811
|
+
# """
|
|
812
|
+
# for detector in detectors:
|
|
813
|
+
# tth_file = detector.tth_file
|
|
814
|
+
# if tth_file is not None:
|
|
815
|
+
# if not info.data.get('par_file'):
|
|
816
|
+
# raise ValueError(
|
|
817
|
+
# 'variable tth angles may only be used with a '
|
|
818
|
+
# 'StrainAnalysisConfig that uses par_file.')
|
|
819
|
+
# else:
|
|
820
|
+
# try:
|
|
821
|
+
# detector.tth_map = ParFile(
|
|
822
|
+
# info.data['par_file']).map_values(
|
|
823
|
+
# info.data['map_config'],
|
|
824
|
+
# np.loadtxt(tth_file))
|
|
825
|
+
# except Exception as e:
|
|
826
|
+
# raise ValueError(
|
|
827
|
+
# 'Could not get map of tth angles from '
|
|
828
|
+
# f'{tth_file}') from e
|
|
829
|
+
# return detectors
|
|
830
|
+
|
|
831
|
+
@field_validator('oversampling')
|
|
832
|
+
@classmethod
|
|
833
|
+
def validate_oversampling(cls, oversampling, info):
|
|
834
|
+
"""Validate the oversampling field.
|
|
835
|
+
|
|
836
|
+
:param oversampling: The value of `oversampling` to validate.
|
|
837
|
+
:type oversampling: dict
|
|
838
|
+
:param info: Pydantic validator info object.
|
|
839
|
+
:type info: StrainAnalysisConfig,
|
|
840
|
+
pydantic_core._pydantic_core.ValidationInfo
|
|
841
|
+
:return: The validated value for oversampling.
|
|
842
|
+
:rtype: bool
|
|
843
|
+
"""
|
|
844
|
+
# Local modules
|
|
845
|
+
from CHAP.utils.general import is_int
|
|
846
|
+
|
|
847
|
+
raise ValueError('oversampling not updated yet')
|
|
848
|
+
map_config = info.data.get('map_config')
|
|
849
|
+
if map_config is None or map_config.attrs['scan_type'] < 3:
|
|
850
|
+
return None
|
|
851
|
+
if oversampling is None:
|
|
852
|
+
return {'num': 10}
|
|
853
|
+
if 'start' in oversampling and not is_int(oversampling['start'], ge=0):
|
|
854
|
+
raise ValueError('Invalid "start" parameter in "oversampling" '
|
|
855
|
+
f'field ({oversampling["start"]})')
|
|
856
|
+
if 'end' in oversampling and not is_int(oversampling['end'], gt=0):
|
|
857
|
+
raise ValueError('Invalid "end" parameter in "oversampling" '
|
|
858
|
+
f'field ({oversampling["end"]})')
|
|
859
|
+
if 'width' in oversampling and not is_int(oversampling['width'], gt=0):
|
|
860
|
+
raise ValueError('Invalid "width" parameter in "oversampling" '
|
|
861
|
+
f'field ({oversampling["width"]})')
|
|
862
|
+
if ('stride' in oversampling
|
|
863
|
+
and not is_int(oversampling['stride'], gt=0)):
|
|
864
|
+
raise ValueError('Invalid "stride" parameter in "oversampling" '
|
|
865
|
+
f'field ({oversampling["stride"]})')
|
|
866
|
+
if 'num' in oversampling and not is_int(oversampling['num'], gt=0):
|
|
867
|
+
raise ValueError('Invalid "num" parameter in "oversampling" '
|
|
868
|
+
f'field ({oversampling["num"]})')
|
|
869
|
+
if 'mode' in oversampling and 'mode' not in ('valid', 'full'):
|
|
870
|
+
raise ValueError('Invalid "mode" parameter in "oversampling" '
|
|
871
|
+
f'field ({oversampling["mode"]})')
|
|
872
|
+
if not ('width' in oversampling or 'stride' in oversampling
|
|
873
|
+
or 'num' in oversampling):
|
|
874
|
+
raise ValueError('Invalid input parameters, specify at least one '
|
|
875
|
+
'of "width", "stride" or "num"')
|
|
876
|
+
return oversampling
|