quantized-lab 0.8.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 (233) hide show
  1. quantized/__init__.py +11 -0
  2. quantized/__main__.py +14 -0
  3. quantized/api.py +220 -0
  4. quantized/app.py +206 -0
  5. quantized/calc/__init__.py +8 -0
  6. quantized/calc/_clipfit.py +76 -0
  7. quantized/calc/_natural_neighbor.py +219 -0
  8. quantized/calc/aggregate.py +159 -0
  9. quantized/calc/backgrounds.py +353 -0
  10. quantized/calc/baseline.py +349 -0
  11. quantized/calc/batch_fit.py +148 -0
  12. quantized/calc/constants.py +27 -0
  13. quantized/calc/corrections.py +192 -0
  14. quantized/calc/crystallography.py +400 -0
  15. quantized/calc/diffusion.py +120 -0
  16. quantized/calc/electrical.py +248 -0
  17. quantized/calc/electrochemistry.py +176 -0
  18. quantized/calc/element_data.json +1 -0
  19. quantized/calc/element_data.py +59 -0
  20. quantized/calc/errors.py +246 -0
  21. quantized/calc/figure.py +417 -0
  22. quantized/calc/figure_break.py +152 -0
  23. quantized/calc/figure_categorical.py +156 -0
  24. quantized/calc/figure_corner.py +229 -0
  25. quantized/calc/figure_facets.py +127 -0
  26. quantized/calc/figure_field.py +137 -0
  27. quantized/calc/figure_hitmap.py +116 -0
  28. quantized/calc/figure_labels.py +62 -0
  29. quantized/calc/figure_map.py +287 -0
  30. quantized/calc/figure_overrides.py +159 -0
  31. quantized/calc/figure_page.py +266 -0
  32. quantized/calc/figure_scale.py +125 -0
  33. quantized/calc/figure_statplots.py +167 -0
  34. quantized/calc/figure_styles.py +131 -0
  35. quantized/calc/figure_ternary.py +239 -0
  36. quantized/calc/figure_ticks.py +217 -0
  37. quantized/calc/fit_autoguess.py +156 -0
  38. quantized/calc/fit_bootstrap.py +163 -0
  39. quantized/calc/fit_bumps.py +258 -0
  40. quantized/calc/fit_constraints.py +114 -0
  41. quantized/calc/fit_equation.py +264 -0
  42. quantized/calc/fit_findxy.py +80 -0
  43. quantized/calc/fit_models.py +195 -0
  44. quantized/calc/fit_models_special.py +189 -0
  45. quantized/calc/fit_odr.py +99 -0
  46. quantized/calc/fit_scan.py +243 -0
  47. quantized/calc/fit_stats.py +215 -0
  48. quantized/calc/fitting.py +199 -0
  49. quantized/calc/formula.py +90 -0
  50. quantized/calc/global_curve_fit.py +305 -0
  51. quantized/calc/global_fit.py +181 -0
  52. quantized/calc/interp2d.py +260 -0
  53. quantized/calc/linecut.py +269 -0
  54. quantized/calc/magnetic.py +414 -0
  55. quantized/calc/magnetometry.py +464 -0
  56. quantized/calc/map.py +228 -0
  57. quantized/calc/mcmc.py +177 -0
  58. quantized/calc/optics.py +228 -0
  59. quantized/calc/pawley.py +251 -0
  60. quantized/calc/peak_batch.py +128 -0
  61. quantized/calc/peak_fit.py +259 -0
  62. quantized/calc/peak_integrate.py +104 -0
  63. quantized/calc/peak_multifit.py +260 -0
  64. quantized/calc/peak_track.py +134 -0
  65. quantized/calc/peaks.py +298 -0
  66. quantized/calc/peakshapes.py +85 -0
  67. quantized/calc/plotting.py +147 -0
  68. quantized/calc/processing.py +232 -0
  69. quantized/calc/qspace.py +48 -0
  70. quantized/calc/reductions.py +155 -0
  71. quantized/calc/reductions_fft.py +383 -0
  72. quantized/calc/refl_sld_presets.json +1 -0
  73. quantized/calc/reflectivity.py +80 -0
  74. quantized/calc/registry.py +303 -0
  75. quantized/calc/relaxation.py +119 -0
  76. quantized/calc/report.py +253 -0
  77. quantized/calc/report_emit.py +227 -0
  78. quantized/calc/resample.py +142 -0
  79. quantized/calc/rsm.py +91 -0
  80. quantized/calc/rsm_analyze.py +245 -0
  81. quantized/calc/semiconductor.py +488 -0
  82. quantized/calc/sld.py +131 -0
  83. quantized/calc/sld_formula.py +138 -0
  84. quantized/calc/spectral.py +357 -0
  85. quantized/calc/statplots.py +214 -0
  86. quantized/calc/stats.py +399 -0
  87. quantized/calc/stats_anova2.py +202 -0
  88. quantized/calc/stats_anova_ext.py +338 -0
  89. quantized/calc/stats_dist.py +196 -0
  90. quantized/calc/stats_glm.py +245 -0
  91. quantized/calc/stats_multivar.py +289 -0
  92. quantized/calc/stats_roc.py +157 -0
  93. quantized/calc/stats_survival.py +261 -0
  94. quantized/calc/stats_tests.py +380 -0
  95. quantized/calc/substrates.py +181 -0
  96. quantized/calc/superconductor.py +359 -0
  97. quantized/calc/surface_fit.py +290 -0
  98. quantized/calc/surface_models.py +156 -0
  99. quantized/calc/thermal.py +119 -0
  100. quantized/calc/thin_film.py +425 -0
  101. quantized/calc/unit_convert.py +259 -0
  102. quantized/calc/units.py +80 -0
  103. quantized/calc/vacuum.py +290 -0
  104. quantized/calc/xray.py +169 -0
  105. quantized/cli.py +214 -0
  106. quantized/datastruct.py +153 -0
  107. quantized/io/__init__.py +11 -0
  108. quantized/io/_hdf5_layout.py +308 -0
  109. quantized/io/_jcamp_asdf.py +135 -0
  110. quantized/io/_xrdml_scan.py +291 -0
  111. quantized/io/base.py +82 -0
  112. quantized/io/bruker_brml.py +177 -0
  113. quantized/io/bruker_raw.py +158 -0
  114. quantized/io/cif.py +266 -0
  115. quantized/io/consolidated.py +122 -0
  116. quantized/io/delimited.py +222 -0
  117. quantized/io/excel.py +135 -0
  118. quantized/io/hdf5.py +192 -0
  119. quantized/io/import_filters.py +178 -0
  120. quantized/io/import_preview.py +262 -0
  121. quantized/io/jcamp.py +179 -0
  122. quantized/io/lakeshore.py +163 -0
  123. quantized/io/ncnr.py +278 -0
  124. quantized/io/netcdf.py +195 -0
  125. quantized/io/opus.py +231 -0
  126. quantized/io/origin.py +346 -0
  127. quantized/io/origin_com.py +194 -0
  128. quantized/io/origin_project/__init__.py +221 -0
  129. quantized/io/origin_project/annotation_marks.py +288 -0
  130. quantized/io/origin_project/container.py +262 -0
  131. quantized/io/origin_project/curve_style_color.py +359 -0
  132. quantized/io/origin_project/figure_geometry.py +108 -0
  133. quantized/io/origin_project/figure_layers.py +333 -0
  134. quantized/io/origin_project/figure_text.py +258 -0
  135. quantized/io/origin_project/figures.py +210 -0
  136. quantized/io/origin_project/figures_opju.py +440 -0
  137. quantized/io/origin_project/notes.py +302 -0
  138. quantized/io/origin_project/opj.py +459 -0
  139. quantized/io/origin_project/opj_curves.py +297 -0
  140. quantized/io/origin_project/opj_shapes.py +148 -0
  141. quantized/io/origin_project/opju.py +146 -0
  142. quantized/io/origin_project/opju_axis_real_form.py +418 -0
  143. quantized/io/origin_project/opju_axis_specimen_form.py +167 -0
  144. quantized/io/origin_project/opju_codec.py +370 -0
  145. quantized/io/origin_project/opju_curves.py +497 -0
  146. quantized/io/origin_project/opju_curves_allcols.py +258 -0
  147. quantized/io/origin_project/opju_figure_curves.py +302 -0
  148. quantized/io/origin_project/opju_figure_text.py +245 -0
  149. quantized/io/origin_project/opju_reports.py +129 -0
  150. quantized/io/origin_project/origin_richtext.py +145 -0
  151. quantized/io/origin_project/preview.py +132 -0
  152. quantized/io/origin_project/templates.py +314 -0
  153. quantized/io/origin_project/tree.py +379 -0
  154. quantized/io/origin_project/tree_opju.py +228 -0
  155. quantized/io/origin_project/windows.py +238 -0
  156. quantized/io/origin_project/windows_opju.py +393 -0
  157. quantized/io/origin_project/writer.py +156 -0
  158. quantized/io/origin_project/writer_blocks.py +282 -0
  159. quantized/io/qd.py +380 -0
  160. quantized/io/refl1d.py +132 -0
  161. quantized/io/registry.py +210 -0
  162. quantized/io/report_export.py +347 -0
  163. quantized/io/rigaku.py +100 -0
  164. quantized/io/sims.py +398 -0
  165. quantized/io/spc.py +311 -0
  166. quantized/io/xrd_csv.py +308 -0
  167. quantized/io/xrdml.py +394 -0
  168. quantized/jobs.py +173 -0
  169. quantized/plugins/__init__.py +50 -0
  170. quantized/plugins/contract.py +111 -0
  171. quantized/plugins/loader.py +394 -0
  172. quantized/plugins/steps.py +90 -0
  173. quantized/routes/__init__.py +7 -0
  174. quantized/routes/_bookcache.py +62 -0
  175. quantized/routes/_export_common.py +27 -0
  176. quantized/routes/_payload.py +58 -0
  177. quantized/routes/_uploadcache.py +59 -0
  178. quantized/routes/aggregate.py +46 -0
  179. quantized/routes/baseline.py +210 -0
  180. quantized/routes/books.py +117 -0
  181. quantized/routes/calc.py +56 -0
  182. quantized/routes/corrections.py +78 -0
  183. quantized/routes/crystallography.py +80 -0
  184. quantized/routes/diffusion.py +58 -0
  185. quantized/routes/electrical.py +101 -0
  186. quantized/routes/electrochemistry.py +83 -0
  187. quantized/routes/export.py +280 -0
  188. quantized/routes/export_facets.py +83 -0
  189. quantized/routes/export_figures.py +471 -0
  190. quantized/routes/export_page.py +125 -0
  191. quantized/routes/fitting.py +379 -0
  192. quantized/routes/fitting_bumps.py +97 -0
  193. quantized/routes/import_template.py +97 -0
  194. quantized/routes/import_wizard.py +150 -0
  195. quantized/routes/jobs_api.py +59 -0
  196. quantized/routes/magnetic.py +135 -0
  197. quantized/routes/magnetometry.py +133 -0
  198. quantized/routes/optics.py +98 -0
  199. quantized/routes/parsers.py +281 -0
  200. quantized/routes/peaks.py +184 -0
  201. quantized/routes/plot.py +103 -0
  202. quantized/routes/reductions.py +121 -0
  203. quantized/routes/reference.py +58 -0
  204. quantized/routes/reflectivity.py +91 -0
  205. quantized/routes/report_export.py +119 -0
  206. quantized/routes/rsm.py +136 -0
  207. quantized/routes/samples.py +32 -0
  208. quantized/routes/semiconductor.py +207 -0
  209. quantized/routes/sld.py +42 -0
  210. quantized/routes/spectral.py +54 -0
  211. quantized/routes/statplots.py +99 -0
  212. quantized/routes/stats.py +418 -0
  213. quantized/routes/stats_design.py +321 -0
  214. quantized/routes/substrates.py +46 -0
  215. quantized/routes/superconductor.py +139 -0
  216. quantized/routes/thermal.py +57 -0
  217. quantized/routes/thin_film.py +153 -0
  218. quantized/routes/vacuum.py +113 -0
  219. quantized/routes/xray.py +32 -0
  220. quantized/samples/demo_vsm.csv +42 -0
  221. quantized/server_launch.py +251 -0
  222. quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
  223. quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
  224. quantized/web/assets/index-BHmmCL-x.js +27 -0
  225. quantized/web/assets/index-BiZzN7J6.css +1 -0
  226. quantized/web/index.html +13 -0
  227. quantized/web/loading.html +69 -0
  228. quantized_lab-0.8.0.dist-info/METADATA +122 -0
  229. quantized_lab-0.8.0.dist-info/RECORD +233 -0
  230. quantized_lab-0.8.0.dist-info/WHEEL +4 -0
  231. quantized_lab-0.8.0.dist-info/entry_points.txt +4 -0
  232. quantized_lab-0.8.0.dist-info/licenses/LICENSE +201 -0
  233. quantized_lab-0.8.0.dist-info/licenses/NOTICE +11 -0
@@ -0,0 +1,245 @@
1
+ """RSM peak extraction. Port of MATLAB ``fitting.rsmAnalyze``.
2
+
3
+ Finds the brightest peaks in a reciprocal-space map and fits each to a 2D peak
4
+ model, returning centres / FWHM in angle space (2theta, omega) and — when the
5
+ map carries Q-space grids — in reciprocal space (Qx, Qz). The two brightest are
6
+ labelled ``substrate`` / ``film`` so the result feeds ``calc/rsm.rsm_strain``.
7
+
8
+ Pipeline (matches the MATLAB method):
9
+ 1. Separable Gaussian smooth to suppress single-pixel noise.
10
+ 2. 3x3 non-max suppression above ``threshold * max`` with a greedy
11
+ min-separation filter, brightest first.
12
+ 3. Per peak: extract a ``fit_window`` patch and fit it with
13
+ ``calc/surface_fit`` (angle grids; refit on the Q grids when present).
14
+ 4. FWHM from the fit (Gaussian sigma -> 2.355 sigma; Lorentzian w -> 2w;
15
+ Pseudo-Voigt -> eta-weighted blend).
16
+
17
+ Pure calc layer — ndarray/DataStruct in -> dict out; no fastapi/pydantic.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import sys
23
+ from typing import Any
24
+
25
+ import numpy as np
26
+ from numpy.typing import ArrayLike, NDArray
27
+ from scipy.signal import convolve2d
28
+
29
+ from quantized.calc.surface_fit import surface_fit
30
+ from quantized.datastruct import DataStruct
31
+
32
+ __all__ = ["rsm_analyze", "rsm_grids_from_datastruct"]
33
+
34
+ _EPS = sys.float_info.epsilon
35
+ _FIT_MODELS = ("2D Gaussian", "2D Lorentzian", "2D Pseudo-Voigt")
36
+
37
+
38
+ def _gaussian_smooth_2d(img: NDArray[np.float64], sigma: float) -> NDArray[np.float64]:
39
+ """Separable 1D Gaussian smoothing (MATLAB ``conv2(k, k, I, 'same')``)."""
40
+ r = max(1, int(np.ceil(3 * sigma)))
41
+ x = np.arange(-r, r + 1, dtype=float)
42
+ k = np.exp(-(x**2) / (2 * sigma**2))
43
+ k = k / k.sum()
44
+ kernel = np.outer(k, k)
45
+ out = convolve2d(img, kernel, mode="same", boundary="fill", fillvalue=0.0)
46
+ return np.asarray(out, dtype=float)
47
+
48
+
49
+ def _find_local_maxima(
50
+ img: NDArray[np.float64], thresh: float, min_sep: int
51
+ ) -> list[tuple[int, int]]:
52
+ """3x3 non-max suppression (interior cells) + greedy min-separation, brightest first."""
53
+ n, m = img.shape
54
+ cands: list[tuple[float, int, int]] = []
55
+ for r in range(1, n - 1):
56
+ for c in range(1, m - 1):
57
+ v = float(img[r, c])
58
+ if v < thresh:
59
+ continue
60
+ if v >= float(img[r - 1 : r + 2, c - 1 : c + 2].max()): # >= so plateaus give one peak
61
+ cands.append((v, r, c))
62
+ cands.sort(key=lambda t: -t[0]) # brightest first
63
+
64
+ kept: list[tuple[int, int]] = []
65
+ for _, r, c in cands:
66
+ if all((r - rk) ** 2 + (c - ck) ** 2 >= min_sep**2 for rk, ck in kept):
67
+ kept.append((r, c))
68
+ return kept
69
+
70
+
71
+ def _patch_indices(shape: tuple[int, int], rc: int, cc: int, half: int) -> tuple[range, range]:
72
+ n, m = shape
73
+ rows = range(max(0, rc - half), min(n, rc + half + 1))
74
+ cols = range(max(0, cc - half), min(m, cc + half + 1))
75
+ return rows, cols
76
+
77
+
78
+ def _try_fit(
79
+ model: str, xg: NDArray[np.float64], yg: NDArray[np.float64], zg: NDArray[np.float64]
80
+ ) -> dict[str, Any] | None:
81
+ """Fit a patch with patch-scoped guess + bounds (port of MATLAB ``tryFit``)."""
82
+ x = xg.ravel()
83
+ y = yg.ravel()
84
+ z = zg.ravel()
85
+ idx = int(np.argmax(z))
86
+ amp, bg = float(z.max()), float(z.min())
87
+ a0 = max(amp - bg, _EPS)
88
+ x0, y0 = float(x[idx]), float(y[idx])
89
+ x_min, x_max = float(x.min()), float(x.max())
90
+ y_min, y_max = float(y.min()), float(y.max())
91
+ x_rng = max(x_max - x_min, _EPS)
92
+ y_rng = max(y_max - y_min, _EPS)
93
+ sx0, sy0 = x_rng / 6, y_rng / 6
94
+ z_span = max(amp - bg, _EPS)
95
+
96
+ p0 = [a0, x0, sx0, y0, sy0, bg]
97
+ lb = [0.0, x_min, x_rng / 100, y_min, y_rng / 100, bg - 10 * z_span]
98
+ ub = [10 * a0, x_max, 2 * x_rng, y_max, 2 * y_rng, bg + 10 * z_span]
99
+ if model == "2D Pseudo-Voigt":
100
+ p0, lb, ub = [*p0, 0.5], [*lb, 0.0], [*ub, 1.0]
101
+ try:
102
+ return surface_fit(x, y, z, model, p0=p0, lower=lb, upper=ub)
103
+ except (ValueError, np.linalg.LinAlgError):
104
+ return None
105
+
106
+
107
+ _FWHM_K = 2 * np.sqrt(2 * np.log(2)) # sigma -> FWHM
108
+
109
+
110
+ def _unpack_fit(fit: dict[str, Any], model: str) -> tuple[float, float, float, float, float, float]:
111
+ """-> (cx, cy, fwx, fwy, amp, bg) with model-specific FWHM."""
112
+ p = fit["params"]
113
+ amp, cx, cy, bg = float(p[0]), float(p[1]), float(p[3]), float(p[5])
114
+ wx, wy = abs(float(p[2])), abs(float(p[4]))
115
+ if model == "2D Gaussian":
116
+ return cx, cy, _FWHM_K * wx, _FWHM_K * wy, amp, bg
117
+ if model == "2D Lorentzian":
118
+ return cx, cy, 2 * wx, 2 * wy, amp, bg
119
+ # Pseudo-Voigt: eta-weighted blend of Lorentzian (2w) and Gaussian (k·w).
120
+ eta = min(max(float(p[6]), 0.0), 1.0)
121
+ fwx = eta * (2 * wx) + (1 - eta) * (_FWHM_K * wx)
122
+ fwy = eta * (2 * wy) + (1 - eta) * (_FWHM_K * wy)
123
+ return cx, cy, fwx, fwy, amp, bg
124
+
125
+
126
+ def _classify(rank: int, n_requested: int) -> str:
127
+ if n_requested >= 2 and rank == 1:
128
+ return "substrate"
129
+ if n_requested >= 2 and rank == 2:
130
+ return "film"
131
+ return "unknown"
132
+
133
+
134
+ def rsm_analyze(
135
+ intensity: ArrayLike,
136
+ axis1: ArrayLike,
137
+ axis2: ArrayLike,
138
+ *,
139
+ qx: ArrayLike | None = None,
140
+ qz: ArrayLike | None = None,
141
+ n_peaks: int = 2,
142
+ threshold: float = 0.01,
143
+ smooth_sigma: float = 1.5,
144
+ min_separation: int = 4,
145
+ fit_window: int = 6,
146
+ fit_model: str = "2D Gaussian",
147
+ intensity_unit: str = "cps",
148
+ ) -> dict[str, Any]:
149
+ """Find + fit the brightest peaks in an RSM ``intensity`` grid.
150
+
151
+ ``intensity`` is ``(N, M)`` with row axis ``axis1`` (omega, length N) and
152
+ column axis ``axis2`` (2theta, length M). Optional ``qx``/``qz`` are ``(N, M)``
153
+ reciprocal-space grids; when given, each peak is refit there too. Returns a
154
+ dict with ``peaks`` (centre_angle ``[omega, 2theta]``, centre_Q ``[Qx, Qz]``,
155
+ fwhm_angle, fwhm_Q, amplitude, background, classification, rank),
156
+ ``n_peaks_found``, ``intensity_unit``, ``used_q_space``.
157
+ """
158
+ if fit_model not in _FIT_MODELS:
159
+ raise ValueError(f"fit_model must be one of {_FIT_MODELS}, got {fit_model!r}")
160
+ img = np.asarray(intensity, dtype=float)
161
+ if img.ndim != 2:
162
+ raise ValueError(f"intensity must be 2-D, got {img.ndim}-D")
163
+ ax1 = np.asarray(axis1, dtype=float).ravel()
164
+ ax2 = np.asarray(axis2, dtype=float).ravel()
165
+ n, m = img.shape
166
+ if ax1.size != n or ax2.size != m:
167
+ raise ValueError(f"axis1/axis2 ({ax1.size}/{ax2.size}) must match intensity {n}x{m}")
168
+ has_q = qx is not None and qz is not None
169
+ qx_g = np.asarray(qx, dtype=float) if qx is not None else None
170
+ qz_g = np.asarray(qz, dtype=float) if qz is not None else None
171
+
172
+ smoothed = _gaussian_smooth_2d(img, smooth_sigma)
173
+ thresh = threshold * float(smoothed.max())
174
+ maxima = _find_local_maxima(smoothed, thresh, min_separation)[:n_peaks]
175
+
176
+ peaks: list[dict[str, Any]] = []
177
+ for rc, cc in maxima:
178
+ prows, pcols = _patch_indices(img.shape, rc, cc, fit_window)
179
+ ix = np.ix_(list(prows), list(pcols))
180
+ z_patch = img[ix]
181
+ tth_grid, omega_grid = np.meshgrid(ax2[list(pcols)], ax1[list(prows)])
182
+ fit_a = _try_fit(fit_model, tth_grid, omega_grid, z_patch)
183
+ if fit_a is None:
184
+ continue
185
+ cx, cy, fwx, fwy, amp, bg = _unpack_fit(fit_a, fit_model)
186
+
187
+ centre_q = [float("nan"), float("nan")]
188
+ fwhm_q = [float("nan"), float("nan")]
189
+ if has_q and qx_g is not None and qz_g is not None:
190
+ fit_q = _try_fit(fit_model, qx_g[ix], qz_g[ix], z_patch)
191
+ if fit_q is not None:
192
+ cqx, cqz, fwqx, fwqz, _, _ = _unpack_fit(fit_q, fit_model)
193
+ centre_q = [cqx, cqz]
194
+ fwhm_q = [fwqx, fwqz]
195
+
196
+ rank = len(peaks) + 1
197
+ peaks.append(
198
+ {
199
+ "rank": rank,
200
+ "centre_angle": [cy, cx], # [omega, 2theta]
201
+ "centre_Q": centre_q,
202
+ "fwhm_angle": [fwy, fwx],
203
+ "fwhm_Q": fwhm_q,
204
+ "amplitude": amp,
205
+ "background": bg,
206
+ "classification": _classify(rank, n_peaks),
207
+ }
208
+ )
209
+
210
+ return {
211
+ "peaks": peaks,
212
+ "n_peaks_found": len(peaks),
213
+ "intensity_unit": intensity_unit,
214
+ "used_q_space": has_q,
215
+ }
216
+
217
+
218
+ def rsm_grids_from_datastruct(ds: DataStruct) -> dict[str, Any]:
219
+ """Reshape a scattered RSM ``DataStruct`` (from ``io/xrdml`` 2D) back to grids.
220
+
221
+ Returns ``{"intensity", "axis1", "axis2", "qx", "qz", "intensity_unit"}``
222
+ ready for :func:`rsm_analyze`. Raises if the dataset is not a 2D RSM.
223
+ """
224
+ if not ds.metadata.get("is2D"):
225
+ raise ValueError("DataStruct is not a 2D RSM (metadata.is2D is not set)")
226
+ shape = ds.metadata.get("map_shape")
227
+ if not shape or len(shape) != 2:
228
+ raise ValueError("RSM DataStruct is missing a valid map_shape")
229
+ n, m = int(shape[0]), int(shape[1])
230
+ intensity = ds.column("Intensity").reshape(n, m)
231
+ axis2 = ds.column("2Theta").reshape(n, m)[0, :] # 2theta varies along columns
232
+ axis1_name = str(ds.metadata.get("axis1_name", "Omega"))
233
+ axis1 = ds.column(axis1_name).reshape(n, m)[:, 0] # secondary axis varies along rows
234
+ grids: dict[str, Any] = {
235
+ "intensity": intensity,
236
+ "axis1": axis1,
237
+ "axis2": axis2,
238
+ "qx": None,
239
+ "qz": None,
240
+ "intensity_unit": ds.units[ds.labels.index("Intensity")],
241
+ }
242
+ if "Qx" in ds.labels and "Qz" in ds.labels:
243
+ grids["qx"] = ds.column("Qx").reshape(n, m)
244
+ grids["qz"] = ds.column("Qz").reshape(n, m)
245
+ return grids