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.
Files changed (120) hide show
  1. modacor/__init__.py +30 -0
  2. modacor/dataclasses/__init__.py +0 -0
  3. modacor/dataclasses/basedata.py +973 -0
  4. modacor/dataclasses/databundle.py +23 -0
  5. modacor/dataclasses/helpers.py +45 -0
  6. modacor/dataclasses/messagehandler.py +75 -0
  7. modacor/dataclasses/process_step.py +233 -0
  8. modacor/dataclasses/process_step_describer.py +146 -0
  9. modacor/dataclasses/processing_data.py +59 -0
  10. modacor/dataclasses/trace_event.py +118 -0
  11. modacor/dataclasses/uncertainty_tools.py +132 -0
  12. modacor/dataclasses/validators.py +84 -0
  13. modacor/debug/pipeline_tracer.py +548 -0
  14. modacor/io/__init__.py +33 -0
  15. modacor/io/csv/__init__.py +0 -0
  16. modacor/io/csv/csv_sink.py +114 -0
  17. modacor/io/csv/csv_source.py +210 -0
  18. modacor/io/hdf/__init__.py +27 -0
  19. modacor/io/hdf/hdf_source.py +120 -0
  20. modacor/io/io_sink.py +41 -0
  21. modacor/io/io_sinks.py +61 -0
  22. modacor/io/io_source.py +164 -0
  23. modacor/io/io_sources.py +208 -0
  24. modacor/io/processing_path.py +113 -0
  25. modacor/io/tiled/__init__.py +16 -0
  26. modacor/io/tiled/tiled_source.py +403 -0
  27. modacor/io/yaml/__init__.py +27 -0
  28. modacor/io/yaml/yaml_source.py +116 -0
  29. modacor/modules/__init__.py +53 -0
  30. modacor/modules/base_modules/__init__.py +0 -0
  31. modacor/modules/base_modules/append_processing_data.py +329 -0
  32. modacor/modules/base_modules/append_sink.py +141 -0
  33. modacor/modules/base_modules/append_source.py +181 -0
  34. modacor/modules/base_modules/bitwise_or_masks.py +113 -0
  35. modacor/modules/base_modules/combine_uncertainties.py +120 -0
  36. modacor/modules/base_modules/combine_uncertainties_max.py +105 -0
  37. modacor/modules/base_modules/divide.py +82 -0
  38. modacor/modules/base_modules/find_scale_factor1d.py +373 -0
  39. modacor/modules/base_modules/multiply.py +77 -0
  40. modacor/modules/base_modules/multiply_databundles.py +73 -0
  41. modacor/modules/base_modules/poisson_uncertainties.py +69 -0
  42. modacor/modules/base_modules/reduce_dimensionality.py +252 -0
  43. modacor/modules/base_modules/sink_processing_data.py +80 -0
  44. modacor/modules/base_modules/subtract.py +80 -0
  45. modacor/modules/base_modules/subtract_databundles.py +67 -0
  46. modacor/modules/base_modules/units_label_update.py +66 -0
  47. modacor/modules/instrument_modules/__init__.py +0 -0
  48. modacor/modules/instrument_modules/readme.md +9 -0
  49. modacor/modules/technique_modules/__init__.py +0 -0
  50. modacor/modules/technique_modules/scattering/__init__.py +0 -0
  51. modacor/modules/technique_modules/scattering/geometry_helpers.py +114 -0
  52. modacor/modules/technique_modules/scattering/index_pixels.py +492 -0
  53. modacor/modules/technique_modules/scattering/indexed_averager.py +628 -0
  54. modacor/modules/technique_modules/scattering/pixel_coordinates_3d.py +417 -0
  55. modacor/modules/technique_modules/scattering/solid_angle_correction.py +63 -0
  56. modacor/modules/technique_modules/scattering/xs_geometry.py +571 -0
  57. modacor/modules/technique_modules/scattering/xs_geometry_from_pixel_coordinates.py +293 -0
  58. modacor/runner/__init__.py +0 -0
  59. modacor/runner/pipeline.py +749 -0
  60. modacor/runner/process_step_registry.py +224 -0
  61. modacor/tests/__init__.py +27 -0
  62. modacor/tests/dataclasses/test_basedata.py +519 -0
  63. modacor/tests/dataclasses/test_basedata_operations.py +439 -0
  64. modacor/tests/dataclasses/test_basedata_to_base_units.py +57 -0
  65. modacor/tests/dataclasses/test_process_step_describer.py +73 -0
  66. modacor/tests/dataclasses/test_processstep.py +282 -0
  67. modacor/tests/debug/test_tracing_integration.py +188 -0
  68. modacor/tests/integration/__init__.py +0 -0
  69. modacor/tests/integration/test_pipeline_run.py +238 -0
  70. modacor/tests/io/__init__.py +27 -0
  71. modacor/tests/io/csv/__init__.py +0 -0
  72. modacor/tests/io/csv/test_csv_source.py +156 -0
  73. modacor/tests/io/hdf/__init__.py +27 -0
  74. modacor/tests/io/hdf/test_hdf_source.py +92 -0
  75. modacor/tests/io/test_io_sources.py +119 -0
  76. modacor/tests/io/tiled/__init__.py +12 -0
  77. modacor/tests/io/tiled/test_tiled_source.py +120 -0
  78. modacor/tests/io/yaml/__init__.py +27 -0
  79. modacor/tests/io/yaml/static_data_example.yaml +26 -0
  80. modacor/tests/io/yaml/test_yaml_source.py +47 -0
  81. modacor/tests/modules/__init__.py +27 -0
  82. modacor/tests/modules/base_modules/__init__.py +27 -0
  83. modacor/tests/modules/base_modules/test_append_processing_data.py +219 -0
  84. modacor/tests/modules/base_modules/test_append_sink.py +76 -0
  85. modacor/tests/modules/base_modules/test_append_source.py +180 -0
  86. modacor/tests/modules/base_modules/test_bitwise_or_masks.py +264 -0
  87. modacor/tests/modules/base_modules/test_combine_uncertainties.py +105 -0
  88. modacor/tests/modules/base_modules/test_combine_uncertainties_max.py +109 -0
  89. modacor/tests/modules/base_modules/test_divide.py +140 -0
  90. modacor/tests/modules/base_modules/test_find_scale_factor1d.py +220 -0
  91. modacor/tests/modules/base_modules/test_multiply.py +113 -0
  92. modacor/tests/modules/base_modules/test_multiply_databundles.py +136 -0
  93. modacor/tests/modules/base_modules/test_poisson_uncertainties.py +61 -0
  94. modacor/tests/modules/base_modules/test_reduce_dimensionality.py +358 -0
  95. modacor/tests/modules/base_modules/test_sink_processing_data.py +119 -0
  96. modacor/tests/modules/base_modules/test_subtract.py +111 -0
  97. modacor/tests/modules/base_modules/test_subtract_databundles.py +136 -0
  98. modacor/tests/modules/base_modules/test_units_label_update.py +91 -0
  99. modacor/tests/modules/technique_modules/__init__.py +0 -0
  100. modacor/tests/modules/technique_modules/scattering/__init__.py +0 -0
  101. modacor/tests/modules/technique_modules/scattering/test_geometry_helpers.py +198 -0
  102. modacor/tests/modules/technique_modules/scattering/test_index_pixels.py +426 -0
  103. modacor/tests/modules/technique_modules/scattering/test_indexed_averaging.py +559 -0
  104. modacor/tests/modules/technique_modules/scattering/test_pixel_coordinates_3d.py +282 -0
  105. modacor/tests/modules/technique_modules/scattering/test_xs_geometry_from_pixel_coordinates.py +224 -0
  106. modacor/tests/modules/technique_modules/scattering/test_xsgeometry.py +635 -0
  107. modacor/tests/requirements.txt +12 -0
  108. modacor/tests/runner/test_pipeline.py +438 -0
  109. modacor/tests/runner/test_process_step_registry.py +65 -0
  110. modacor/tests/test_import.py +43 -0
  111. modacor/tests/test_modacor.py +17 -0
  112. modacor/tests/test_units.py +79 -0
  113. modacor/units.py +97 -0
  114. modacor-1.0.0.dist-info/METADATA +482 -0
  115. modacor-1.0.0.dist-info/RECORD +120 -0
  116. modacor-1.0.0.dist-info/WHEEL +5 -0
  117. modacor-1.0.0.dist-info/licenses/AUTHORS.md +11 -0
  118. modacor-1.0.0.dist-info/licenses/LICENSE +11 -0
  119. modacor-1.0.0.dist-info/licenses/LICENSE.txt +11 -0
  120. modacor-1.0.0.dist-info/top_level.txt +1 -0
@@ -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