modacor 1.0.0__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.
- modacor/__init__.py +30 -0
- modacor/dataclasses/__init__.py +0 -0
- modacor/dataclasses/basedata.py +973 -0
- modacor/dataclasses/databundle.py +23 -0
- modacor/dataclasses/helpers.py +45 -0
- modacor/dataclasses/messagehandler.py +75 -0
- modacor/dataclasses/process_step.py +233 -0
- modacor/dataclasses/process_step_describer.py +146 -0
- modacor/dataclasses/processing_data.py +59 -0
- modacor/dataclasses/trace_event.py +118 -0
- modacor/dataclasses/uncertainty_tools.py +132 -0
- modacor/dataclasses/validators.py +84 -0
- modacor/debug/pipeline_tracer.py +548 -0
- modacor/io/__init__.py +33 -0
- modacor/io/csv/__init__.py +0 -0
- modacor/io/csv/csv_sink.py +114 -0
- modacor/io/csv/csv_source.py +210 -0
- modacor/io/hdf/__init__.py +27 -0
- modacor/io/hdf/hdf_source.py +120 -0
- modacor/io/io_sink.py +41 -0
- modacor/io/io_sinks.py +61 -0
- modacor/io/io_source.py +164 -0
- modacor/io/io_sources.py +208 -0
- modacor/io/processing_path.py +113 -0
- modacor/io/tiled/__init__.py +16 -0
- modacor/io/tiled/tiled_source.py +403 -0
- modacor/io/yaml/__init__.py +27 -0
- modacor/io/yaml/yaml_source.py +116 -0
- modacor/modules/__init__.py +53 -0
- modacor/modules/base_modules/__init__.py +0 -0
- modacor/modules/base_modules/append_processing_data.py +329 -0
- modacor/modules/base_modules/append_sink.py +141 -0
- modacor/modules/base_modules/append_source.py +181 -0
- modacor/modules/base_modules/bitwise_or_masks.py +113 -0
- modacor/modules/base_modules/combine_uncertainties.py +120 -0
- modacor/modules/base_modules/combine_uncertainties_max.py +105 -0
- modacor/modules/base_modules/divide.py +82 -0
- modacor/modules/base_modules/find_scale_factor1d.py +373 -0
- modacor/modules/base_modules/multiply.py +77 -0
- modacor/modules/base_modules/multiply_databundles.py +73 -0
- modacor/modules/base_modules/poisson_uncertainties.py +69 -0
- modacor/modules/base_modules/reduce_dimensionality.py +252 -0
- modacor/modules/base_modules/sink_processing_data.py +80 -0
- modacor/modules/base_modules/subtract.py +80 -0
- modacor/modules/base_modules/subtract_databundles.py +67 -0
- modacor/modules/base_modules/units_label_update.py +66 -0
- modacor/modules/instrument_modules/__init__.py +0 -0
- modacor/modules/instrument_modules/readme.md +9 -0
- modacor/modules/technique_modules/__init__.py +0 -0
- modacor/modules/technique_modules/scattering/__init__.py +0 -0
- modacor/modules/technique_modules/scattering/geometry_helpers.py +114 -0
- modacor/modules/technique_modules/scattering/index_pixels.py +492 -0
- modacor/modules/technique_modules/scattering/indexed_averager.py +628 -0
- modacor/modules/technique_modules/scattering/pixel_coordinates_3d.py +417 -0
- modacor/modules/technique_modules/scattering/solid_angle_correction.py +63 -0
- modacor/modules/technique_modules/scattering/xs_geometry.py +571 -0
- modacor/modules/technique_modules/scattering/xs_geometry_from_pixel_coordinates.py +293 -0
- modacor/runner/__init__.py +0 -0
- modacor/runner/pipeline.py +749 -0
- modacor/runner/process_step_registry.py +224 -0
- modacor/tests/__init__.py +27 -0
- modacor/tests/dataclasses/test_basedata.py +519 -0
- modacor/tests/dataclasses/test_basedata_operations.py +439 -0
- modacor/tests/dataclasses/test_basedata_to_base_units.py +57 -0
- modacor/tests/dataclasses/test_process_step_describer.py +73 -0
- modacor/tests/dataclasses/test_processstep.py +282 -0
- modacor/tests/debug/test_tracing_integration.py +188 -0
- modacor/tests/integration/__init__.py +0 -0
- modacor/tests/integration/test_pipeline_run.py +238 -0
- modacor/tests/io/__init__.py +27 -0
- modacor/tests/io/csv/__init__.py +0 -0
- modacor/tests/io/csv/test_csv_source.py +156 -0
- modacor/tests/io/hdf/__init__.py +27 -0
- modacor/tests/io/hdf/test_hdf_source.py +92 -0
- modacor/tests/io/test_io_sources.py +119 -0
- modacor/tests/io/tiled/__init__.py +12 -0
- modacor/tests/io/tiled/test_tiled_source.py +120 -0
- modacor/tests/io/yaml/__init__.py +27 -0
- modacor/tests/io/yaml/static_data_example.yaml +26 -0
- modacor/tests/io/yaml/test_yaml_source.py +47 -0
- modacor/tests/modules/__init__.py +27 -0
- modacor/tests/modules/base_modules/__init__.py +27 -0
- modacor/tests/modules/base_modules/test_append_processing_data.py +219 -0
- modacor/tests/modules/base_modules/test_append_sink.py +76 -0
- modacor/tests/modules/base_modules/test_append_source.py +180 -0
- modacor/tests/modules/base_modules/test_bitwise_or_masks.py +264 -0
- modacor/tests/modules/base_modules/test_combine_uncertainties.py +105 -0
- modacor/tests/modules/base_modules/test_combine_uncertainties_max.py +109 -0
- modacor/tests/modules/base_modules/test_divide.py +140 -0
- modacor/tests/modules/base_modules/test_find_scale_factor1d.py +220 -0
- modacor/tests/modules/base_modules/test_multiply.py +113 -0
- modacor/tests/modules/base_modules/test_multiply_databundles.py +136 -0
- modacor/tests/modules/base_modules/test_poisson_uncertainties.py +61 -0
- modacor/tests/modules/base_modules/test_reduce_dimensionality.py +358 -0
- modacor/tests/modules/base_modules/test_sink_processing_data.py +119 -0
- modacor/tests/modules/base_modules/test_subtract.py +111 -0
- modacor/tests/modules/base_modules/test_subtract_databundles.py +136 -0
- modacor/tests/modules/base_modules/test_units_label_update.py +91 -0
- modacor/tests/modules/technique_modules/__init__.py +0 -0
- modacor/tests/modules/technique_modules/scattering/__init__.py +0 -0
- modacor/tests/modules/technique_modules/scattering/test_geometry_helpers.py +198 -0
- modacor/tests/modules/technique_modules/scattering/test_index_pixels.py +426 -0
- modacor/tests/modules/technique_modules/scattering/test_indexed_averaging.py +559 -0
- modacor/tests/modules/technique_modules/scattering/test_pixel_coordinates_3d.py +282 -0
- modacor/tests/modules/technique_modules/scattering/test_xs_geometry_from_pixel_coordinates.py +224 -0
- modacor/tests/modules/technique_modules/scattering/test_xsgeometry.py +635 -0
- modacor/tests/requirements.txt +12 -0
- modacor/tests/runner/test_pipeline.py +438 -0
- modacor/tests/runner/test_process_step_registry.py +65 -0
- modacor/tests/test_import.py +43 -0
- modacor/tests/test_modacor.py +17 -0
- modacor/tests/test_units.py +79 -0
- modacor/units.py +97 -0
- modacor-1.0.0.dist-info/METADATA +482 -0
- modacor-1.0.0.dist-info/RECORD +120 -0
- modacor-1.0.0.dist-info/WHEEL +5 -0
- modacor-1.0.0.dist-info/licenses/AUTHORS.md +11 -0
- modacor-1.0.0.dist-info/licenses/LICENSE +11 -0
- modacor-1.0.0.dist-info/licenses/LICENSE.txt +11 -0
- modacor-1.0.0.dist-info/top_level.txt +1 -0
modacor/io/io_sources.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# /usr/bin/env python3
|
|
3
|
+
# -*- coding: utf-8 -*-
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
__coding__ = "utf-8"
|
|
8
|
+
__authors__ = ["Malte Storm", "Brian R. Pauw"]
|
|
9
|
+
__copyright__ = "Copyright 2025, The MoDaCor team"
|
|
10
|
+
__date__ = "06/06/2025"
|
|
11
|
+
__status__ = "Development" # "Development", "Production"
|
|
12
|
+
# end of header and standard imports
|
|
13
|
+
|
|
14
|
+
__all__ = ["IoSources"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from typing import Any, Optional
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
from attrs import define, field
|
|
21
|
+
|
|
22
|
+
from modacor.io.io_source import ArraySlice, IoSource
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@define
|
|
26
|
+
class IoSources:
|
|
27
|
+
"""
|
|
28
|
+
IoSources is a collection of al the defined IoSource instances to load data.
|
|
29
|
+
|
|
30
|
+
It provides the interface to register new sources and access the different
|
|
31
|
+
data sources through a single interface.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
defined_sources: dict[str, IoSource] = field(factory=dict)
|
|
35
|
+
|
|
36
|
+
def register_source(self, source: IoSource, source_reference: str | None = None) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Register a new source class with the given name. If no source_reference is provided, the
|
|
39
|
+
source's own source_reference attribute will be used.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
source : IoSource
|
|
44
|
+
The class of the source to register.
|
|
45
|
+
source_reference : str
|
|
46
|
+
The reference name of the source to register.
|
|
47
|
+
"""
|
|
48
|
+
if not isinstance(source, IoSource):
|
|
49
|
+
raise TypeError("source_class must be a subclass of IoSource")
|
|
50
|
+
if source_reference is None:
|
|
51
|
+
source_reference = source.source_reference
|
|
52
|
+
if not isinstance(source_reference, str):
|
|
53
|
+
raise TypeError("source_name must be a string")
|
|
54
|
+
if source_reference in self.defined_sources:
|
|
55
|
+
raise ValueError(f"Source {source_reference} already registered.")
|
|
56
|
+
self.defined_sources[source_reference] = source
|
|
57
|
+
|
|
58
|
+
def get_source(self, source_reference: str) -> IoSource:
|
|
59
|
+
"""
|
|
60
|
+
Get the source class associated with the given name.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
source_reference : str
|
|
65
|
+
The reference name of the source to access.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
IoSource :
|
|
70
|
+
The source class associated with the provided name.
|
|
71
|
+
"""
|
|
72
|
+
if source_reference not in self.defined_sources:
|
|
73
|
+
raise KeyError(f"Source {source_reference} not registered.")
|
|
74
|
+
return self.defined_sources[source_reference]
|
|
75
|
+
|
|
76
|
+
def split_data_reference(self, data_reference: str) -> tuple[str, str]:
|
|
77
|
+
"""
|
|
78
|
+
Split the data reference into source reference and data key.
|
|
79
|
+
|
|
80
|
+
The data_reference is composed of the source reference and the internal
|
|
81
|
+
data reference, separated by "::".
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
data_reference : str
|
|
86
|
+
The reference name of the source to access.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
tuple[str, str] :
|
|
91
|
+
A tuple containing the source reference and the data key.
|
|
92
|
+
"""
|
|
93
|
+
_split = data_reference.split("::", 1)
|
|
94
|
+
if len(_split) != 2:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"data_reference must be in the format 'source_ref::data_key' with a "
|
|
97
|
+
"double colon separating both entries."
|
|
98
|
+
)
|
|
99
|
+
return _split[0], _split[1]
|
|
100
|
+
|
|
101
|
+
def get_data(self, data_reference: str, load_slice: Optional[ArraySlice] = ...) -> np.ndarray:
|
|
102
|
+
"""
|
|
103
|
+
Get data from the specified source using the provided data key.
|
|
104
|
+
|
|
105
|
+
The data_reference is composed of the source reference and the internal
|
|
106
|
+
data reference, separated by "::".
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
data_reference : str
|
|
111
|
+
The reference name of the source to access.
|
|
112
|
+
load_slice : Optional[ArraySlice]
|
|
113
|
+
A slice or tuple of slices to apply to the data. If None or ellipsis, the entire data is returned.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
Any :
|
|
118
|
+
The data associated with the provided key.
|
|
119
|
+
"""
|
|
120
|
+
_source_ref, _data_key = self.split_data_reference(data_reference)
|
|
121
|
+
_source = self.get_source(_source_ref)
|
|
122
|
+
return _source.get_data(_data_key, load_slice=load_slice)
|
|
123
|
+
|
|
124
|
+
def get_data_shape(self, data_reference: str) -> np.ndarray:
|
|
125
|
+
"""
|
|
126
|
+
Get data from the specified source using the provided data key.
|
|
127
|
+
|
|
128
|
+
The data_reference is composed of the source reference and the internal
|
|
129
|
+
data reference, separated by "::".
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
data_reference : str
|
|
134
|
+
The reference name of the source to access.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
Any :
|
|
139
|
+
The data associated with the provided key.
|
|
140
|
+
"""
|
|
141
|
+
_source_ref, _data_key = self.split_data_reference(data_reference)
|
|
142
|
+
_source = self.get_source(_source_ref)
|
|
143
|
+
return _source.get_data_shape(_data_key)
|
|
144
|
+
|
|
145
|
+
def get_data_dtype(self, data_reference: str) -> np.ndarray:
|
|
146
|
+
"""
|
|
147
|
+
Get data from the specified source using the provided data key.
|
|
148
|
+
|
|
149
|
+
The data_reference is composed of the source reference and the internal
|
|
150
|
+
data reference, separated by "::".
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
data_reference : str
|
|
155
|
+
The reference name of the source to access.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
Any :
|
|
160
|
+
The data associated with the provided key.
|
|
161
|
+
"""
|
|
162
|
+
_source_ref, _data_key = self.split_data_reference(data_reference)
|
|
163
|
+
_source = self.get_source(_source_ref)
|
|
164
|
+
return _source.get_data_dtype(_data_key)
|
|
165
|
+
|
|
166
|
+
def get_data_attributes(self, data_reference: str) -> np.ndarray:
|
|
167
|
+
"""
|
|
168
|
+
Get data from the specified source using the provided data key.
|
|
169
|
+
|
|
170
|
+
The data_reference is composed of the source reference and the internal
|
|
171
|
+
data reference, separated by "::".
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
data_reference : str
|
|
176
|
+
The reference name of the source to access.
|
|
177
|
+
index : int
|
|
178
|
+
The index to access the data.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
Any :
|
|
183
|
+
The data associated with the provided key.
|
|
184
|
+
"""
|
|
185
|
+
_source_ref, _data_key = self.split_data_reference(data_reference)
|
|
186
|
+
_source = self.get_source(_source_ref)
|
|
187
|
+
return _source.get_data_attributes(_data_key)
|
|
188
|
+
|
|
189
|
+
def get_static_metadata(self, data_reference: str) -> Any:
|
|
190
|
+
"""
|
|
191
|
+
Get static metadata from the specified source using the provided data key.
|
|
192
|
+
|
|
193
|
+
The data_reference is composed of the source reference and the internal
|
|
194
|
+
data reference, separated by "::".
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
data_reference : str
|
|
199
|
+
The reference name of the source to access.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
Any :
|
|
204
|
+
The static metadata associated with the provided key.
|
|
205
|
+
"""
|
|
206
|
+
_source_ref, _data_key = data_reference.split("::", 1)
|
|
207
|
+
_source = self.get_source(_source_ref)
|
|
208
|
+
return _source.get_static_metadata(_data_key)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# /usr/bin/env python3
|
|
3
|
+
# -*- coding: utf-8 -*-
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
__coding__ = "utf-8"
|
|
8
|
+
__authors__ = ["Brian R. Pauw"]
|
|
9
|
+
__copyright__ = "Copyright 2026, The MoDaCor team"
|
|
10
|
+
__date__ = "09/01/2026"
|
|
11
|
+
__status__ = "Development" # "Development", "Production"
|
|
12
|
+
# end of header and standard imports
|
|
13
|
+
|
|
14
|
+
__all__ = ["ProcessingPath", "parse_processing_path", "resolve_processing_path", "infer_units_for_path"]
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import PurePosixPath
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from modacor import ureg
|
|
21
|
+
from modacor.dataclasses.basedata import BaseData
|
|
22
|
+
from modacor.dataclasses.processing_data import ProcessingData
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class ProcessingPath:
|
|
27
|
+
original: str
|
|
28
|
+
databundle_key: str
|
|
29
|
+
basedata_name: str
|
|
30
|
+
subpath: tuple[str, ...]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_processing_path(path: str) -> ProcessingPath:
|
|
34
|
+
if not isinstance(path, str) or not path.strip():
|
|
35
|
+
raise TypeError("ProcessingData path must be a non-empty string.")
|
|
36
|
+
|
|
37
|
+
original = path.strip()
|
|
38
|
+
# normalize like posix path; allow leading '/'
|
|
39
|
+
pp = PurePosixPath(original)
|
|
40
|
+
parts = tuple(p for p in pp.parts if p != "/")
|
|
41
|
+
|
|
42
|
+
if len(parts) < 2:
|
|
43
|
+
raise ValueError(f"ProcessingData path must be at least '<bundle>/<basedata>' (got: {original}).")
|
|
44
|
+
|
|
45
|
+
return ProcessingPath(
|
|
46
|
+
original=original,
|
|
47
|
+
databundle_key=parts[0],
|
|
48
|
+
basedata_name=parts[1],
|
|
49
|
+
subpath=parts[2:],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def resolve_processing_path(processing_data: ProcessingData, path: str) -> Any:
|
|
54
|
+
pp = parse_processing_path(path)
|
|
55
|
+
bd = processing_data[pp.databundle_key][pp.basedata_name]
|
|
56
|
+
if not isinstance(bd, BaseData):
|
|
57
|
+
raise TypeError(f"Path {pp.original} did not resolve to a BaseData at the root.")
|
|
58
|
+
|
|
59
|
+
obj: Any = bd
|
|
60
|
+
for key in pp.subpath:
|
|
61
|
+
if isinstance(obj, BaseData):
|
|
62
|
+
if key == "signal":
|
|
63
|
+
obj = obj.signal
|
|
64
|
+
continue
|
|
65
|
+
if key == "weights":
|
|
66
|
+
obj = obj.weights
|
|
67
|
+
continue
|
|
68
|
+
if key == "uncertainties":
|
|
69
|
+
obj = obj.uncertainties
|
|
70
|
+
continue
|
|
71
|
+
if key == "variances":
|
|
72
|
+
obj = obj.variances
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
if isinstance(obj, dict):
|
|
76
|
+
obj = obj[key]
|
|
77
|
+
else:
|
|
78
|
+
if not hasattr(obj, key):
|
|
79
|
+
raise AttributeError(
|
|
80
|
+
f"Object of type {type(obj).__name__} has no attribute '{key}' (path: {pp.original})"
|
|
81
|
+
)
|
|
82
|
+
obj = getattr(obj, key)
|
|
83
|
+
|
|
84
|
+
return obj
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def infer_units_for_path(processing_data: ProcessingData, path: str) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Stable unit inference for CSV header row 2.
|
|
90
|
+
|
|
91
|
+
- .../signal -> bd.units
|
|
92
|
+
- .../uncertainties/<k> -> bd.units
|
|
93
|
+
- .../variances/<k> -> bd.units**2
|
|
94
|
+
- .../weights -> dimensionless
|
|
95
|
+
- otherwise -> ""
|
|
96
|
+
"""
|
|
97
|
+
pp = parse_processing_path(path)
|
|
98
|
+
bd: BaseData = processing_data[pp.databundle_key][pp.basedata_name]
|
|
99
|
+
|
|
100
|
+
if len(pp.subpath) == 0:
|
|
101
|
+
return "" # no guessing
|
|
102
|
+
|
|
103
|
+
first = pp.subpath[0]
|
|
104
|
+
if first == "signal":
|
|
105
|
+
return str(bd.units)
|
|
106
|
+
if first == "uncertainties":
|
|
107
|
+
return str(bd.units)
|
|
108
|
+
if first == "variances":
|
|
109
|
+
return str(bd.units**2)
|
|
110
|
+
if first == "weights":
|
|
111
|
+
return str(ureg.dimensionless)
|
|
112
|
+
|
|
113
|
+
return ""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# /usr/bin/env python3
|
|
3
|
+
# -*- coding: utf-8 -*-
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
__coding__ = "utf-8"
|
|
8
|
+
__authors__ = ["Brian R. Pauw"] # add names to the list as appropriate
|
|
9
|
+
__copyright__ = "Copyright 2026, The MoDaCor team"
|
|
10
|
+
__date__ = "20/01/2026"
|
|
11
|
+
__status__ = "Development" # "Development", "Production"
|
|
12
|
+
# end of header and standard imports
|
|
13
|
+
|
|
14
|
+
__all__ = ["TiledSource"]
|
|
15
|
+
|
|
16
|
+
from .tiled_source import TiledSource
|