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
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# /usr/bin/env python3
|
|
3
|
+
# -*- coding: utf-8 -*-
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Tuple
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from modacor import ureg
|
|
13
|
+
from modacor.dataclasses.basedata import BaseData
|
|
14
|
+
from modacor.dataclasses.helpers import basedata_from_sources
|
|
15
|
+
from modacor.dataclasses.messagehandler import MessageHandler
|
|
16
|
+
from modacor.dataclasses.process_step import ProcessStep
|
|
17
|
+
from modacor.dataclasses.process_step_describer import ProcessStepDescriber
|
|
18
|
+
from modacor.modules.technique_modules.scattering.geometry_helpers import (
|
|
19
|
+
prepare_static_scalar,
|
|
20
|
+
require_scalar,
|
|
21
|
+
unit_vec3,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = MessageHandler(name=__name__)
|
|
25
|
+
|
|
26
|
+
__version__ = "20260106.1"
|
|
27
|
+
__all__ = ["XSGeometryFromPixelCoordinates"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class XSGeometryFromPixelCoordinates(ProcessStep):
|
|
31
|
+
"""
|
|
32
|
+
Compute scattering geometry from precomputed lab-frame pixel coordinates.
|
|
33
|
+
|
|
34
|
+
Inputs in each databundle:
|
|
35
|
+
- coord_x, coord_y, coord_z (BaseData arrays, length units)
|
|
36
|
+
|
|
37
|
+
Inputs from configuration sources:
|
|
38
|
+
- sample_z: scalar length (sample is at (0,0,sample_z))
|
|
39
|
+
- wavelength: scalar length
|
|
40
|
+
- pixel_pitch_fast, pixel_pitch_slow: scalar length/pixel (for Omega)
|
|
41
|
+
|
|
42
|
+
Outputs:
|
|
43
|
+
- Q0, Q1, Q2, Q, Psi, TwoTheta, Omega
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
documentation = ProcessStepDescriber(
|
|
47
|
+
calling_name="Add Q, Psi, TwoTheta, Omega from pixel coordinates",
|
|
48
|
+
calling_id="XSGeometryFromPixelCoordinates",
|
|
49
|
+
calling_module_path=Path(__file__),
|
|
50
|
+
calling_version=__version__,
|
|
51
|
+
required_data_keys=["coord_x", "coord_y", "coord_z"],
|
|
52
|
+
arguments={
|
|
53
|
+
"sample_z_source": {
|
|
54
|
+
"type": (str, type(None)),
|
|
55
|
+
"required": True,
|
|
56
|
+
"default": None,
|
|
57
|
+
"doc": "IoSources key for sample z-position signal.",
|
|
58
|
+
},
|
|
59
|
+
"sample_z_units_source": {
|
|
60
|
+
"type": (str, type(None)),
|
|
61
|
+
"default": None,
|
|
62
|
+
"doc": "IoSources key for sample z-position units.",
|
|
63
|
+
},
|
|
64
|
+
"sample_z_uncertainties_sources": {
|
|
65
|
+
"type": dict,
|
|
66
|
+
"default": {},
|
|
67
|
+
"doc": "Uncertainty sources for sample z-position.",
|
|
68
|
+
},
|
|
69
|
+
"wavelength_source": {
|
|
70
|
+
"type": (str, type(None)),
|
|
71
|
+
"required": True,
|
|
72
|
+
"default": None,
|
|
73
|
+
"doc": "IoSources key for wavelength signal.",
|
|
74
|
+
},
|
|
75
|
+
"wavelength_units_source": {
|
|
76
|
+
"type": (str, type(None)),
|
|
77
|
+
"default": None,
|
|
78
|
+
"doc": "IoSources key for wavelength units.",
|
|
79
|
+
},
|
|
80
|
+
"wavelength_uncertainties_sources": {
|
|
81
|
+
"type": dict,
|
|
82
|
+
"default": {},
|
|
83
|
+
"doc": "Uncertainty sources for wavelength.",
|
|
84
|
+
},
|
|
85
|
+
"pixel_pitch_slow_source": {
|
|
86
|
+
"type": (str, type(None)),
|
|
87
|
+
"required": True,
|
|
88
|
+
"default": None,
|
|
89
|
+
"doc": "IoSources key for slow-axis pixel pitch signal.",
|
|
90
|
+
},
|
|
91
|
+
"pixel_pitch_slow_units_source": {
|
|
92
|
+
"type": (str, type(None)),
|
|
93
|
+
"default": None,
|
|
94
|
+
"doc": "IoSources key for slow-axis pixel pitch units.",
|
|
95
|
+
},
|
|
96
|
+
"pixel_pitch_slow_uncertainties_sources": {
|
|
97
|
+
"type": dict,
|
|
98
|
+
"default": {},
|
|
99
|
+
"doc": "Uncertainty sources for slow-axis pixel pitch.",
|
|
100
|
+
},
|
|
101
|
+
"pixel_pitch_fast_source": {
|
|
102
|
+
"type": (str, type(None)),
|
|
103
|
+
"required": True,
|
|
104
|
+
"default": None,
|
|
105
|
+
"doc": "IoSources key for fast-axis pixel pitch signal.",
|
|
106
|
+
},
|
|
107
|
+
"pixel_pitch_fast_units_source": {
|
|
108
|
+
"type": (str, type(None)),
|
|
109
|
+
"default": None,
|
|
110
|
+
"doc": "IoSources key for fast-axis pixel pitch units.",
|
|
111
|
+
},
|
|
112
|
+
"pixel_pitch_fast_uncertainties_sources": {
|
|
113
|
+
"type": dict,
|
|
114
|
+
"default": {},
|
|
115
|
+
"doc": "Uncertainty sources for fast-axis pixel pitch.",
|
|
116
|
+
},
|
|
117
|
+
"detector_normal": {
|
|
118
|
+
"type": tuple,
|
|
119
|
+
"default": (0.0, 0.0, 1.0),
|
|
120
|
+
"doc": "Detector normal unit vector in lab frame.",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
modifies={
|
|
124
|
+
"Q0": ["signal", "uncertainties"],
|
|
125
|
+
"Q1": ["signal", "uncertainties"],
|
|
126
|
+
"Q2": ["signal", "uncertainties"],
|
|
127
|
+
"Q": ["signal", "uncertainties"],
|
|
128
|
+
"Psi": ["signal"], # computed from nominal x/y only
|
|
129
|
+
"TwoTheta": ["signal", "uncertainties"],
|
|
130
|
+
"Omega": ["signal", "uncertainties"],
|
|
131
|
+
},
|
|
132
|
+
step_keywords=["geometry", "Q", "Psi", "TwoTheta", "Solid Angle", "Omega", "scattering"],
|
|
133
|
+
step_doc="Compute Q-vector components and angles from lab-frame pixel coordinates.",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
output_keys: Tuple[str, ...] = ("Q0", "Q1", "Q2", "Q", "Psi", "TwoTheta", "Omega")
|
|
137
|
+
|
|
138
|
+
# ----------------------------
|
|
139
|
+
# loading helpers
|
|
140
|
+
# ----------------------------
|
|
141
|
+
|
|
142
|
+
def _load_from_sources(self, key: str) -> BaseData:
|
|
143
|
+
return basedata_from_sources(
|
|
144
|
+
io_sources=self.io_sources,
|
|
145
|
+
signal_source=self.configuration.get(f"{key}_source"),
|
|
146
|
+
units_source=self.configuration.get(f"{key}_units_source", None),
|
|
147
|
+
uncertainty_sources=self.configuration.get(f"{key}_uncertainties_sources", {}),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# ----------------------------
|
|
151
|
+
# core compute
|
|
152
|
+
# ----------------------------
|
|
153
|
+
|
|
154
|
+
def _compute(
|
|
155
|
+
self,
|
|
156
|
+
*,
|
|
157
|
+
coord_x: BaseData,
|
|
158
|
+
coord_y: BaseData,
|
|
159
|
+
coord_z: BaseData,
|
|
160
|
+
sample_z: BaseData,
|
|
161
|
+
wavelength: BaseData,
|
|
162
|
+
pitch_slow: BaseData,
|
|
163
|
+
pitch_fast: BaseData,
|
|
164
|
+
detector_normal: np.ndarray,
|
|
165
|
+
) -> Dict[str, BaseData]:
|
|
166
|
+
# sample position is (0,0,sample_z)
|
|
167
|
+
dz = coord_z - sample_z
|
|
168
|
+
dx = coord_x
|
|
169
|
+
dy = coord_y
|
|
170
|
+
|
|
171
|
+
# ray length
|
|
172
|
+
R = ((dx**2) + (dy**2) + (dz**2)).sqrt()
|
|
173
|
+
|
|
174
|
+
# angles
|
|
175
|
+
r_perp = ((dx**2) + (dy**2)).sqrt()
|
|
176
|
+
TwoTheta = (r_perp / dz).arctan() # radians
|
|
177
|
+
|
|
178
|
+
# k = 2π/λ
|
|
179
|
+
two_pi = float(2.0 * np.pi)
|
|
180
|
+
k = two_pi / wavelength # 1/length
|
|
181
|
+
|
|
182
|
+
# unit direction to pixel
|
|
183
|
+
rhat_x = dx / R
|
|
184
|
+
rhat_y = dy / R
|
|
185
|
+
rhat_z = dz / R
|
|
186
|
+
|
|
187
|
+
# q = k_out - k_in, with k_in along +z: (0,0,k)
|
|
188
|
+
Q0 = k * rhat_x
|
|
189
|
+
Q1 = k * rhat_y
|
|
190
|
+
Q2 = k * (rhat_z - 1.0)
|
|
191
|
+
|
|
192
|
+
Q = ((Q0**2) + (Q1**2) + (Q2**2)).sqrt()
|
|
193
|
+
|
|
194
|
+
# Psi from NOMINAL geometry only (matches your earlier approach)
|
|
195
|
+
psi_signal = np.arctan2(dy.signal, dx.signal)
|
|
196
|
+
Psi = BaseData(signal=psi_signal, units=ureg.radian)
|
|
197
|
+
|
|
198
|
+
# Solid angle per pixel:
|
|
199
|
+
# dΩ ≈ A * cos(alpha) / R^2, with cos(alpha)=n·rhat
|
|
200
|
+
# Here A from pitches (length/pixel)×(length/pixel).
|
|
201
|
+
n = detector_normal
|
|
202
|
+
cos_alpha = (rhat_x * n[0]) + (rhat_y * n[1]) + (rhat_z * n[2])
|
|
203
|
+
cos_alpha_clipped = cos_alpha.copy()
|
|
204
|
+
cos_alpha_clipped.signal = np.clip(cos_alpha.signal, 0.0, None)
|
|
205
|
+
cos_alpha = cos_alpha_clipped
|
|
206
|
+
|
|
207
|
+
one_px = BaseData(signal=np.array(1.0, dtype=float), units=ureg.pixel, rank_of_data=0)
|
|
208
|
+
area_pixel = (pitch_fast * one_px) * (pitch_slow * one_px) # -> m^2
|
|
209
|
+
Omega = (area_pixel * cos_alpha) / (R**2)
|
|
210
|
+
Omega.units = ureg.steradian # (steradian is dimensionless, but explicit is fine)
|
|
211
|
+
|
|
212
|
+
return {"Q0": Q0, "Q1": Q1, "Q2": Q2, "Q": Q, "Psi": Psi, "TwoTheta": TwoTheta, "Omega": Omega}
|
|
213
|
+
|
|
214
|
+
# ----------------------------
|
|
215
|
+
# ProcessStep lifecycle
|
|
216
|
+
# ----------------------------
|
|
217
|
+
|
|
218
|
+
def prepare_execution(self):
|
|
219
|
+
super().prepare_execution()
|
|
220
|
+
|
|
221
|
+
with_keys = self.configuration.get("with_processing_keys") or []
|
|
222
|
+
if not with_keys:
|
|
223
|
+
raise ValueError("XSGeometryFromPixelCoordinates: configuration.with_processing_keys is empty.")
|
|
224
|
+
|
|
225
|
+
# reference bundle
|
|
226
|
+
ref = self.processing_data[with_keys[0]]
|
|
227
|
+
coord_x: BaseData = ref["coord_x"]
|
|
228
|
+
coord_y: BaseData = ref["coord_y"]
|
|
229
|
+
coord_z: BaseData = ref["coord_z"]
|
|
230
|
+
|
|
231
|
+
RoD = int(
|
|
232
|
+
getattr(coord_x, "rank_of_data", ref["signal"].rank_of_data if "signal" in ref else np.ndim(coord_x.signal))
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
sample_z = prepare_static_scalar(
|
|
236
|
+
self._load_from_sources("sample_z"), require_units=ureg.m, uncertainty_key="sample_position_jitter"
|
|
237
|
+
)
|
|
238
|
+
wavelength = prepare_static_scalar(
|
|
239
|
+
self._load_from_sources("wavelength"), require_units=ureg.m, uncertainty_key="wavelength_jitter"
|
|
240
|
+
)
|
|
241
|
+
pitch_slow = prepare_static_scalar(
|
|
242
|
+
self._load_from_sources("pixel_pitch_slow"),
|
|
243
|
+
require_units=ureg.m / ureg.pixel,
|
|
244
|
+
uncertainty_key="pixel_pitch_jitter",
|
|
245
|
+
)
|
|
246
|
+
pitch_fast = prepare_static_scalar(
|
|
247
|
+
self._load_from_sources("pixel_pitch_fast"),
|
|
248
|
+
require_units=ureg.m / ureg.pixel,
|
|
249
|
+
uncertainty_key="pixel_pitch_jitter",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
detector_normal = unit_vec3(self.configuration.get("detector_normal", (0.0, 0.0, 1.0)), name="detector_normal")
|
|
253
|
+
|
|
254
|
+
# (optional) enforce scalar-ness right before compute:
|
|
255
|
+
sample_z = require_scalar("sample_z", sample_z)
|
|
256
|
+
wavelength = require_scalar("wavelength", wavelength)
|
|
257
|
+
pitch_slow = require_scalar("pixel_pitch_slow", pitch_slow)
|
|
258
|
+
pitch_fast = require_scalar("pixel_pitch_fast", pitch_fast)
|
|
259
|
+
|
|
260
|
+
out = self._compute(
|
|
261
|
+
coord_x=coord_x,
|
|
262
|
+
coord_y=coord_y,
|
|
263
|
+
coord_z=coord_z,
|
|
264
|
+
sample_z=sample_z,
|
|
265
|
+
wavelength=wavelength,
|
|
266
|
+
pitch_slow=pitch_slow,
|
|
267
|
+
pitch_fast=pitch_fast,
|
|
268
|
+
detector_normal=detector_normal,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
for bd in out.values():
|
|
272
|
+
bd.rank_of_data = min(RoD, int(np.ndim(bd.signal)))
|
|
273
|
+
|
|
274
|
+
self._prepared_data = {k: out[k] for k in self.output_keys}
|
|
275
|
+
|
|
276
|
+
def calculate(self):
|
|
277
|
+
with_keys = self.configuration.get("with_processing_keys") or []
|
|
278
|
+
if not with_keys:
|
|
279
|
+
logger.warning("XSGeometryFromPixelCoordinates: no with_processing_keys specified; nothing to do.")
|
|
280
|
+
return {}
|
|
281
|
+
|
|
282
|
+
out: Dict[str, object] = {}
|
|
283
|
+
for key in with_keys:
|
|
284
|
+
bundle = self.processing_data.get(key)
|
|
285
|
+
if bundle is None:
|
|
286
|
+
logger.warning(
|
|
287
|
+
f"XSGeometryFromPixelCoordinates: no processing_data entry for key={key!r}; skipping." # noqa: E702
|
|
288
|
+
)
|
|
289
|
+
continue
|
|
290
|
+
for out_key, bd in self._prepared_data.items():
|
|
291
|
+
bundle[out_key] = bd
|
|
292
|
+
out[key] = bundle
|
|
293
|
+
return out
|
|
File without changes
|