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/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()
|
CHAP/giwaxs/__init__.py
ADDED
|
@@ -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
|