ChessAnalysisPipeline 0.0.17.dev3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. CHAP/TaskManager.py +216 -0
  2. CHAP/__init__.py +27 -0
  3. CHAP/common/__init__.py +57 -0
  4. CHAP/common/models/__init__.py +8 -0
  5. CHAP/common/models/common.py +124 -0
  6. CHAP/common/models/integration.py +659 -0
  7. CHAP/common/models/map.py +1291 -0
  8. CHAP/common/processor.py +2869 -0
  9. CHAP/common/reader.py +658 -0
  10. CHAP/common/utils.py +110 -0
  11. CHAP/common/writer.py +730 -0
  12. CHAP/edd/__init__.py +23 -0
  13. CHAP/edd/models.py +876 -0
  14. CHAP/edd/processor.py +3069 -0
  15. CHAP/edd/reader.py +1023 -0
  16. CHAP/edd/select_material_params_gui.py +348 -0
  17. CHAP/edd/utils.py +1572 -0
  18. CHAP/edd/writer.py +26 -0
  19. CHAP/foxden/__init__.py +19 -0
  20. CHAP/foxden/models.py +71 -0
  21. CHAP/foxden/processor.py +124 -0
  22. CHAP/foxden/reader.py +224 -0
  23. CHAP/foxden/utils.py +80 -0
  24. CHAP/foxden/writer.py +168 -0
  25. CHAP/giwaxs/__init__.py +11 -0
  26. CHAP/giwaxs/models.py +491 -0
  27. CHAP/giwaxs/processor.py +776 -0
  28. CHAP/giwaxs/reader.py +8 -0
  29. CHAP/giwaxs/writer.py +8 -0
  30. CHAP/inference/__init__.py +7 -0
  31. CHAP/inference/processor.py +69 -0
  32. CHAP/inference/reader.py +8 -0
  33. CHAP/inference/writer.py +8 -0
  34. CHAP/models.py +227 -0
  35. CHAP/pipeline.py +479 -0
  36. CHAP/processor.py +125 -0
  37. CHAP/reader.py +124 -0
  38. CHAP/runner.py +277 -0
  39. CHAP/saxswaxs/__init__.py +7 -0
  40. CHAP/saxswaxs/processor.py +8 -0
  41. CHAP/saxswaxs/reader.py +8 -0
  42. CHAP/saxswaxs/writer.py +8 -0
  43. CHAP/server.py +125 -0
  44. CHAP/sin2psi/__init__.py +7 -0
  45. CHAP/sin2psi/processor.py +8 -0
  46. CHAP/sin2psi/reader.py +8 -0
  47. CHAP/sin2psi/writer.py +8 -0
  48. CHAP/tomo/__init__.py +15 -0
  49. CHAP/tomo/models.py +210 -0
  50. CHAP/tomo/processor.py +3862 -0
  51. CHAP/tomo/reader.py +9 -0
  52. CHAP/tomo/writer.py +59 -0
  53. CHAP/utils/__init__.py +6 -0
  54. CHAP/utils/converters.py +188 -0
  55. CHAP/utils/fit.py +2947 -0
  56. CHAP/utils/general.py +2655 -0
  57. CHAP/utils/material.py +274 -0
  58. CHAP/utils/models.py +595 -0
  59. CHAP/utils/parfile.py +224 -0
  60. CHAP/writer.py +122 -0
  61. MLaaS/__init__.py +0 -0
  62. MLaaS/ktrain.py +205 -0
  63. MLaaS/mnist_img.py +83 -0
  64. MLaaS/tfaas_client.py +371 -0
  65. chessanalysispipeline-0.0.17.dev3.dist-info/LICENSE +60 -0
  66. chessanalysispipeline-0.0.17.dev3.dist-info/METADATA +29 -0
  67. chessanalysispipeline-0.0.17.dev3.dist-info/RECORD +70 -0
  68. chessanalysispipeline-0.0.17.dev3.dist-info/WHEEL +5 -0
  69. chessanalysispipeline-0.0.17.dev3.dist-info/entry_points.txt +2 -0
  70. chessanalysispipeline-0.0.17.dev3.dist-info/top_level.txt +2 -0
CHAP/foxden/writer.py ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env python
2
+ #-*- coding: utf-8 -*-
3
+ """
4
+ File : writer.py
5
+ Author : Valentin Kuznetsov <vkuznet AT gmail dot com>
6
+ Description: FOXDEN writers
7
+ """
8
+
9
+ # System modules
10
+ import json
11
+
12
+ # Local modules
13
+ from CHAP.foxden.utils import HttpRequest
14
+ from CHAP.writer import Writer
15
+
16
+
17
+ class FoxdenDoiWriter(Writer):
18
+ """Writer for saving info to the FOXDEN DOI service."""
19
+ def write(
20
+ self, url, data, provider='Datacite', description='', draft=True,
21
+ publishMetadata=True, verbose=False):
22
+ """Write data to the FOXDEN DOI service.
23
+
24
+ :param data: Input data.
25
+ :type data: list[PipelineData]
26
+ :param url: URL of service.
27
+ :type url: str
28
+ :param provider: DOI provider name, e.g. Zenodo, Datacite,
29
+ Materialcommons, defaults to `'Datacite'`.
30
+ :type provider: str, optional
31
+ :param description: Dataset description.
32
+ :type description: str, optional
33
+ :param draft: Draft DOI flag, defaults to `True`.
34
+ :type draft: bool, optional
35
+ :param publishMetadata: Publish metadata with DOI,
36
+ defaults to `True`.
37
+ :type publishMetadata: bool, optional
38
+ :param verbose: Verbose output flag, defaults to `False`.
39
+ :type verbose: bool, optional
40
+ :return: HTTP response from FOXDEN DOI service.
41
+ :rtype: list[dict]
42
+ """
43
+ self.logger.info(
44
+ f'Executing "process" with url={url} data={data}')
45
+ rurl = f'{url}/publish'
46
+ raise NotImplementedError
47
+ # FIX it would be useful to perform validation of data
48
+ # if isinstance(data, list) and len(data) == 1:
49
+ # data = data[0]['data'][0]
50
+ # if not isinstance(data, dict):
51
+ # raise ValueError(f'Invalid "data" parameter ({data})')
52
+ draft_str = 'on' if draft else ''
53
+ publish_meta = 'on' if publishMetadata else ''
54
+ payload = {
55
+ # 'did': did,
56
+ 'provider': provider.lower(),
57
+ 'draft': draft_str,
58
+ 'metadata': publish_meta,
59
+ 'description': description,
60
+ }
61
+ if verbose:
62
+ self.logger.info(f'method=POST url={rurl} payload={payload}')
63
+ response = HttpRequest(rurl, payload, method='POST', scope='write')
64
+ if verbose:
65
+ self.logger.info(
66
+ f'code={response.status_code} data={response.text}')
67
+ result = [{'code': response.status_code, 'data': response.text}]
68
+ return result
69
+
70
+
71
+ class FoxdenMetadataWriter(Writer):
72
+ """Writer for saving data to the FOXDEN Metadata service."""
73
+ def write(self, data, url):#config=None):
74
+ """Write data to the FOXDEN Metadata service.
75
+
76
+ :param data: Input data.
77
+ :type data: list[PipelineData]
78
+ :param config: FOXDEN HTTP request configuration.
79
+ :type config: CHAP.foxden.models.FoxdenRequestConfig
80
+ :return: HTTP response from FOXDEN Metadata service.
81
+ :rtype: list[dict]
82
+ """
83
+ # System modules
84
+ from getpass import getuser
85
+
86
+ record = self.get_data(data, schema='metadata')
87
+ if not isinstance(record, dict):
88
+ raise ValueError('Invalid metadata record {(record)}')
89
+
90
+ # FIX it would be useful to perform validation of record
91
+
92
+ # Load and validate the FoxdenRequestConfig configuration
93
+ config = self.get_config(
94
+ config={'url': url}, schema='foxden.models.FoxdenRequestConfig')
95
+ # config=config, schema='foxden.models.FoxdenRequestConfig')
96
+ self.logger.debug(f'config: {config}')
97
+
98
+ # For now cut out anything but the did and application fields
99
+ # from the CHAP workflow metadata record
100
+ record = {'did': record['did'],
101
+ 'application': record.get('application', 'CHAP'),
102
+ 'btr': record.get('btr'),
103
+ 'user': getuser()}
104
+
105
+ # Submit HTTP request and return response
106
+ rurl = f'{config.url}'
107
+ mrec = {'Schema': 'Analysis', 'Record': record}
108
+ payload = json.dumps(mrec)
109
+ self.logger.info(f'method=POST url={rurl} payload={payload}')
110
+ response = HttpRequest(rurl, payload, method='POST', scope='write')
111
+ if config.verbose:
112
+ self.logger.info(
113
+ f'code={response.status_code} data={response.text}')
114
+ if response.status_code == 200:
115
+ result = [{'code': response.status_code, 'data': response.text}]
116
+ else:
117
+ self.logger.warning(f'HTTP error code {response.status_code}')
118
+ self.logger.warning(f'HTTP response:\n{response.__dict__}')
119
+ result = []
120
+ return result
121
+
122
+
123
+ class FoxdenProvenanceWriter(Writer):
124
+ """Writer for saving data to the FOXDEN Provenance service."""
125
+ def write(self, data, url):#config=None):
126
+ """Write data to the FOXDEN Provenance service.
127
+
128
+ :param data: Input data.
129
+ :type data: list[PipelineData]
130
+ :param config: FOXDEN HTTP request configuration.
131
+ :type config: CHAP.foxden.models.FoxdenRequestConfig
132
+ :return: HTTP response from FOXDEN Provenance service.
133
+ :rtype: list[dict]
134
+ """
135
+ record = self.get_data(data, name='FoxdenProvenanceProcessor')
136
+ if not isinstance(record, dict):
137
+ raise ValueError('Invalid provenance record {(record)}')
138
+
139
+ # FIX it would be useful to perform validation of data
140
+
141
+ # Load and validate the FoxdenRequestConfig configuration
142
+ config = self.get_config(
143
+ config={'url': url}, schema='foxden.models.FoxdenRequestConfig')
144
+ # config=config, schema='foxden.models.FoxdenRequestConfig')
145
+ self.logger.debug(f'config: {config}')
146
+
147
+ # Submit HTTP request and return response
148
+ rurl = f'{url}/dataset'
149
+ payload = json.dumps(record)
150
+ self.logger.info(f'method=POST url={rurl} payload={payload}')
151
+ response = HttpRequest(rurl, payload, method='POST', scope='write')
152
+ if config.verbose:
153
+ self.logger.info(
154
+ f'code={response.status_code} data={response.text}')
155
+ if response.status_code == 200:
156
+ result = [{'code': response.status_code, 'data': response.text}]
157
+ else:
158
+ self.logger.warning(f'HTTP error code {response.status_code}')
159
+ self.logger.warning(f'HTTP response:\n{response.__dict__}')
160
+ result = []
161
+ return result
162
+
163
+
164
+ if __name__ == '__main__':
165
+ # Local modules
166
+ from CHAP.writer import main
167
+
168
+ main()
@@ -0,0 +1,11 @@
1
+ """This subpackage contains `PipelineItems` unique to GIWAXS data
2
+ processing workflows.
3
+ """
4
+
5
+ #from CHAP.giwaxs.models import PyfaiIntegrationConfig
6
+ from CHAP.giwaxs.processor import (
7
+ GiwaxsConversionProcessor,
8
+ PyfaiIntegrationProcessor,
9
+ )
10
+ # from CHAP.giwaxs.reader import
11
+ # from CHAP.giwaxs.writer import
CHAP/giwaxs/models.py ADDED
@@ -0,0 +1,491 @@
1
+ """GIWAXS Pydantic model classes."""
2
+
3
+ # System modules
4
+ from copy import deepcopy
5
+ import os
6
+ from typing import (
7
+ Literal,
8
+ Optional,
9
+ Union,
10
+ )
11
+
12
+ # Third party modules
13
+ from pydantic import (
14
+ FilePath,
15
+ PrivateAttr,
16
+ confloat,
17
+ conint,
18
+ conlist,
19
+ constr,
20
+ field_validator,
21
+ model_validator,
22
+ )
23
+ from pyFAI.integrator.azimuthal import AzimuthalIntegrator
24
+
25
+ # Local modules
26
+ from CHAP import CHAPBaseModel
27
+ from CHAP.common.models.map import Detector
28
+
29
+
30
+ class AzimuthalIntegratorConfig(Detector, CHAPBaseModel):
31
+ """Azimuthal integrator configuration class to represent a single
32
+ detector used in the experiment.
33
+
34
+ :param mask_file: Path to the mask file.
35
+ :type mask_file: FilePath, optional
36
+ :param poni_file: Path to the PONI file, specify either `poni_file`
37
+ or `params`, not both.
38
+ :type poni_file: FilePath, optional
39
+ :param params: Azimuthal integrator configuration parameters,
40
+ specify either `poni_file` or `params`, not both.
41
+ :type params: dict, optional
42
+ """
43
+ mask_file: Optional[FilePath] = None
44
+ params: Optional[dict] = None
45
+ poni_file: Optional[FilePath] = None
46
+
47
+ _ai: AzimuthalIntegrator = PrivateAttr()
48
+
49
+ @model_validator(mode='before')
50
+ @classmethod
51
+ def validate_azimuthalintegratorconfig_before(cls, data):
52
+ if isinstance(data, dict):
53
+ inputdir = data.get('inputdir')
54
+ mask_file = data.get('mask_file')
55
+ params = data.get('params')
56
+ poni_file = data.get('poni_file')
57
+ if mask_file is not None:
58
+ if inputdir is not None and not os.path.isabs(mask_file):
59
+ data['mask_file'] = mask_file
60
+ if params is not None:
61
+ if poni_file is not None:
62
+ print(
63
+ 'Specify either poni_file or params, not both, '
64
+ 'ignoring poni_file')
65
+ poni_file = None
66
+ elif poni_file is not None:
67
+ if inputdir is not None and not os.path.isabs(poni_file):
68
+ data['poni_file'] = poni_file
69
+ else:
70
+ raise ValueError('Specify either poni_file or params')
71
+ return data
72
+
73
+ @model_validator(mode='after')
74
+ def validate_azimuthalintegratorconfig_after(self):
75
+ """Set the default azimuthal integrator.
76
+
77
+ :return: Validated configuration class.
78
+ :rtype: AzimuthalIntegratorConfig
79
+ """
80
+ if self.params is not None:
81
+ self._ai = AzimuthalIntegrator(**self.params)
82
+ elif self.poni_file is not None:
83
+ # Third party modules
84
+ from pyFAI import load
85
+
86
+ self._ai = load(str(self.poni_file))
87
+ self.params = {
88
+ 'detector': self._ai.detector.name,
89
+ 'dist': self._ai.dist,
90
+ 'poni1': self._ai.poni1,
91
+ 'poni2': self._ai.poni2,
92
+ 'rot1': self._ai.rot1,
93
+ 'rot2': self._ai.rot2,
94
+ 'rot3': self._ai.rot3,
95
+ 'wavelength': self._ai.wavelength,
96
+ }
97
+ return self
98
+
99
+ @property
100
+ def ai(self):
101
+ """Return the azimuthal integrator."""
102
+ return self._ai
103
+
104
+
105
+ class GiwaxsConversionConfig(CHAPBaseModel):
106
+ """Class representing metadata required to locate GIWAXS image
107
+ files for a single scan to convert to q_par/q_perp coordinates.
108
+
109
+ :ivar azimuthal_integrators: List of azimuthal integrator
110
+ configurations.
111
+ :type azimuthal_integrators: list[
112
+ CHAP.giwaxs.models.AzimuthalIntegratorConfig]
113
+ :ivar scan_step_indices: Optional scan step indices to convert.
114
+ If not specified, all images will be converted.
115
+ :type scan_step_indices: Union(int, list[int], str), optional
116
+ :ivar save_raw_data: Save the raw data in the NeXus output,
117
+ defaults to `False`.
118
+ :type save_raw_data: bool, optional
119
+ """
120
+ azimuthal_integrators: conlist(
121
+ min_length=1, item_type=AzimuthalIntegratorConfig)
122
+ scan_step_indices: Optional[
123
+ conlist(min_length=1, item_type=conint(ge=0))] = None
124
+ save_raw_data: Optional[bool] = False
125
+
126
+ @field_validator('scan_step_indices', mode='before')
127
+ @classmethod
128
+ def validate_scan_step_indices(cls, scan_step_indices):
129
+ """Ensure that a valid configuration was provided and finalize
130
+ PONI filepaths.
131
+
132
+ :param data: Pydantic validator data object.
133
+ :type data: GiwaxsConversionConfig,
134
+ pydantic_core._pydantic_core.ValidationInfo
135
+ :return: The currently validated list of class properties.
136
+ :rtype: dict
137
+ """
138
+ if isinstance(scan_step_indices, int):
139
+ scan_step_indices = [scan_step_indices]
140
+ if isinstance(scan_step_indices, str):
141
+ # Local modules
142
+ from CHAP.utils.general import string_to_list
143
+
144
+ scan_step_indices = string_to_list(scan_step_indices)
145
+
146
+ return scan_step_indices
147
+
148
+
149
+ class MultiGeometryConfig(CHAPBaseModel):
150
+ """Class representing the configuration for treating simultaneously
151
+ multiple detector configuration within a single integration
152
+
153
+ :ivar ais: List of detector IDs of azimuthal integrators
154
+ :type ais: Union[str, list[str]]
155
+ :ivar azimuth_range: Common azimuthal range for integration,
156
+ defaults to `[-180.0, 180.0]`.
157
+ :type azimuth_range: Union(list[float, float],
158
+ tuple[float, float]), optional
159
+ :ivar radial_range: Common range for integration, defaults to
160
+ `[0.0, 180.0]`.
161
+ :type radial_range: Union(list[float, float],
162
+ tuple[float, float]), optional
163
+ """
164
+ ais: conlist(
165
+ min_length=1, item_type=constr(min_length=1, strip_whitespace=True))
166
+ azimuth_range: Optional[
167
+ conlist(
168
+ min_length=2, max_length=2,
169
+ item_type=confloat(ge=-180, le=360, allow_inf_nan=False))
170
+ ] = [-180.0, 180.0]
171
+ radial_range: Optional[
172
+ conlist(
173
+ min_length=2, max_length=2,
174
+ item_type=confloat(ge=0, le=180, allow_inf_nan=False))
175
+ ] = [0.0, 180.0]
176
+ unit: Optional[
177
+ constr(strip_whitespace=True, min_length=1)] = 'q_A^-1'
178
+ chi_disc: Optional[int] = 180
179
+ empty: Optional[confloat(allow_inf_nan=False)] = 0.0
180
+ wavelength: Optional[confloat(allow_inf_nan=False)] = None
181
+
182
+ @field_validator('ais', mode='before')
183
+ @classmethod
184
+ def validate_ais(cls, ais):
185
+ """Validate the detector IDs of the azimuthal integrators.
186
+
187
+ :param ais: The detector IDs.
188
+ :type ais: str, list[str]
189
+ :return: The detector ais.
190
+ :rtype: list[str]
191
+ """
192
+ if isinstance(ais, str):
193
+ return [ais]
194
+ return ais
195
+
196
+ # @field_validator('radial_units')
197
+ # @classmethod
198
+ # def validate_radial_units(cls, radial_units):
199
+ # """Validate the radial units for the integration.
200
+ #
201
+ # :param radial_units: Unvalidated radial units for the
202
+ # integration.
203
+ # :type radial_units: str
204
+ # :raises ValueError: If radial units are not one of the
205
+ # recognized radial units.
206
+ # :return: Validated radial units.
207
+ # :rtype: str
208
+ # """
209
+ # # Third party modules
210
+ # from pyFAI.units import RADIAL_UNITS
211
+ #
212
+ # if radial_units in RADIAL_UNITS.keys():
213
+ # return radial_units
214
+ # else:
215
+ # raise ValueError(
216
+ # f'Invalid radial units: {radial_units}. Must be one of '
217
+ # ', '.join(RADIAL_UNITS.keys()))
218
+
219
+ # @field_validator('azimuthal_units')
220
+ # def validate_azimuthal_units(cls, azimuthal_units):
221
+ # """Validate that `azimuthal_units` is one of the keys in the
222
+ # `pyFAI.units.AZIMUTHAL_UNITS` dictionary.
223
+ #
224
+ # :param azimuthal_units: The string representing the unit to be
225
+ # validated.
226
+ # :type azimuthal_units: str
227
+ # :raises ValueError: If `azimuthal_units` is not one of the
228
+ # keys in `pyFAI.units.AZIMUTHAL_UNITS`.
229
+ # :return: The original supplied value, if is one of the keys in
230
+ # `pyFAI.units.AZIMUTHAL_UNITS`.
231
+ # :rtype: str
232
+ # """
233
+ # # Third party modules
234
+ # from pyFAI.units import AZIMUTHAL_UNITS
235
+ #
236
+ # if azimuthal_units in AZIMUTHAL_UNITS.keys():
237
+ # return azimuthal_units
238
+ # else:
239
+ # raise ValueError(
240
+ # f'Invalid azimuthal units: {azimuthal_units}. Must be one of '
241
+ # ', '.join(AZIMUTHAL_UNITS.keys()))
242
+
243
+
244
+ class Integrate1dConfig(CHAPBaseModel):
245
+ """Class with the input parameters to performs 1D azimuthal
246
+ integration with `pyFAI`.
247
+
248
+ :ivar error_model: When the variance is unknown, an error model
249
+ can be given:
250
+ `poisson` (variance = I) or `azimuthal` (variance = (I-<I>)^2).
251
+ :type error_model: str, optionalw
252
+ :ivar method: IntegrationMethod instance or 3-tuple with
253
+ (splitting, algorithm, implementation)
254
+ :type method: IntegrationMethod, optional
255
+ :ivar npt: Number of integration points, defaults to 1800.
256
+ :type npt: int, optional
257
+ """
258
+ # correctSolidAngle: true
259
+ # dark: None
260
+ error_model: Optional[constr(strip_whitespace=True, min_length=1)] = None
261
+ # filename: None
262
+ # flat: None
263
+ # mask: None
264
+ # metadata: None
265
+ method: Optional[
266
+ conlist(
267
+ min_length=3, max_length=3,
268
+ item_type=constr(strip_whitespace=True, min_length=1))
269
+ ] = ['bbox', 'csr', 'cython']
270
+ #normalization_factor: Optional[confloat(allow_inf_nan=False)] = 1.0
271
+ npt: Optional[conint(gt=0)] = 1800
272
+ # polarization_factor: None
273
+ # variance: None
274
+ attrs: Optional[dict] = {}
275
+
276
+
277
+ class Integrate2dConfig(CHAPBaseModel):
278
+ """Class with the input parameters to performs 2D azimuthal
279
+ integration with `pyFAI`.
280
+
281
+ :ivar error_model: When the variance is unknown, an error model
282
+ can be given:
283
+ `poisson` (variance = I) or `azimuthal` (variance = (I-<I>)^2).
284
+ :type error_model: str, optional
285
+ :ivar method: IntegrationMethod instance or 3-tuple with
286
+ (splitting, algorithm, implementation)
287
+ :type method: IntegrationMethod, optional
288
+ :ivar npt_azim: Number of points for the integration in the
289
+ azimuthal direction, defaults to 3600.
290
+ :type npt_azim: int, optional
291
+ :ivar npt_rad: Number of points for the integration in the
292
+ radial direction, defaults to 1800.
293
+ :type npt_rad: int, optional
294
+ """
295
+ # correctSolidAngle: true
296
+ # dark: None
297
+ # filename: None
298
+ # flat: None
299
+ error_model: Optional[constr(strip_whitespace=True, min_length=1)] = None
300
+ # mask: None
301
+ # metadata: None
302
+ method: Optional[
303
+ conlist(
304
+ min_length=3, max_length=3,
305
+ item_type=constr(strip_whitespace=True, min_length=1))
306
+ ] = ['bbox', 'csr', 'cython']
307
+ # normalization_factor: None
308
+ npt_azim: Optional[conint(gt=0)] = 3600
309
+ npt_rad: Optional[conint(gt=0)] = 1800
310
+ # polarization_factor: None
311
+ # safe: None
312
+ # variance: None
313
+ attrs: Optional[dict] = {}
314
+
315
+
316
+ class PyfaiIntegratorConfig(CHAPBaseModel):
317
+ """Class representing the configuration for detector data
318
+ integrater for `pyFAI`.
319
+
320
+ :ivar right_handed: For radial and cake integration, reverse the
321
+ direction of the azimuthal coordinate from pyFAI's convention,
322
+ defaults to True.
323
+ :type right_handed: bool, optional
324
+ """
325
+ name: constr(strip_whitespace=True, min_length=1)
326
+ integration_method: Literal[
327
+ 'integrate1d', 'integrate2d', 'integrate_radial']
328
+ multi_geometry: Optional[MultiGeometryConfig] = None
329
+ integration_params: Optional[
330
+ Union[Integrate1dConfig, Integrate2dConfig]] = None
331
+ right_handed: bool = True
332
+
333
+ @model_validator(mode='before')
334
+ @classmethod
335
+ def validate_pyfaiintegratorconfig_before(cls, data):
336
+ """Validate the integration parameters.
337
+
338
+ :param data: Pydantic validator data object.
339
+ :type data: PyfaiIntegratorConfig,
340
+ pydantic_core._pydantic_core.ValidationInfo
341
+ :return: The currently validated list of class properties.
342
+ :rtype: dict
343
+ """
344
+ if 'multi_geometry' in data:
345
+ return data
346
+ mg = MultiGeometryConfig(**data['integration_params'])
347
+ if len(mg.ais) != 1:
348
+ raise ValueError('Invalid parameter integration_params["ais"] ',
349
+ f'({mg.ais}, multiple detectors not allowed')
350
+ data['integration_params']['attrs'] = mg.model_dump(
351
+ include={'azimuth_range', 'radial_range', 'unit'})
352
+ return data
353
+
354
+ @model_validator(mode='after')
355
+ def validate_pyfaiintegratorconfig_after(self):
356
+ """Choose the integration_params type depending on the
357
+ `integration_method` value.
358
+
359
+ :param data: Pydantic validator data object.
360
+ :type data: pydantic_core._pydantic_core.ValidationInfo
361
+ :raises ValueError: Invalid `integration_method`.
362
+ :return: The validated list of class properties.
363
+ :rtype: dict
364
+ """
365
+ if self.integration_method == 'integrate1d':
366
+ if self.integration_params is None:
367
+ self.integration_params = Integrate1dConfig()
368
+ else:
369
+ self.integration_params = Integrate1dConfig(
370
+ **self.integration_params.model_dump())
371
+ elif self.integration_method == 'integrate2d':
372
+ if self.integration_params is None:
373
+ self.integration_params = Integrate2dConfig()
374
+ else:
375
+ self.integration_params = Integrate2dConfig(
376
+ **self.integration_params.model_dump())
377
+ else:
378
+ raise ValueError('Invalid parameter integration_params '
379
+ f'({self.integration_params})')
380
+ return self
381
+
382
+ def integrate(self, ais, data, masks=None):
383
+ if self.integration_method == 'integrate_radial':
384
+ raise NotImplementedError
385
+ else:
386
+ npts = [d.shape[0] for d in data.values()]
387
+ if not all(_npts == npts[0] for _npts in npts):
388
+ raise RuntimeError('Different number of detector frames for '
389
+ f'each azimuthal integrator ({npts})')
390
+ npts = npts[0]
391
+ if self.multi_geometry is None:
392
+ id = list(ais.keys())[0]
393
+ ai = list(ais.values())[0]
394
+ integration_method = getattr(ai, self.integration_method)
395
+ integration_params = self.integration_params.model_dump()
396
+ integration_params = {
397
+ **integration_params, **integration_params['attrs']}
398
+ del integration_params['attrs']
399
+ results = [
400
+ integration_method(
401
+ data[id][i], mask=masks[id], **integration_params)
402
+ for i in range(npts)
403
+ ]
404
+ # import matplotlib.pyplot as plt
405
+ # plt.figure()
406
+ # plt.plot(results[0].radial, results[0].intensity, label="XPD 2")
407
+ # plt.show()
408
+ else:
409
+ # Third party modules
410
+ from pyFAI.multi_geometry import MultiGeometry
411
+
412
+ mg = MultiGeometry(
413
+ [ais[ai] for ai in self.multi_geometry.ais],
414
+ **self.multi_geometry.model_dump(exclude={'ais'}))
415
+ integration_method = getattr(mg, self.integration_method)
416
+ if masks is None:
417
+ lst_mask = None
418
+ else:
419
+ lst_mask = [masks[ai] for ai in self.multi_geometry.ais]
420
+ results = [
421
+ integration_method(
422
+ [data[ai][i] for ai in self.multi_geometry.ais],
423
+ lst_mask=lst_mask,
424
+ **self.integration_params.model_dump(exclude='attrs'))
425
+ # normalization_factor=[8177142.28771039],
426
+ # method=('bbox', 'csr', 'cython'),
427
+ # **self.integration_params.model_dump(exclude={'normalization_factor', 'method'}))
428
+ for i in range(npts)
429
+ ]
430
+ if npts == 1:
431
+ intensities = results[0].intensity
432
+ else:
433
+ intensities = [v.intensity for v in results]
434
+ if isinstance(self.integration_params, Integrate1dConfig):
435
+ if self.multi_geometry is None:
436
+ unit = integration_params['unit']
437
+ else:
438
+ unit = self.multi_geometry.unit
439
+ results = {
440
+ 'intensities': intensities,
441
+ 'radial': {'coords': results[0].radial, 'unit': unit}}
442
+ else:
443
+ results = {
444
+ 'intensities': intensities,
445
+ 'radial': {
446
+ 'coords': results[0].radial,
447
+ 'unit': results[0].radial_unit.name},
448
+ 'azimuthal': {
449
+ 'coords': results[0].azimuthal,
450
+ 'unit': results[0].azimuthal_unit.name}}
451
+
452
+ return results
453
+
454
+
455
+ class PyfaiIntegrationConfig(CHAPBaseModel):
456
+ azimuthal_integrators: Optional[conlist(
457
+ min_length=1, item_type=AzimuthalIntegratorConfig)] = None
458
+ integrations: conlist(min_length=1, item_type=PyfaiIntegratorConfig)
459
+ sum_axes: Optional[bool] = False
460
+ #sum_axes: Optional[
461
+ # Union[bool, conlist(min_length=1, item_type=str)]] = False
462
+
463
+ @model_validator(mode='before')
464
+ @classmethod
465
+ def validate_pyfaiintegrationconfig_before(cls, data):
466
+ """Ensure that a valid configuration was provided and finalize
467
+ PONI filepaths.
468
+
469
+ :param data: Pydantic validator data object.
470
+ :type data: GiwaxsConversionConfig,
471
+ pydantic_core._pydantic_core.ValidationInfo
472
+ :return: The currently validated list of class properties.
473
+ :rtype: dict
474
+ """
475
+ if isinstance(data, dict):
476
+ inputdir = data.get('inputdir')
477
+ if inputdir is not None and 'azimuthal_integrators' in data:
478
+ ais = data.get('azimuthal_integrators')
479
+ for i, ai in enumerate(deepcopy(ais)):
480
+ if isinstance(ai, dict):
481
+ poni_file = ai['poni_file']
482
+ if not os.path.isabs(poni_file):
483
+ ais[i]['poni_file'] = os.path.join(
484
+ inputdir, poni_file)
485
+ else:
486
+ poni_file = ai.poni_file
487
+ if not os.path.isabs(poni_file):
488
+ ais[i].poni_file = os.path.join(
489
+ inputdir, poni_file)
490
+ data['azimuthal_integrators'] = ais
491
+ return data