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.
- quantized/__init__.py +11 -0
- quantized/__main__.py +14 -0
- quantized/api.py +220 -0
- quantized/app.py +206 -0
- quantized/calc/__init__.py +8 -0
- quantized/calc/_clipfit.py +76 -0
- quantized/calc/_natural_neighbor.py +219 -0
- quantized/calc/aggregate.py +159 -0
- quantized/calc/backgrounds.py +353 -0
- quantized/calc/baseline.py +349 -0
- quantized/calc/batch_fit.py +148 -0
- quantized/calc/constants.py +27 -0
- quantized/calc/corrections.py +192 -0
- quantized/calc/crystallography.py +400 -0
- quantized/calc/diffusion.py +120 -0
- quantized/calc/electrical.py +248 -0
- quantized/calc/electrochemistry.py +176 -0
- quantized/calc/element_data.json +1 -0
- quantized/calc/element_data.py +59 -0
- quantized/calc/errors.py +246 -0
- quantized/calc/figure.py +417 -0
- quantized/calc/figure_break.py +152 -0
- quantized/calc/figure_categorical.py +156 -0
- quantized/calc/figure_corner.py +229 -0
- quantized/calc/figure_facets.py +127 -0
- quantized/calc/figure_field.py +137 -0
- quantized/calc/figure_hitmap.py +116 -0
- quantized/calc/figure_labels.py +62 -0
- quantized/calc/figure_map.py +287 -0
- quantized/calc/figure_overrides.py +159 -0
- quantized/calc/figure_page.py +266 -0
- quantized/calc/figure_scale.py +125 -0
- quantized/calc/figure_statplots.py +167 -0
- quantized/calc/figure_styles.py +131 -0
- quantized/calc/figure_ternary.py +239 -0
- quantized/calc/figure_ticks.py +217 -0
- quantized/calc/fit_autoguess.py +156 -0
- quantized/calc/fit_bootstrap.py +163 -0
- quantized/calc/fit_bumps.py +258 -0
- quantized/calc/fit_constraints.py +114 -0
- quantized/calc/fit_equation.py +264 -0
- quantized/calc/fit_findxy.py +80 -0
- quantized/calc/fit_models.py +195 -0
- quantized/calc/fit_models_special.py +189 -0
- quantized/calc/fit_odr.py +99 -0
- quantized/calc/fit_scan.py +243 -0
- quantized/calc/fit_stats.py +215 -0
- quantized/calc/fitting.py +199 -0
- quantized/calc/formula.py +90 -0
- quantized/calc/global_curve_fit.py +305 -0
- quantized/calc/global_fit.py +181 -0
- quantized/calc/interp2d.py +260 -0
- quantized/calc/linecut.py +269 -0
- quantized/calc/magnetic.py +414 -0
- quantized/calc/magnetometry.py +464 -0
- quantized/calc/map.py +228 -0
- quantized/calc/mcmc.py +177 -0
- quantized/calc/optics.py +228 -0
- quantized/calc/pawley.py +251 -0
- quantized/calc/peak_batch.py +128 -0
- quantized/calc/peak_fit.py +259 -0
- quantized/calc/peak_integrate.py +104 -0
- quantized/calc/peak_multifit.py +260 -0
- quantized/calc/peak_track.py +134 -0
- quantized/calc/peaks.py +298 -0
- quantized/calc/peakshapes.py +85 -0
- quantized/calc/plotting.py +147 -0
- quantized/calc/processing.py +232 -0
- quantized/calc/qspace.py +48 -0
- quantized/calc/reductions.py +155 -0
- quantized/calc/reductions_fft.py +383 -0
- quantized/calc/refl_sld_presets.json +1 -0
- quantized/calc/reflectivity.py +80 -0
- quantized/calc/registry.py +303 -0
- quantized/calc/relaxation.py +119 -0
- quantized/calc/report.py +253 -0
- quantized/calc/report_emit.py +227 -0
- quantized/calc/resample.py +142 -0
- quantized/calc/rsm.py +91 -0
- quantized/calc/rsm_analyze.py +245 -0
- quantized/calc/semiconductor.py +488 -0
- quantized/calc/sld.py +131 -0
- quantized/calc/sld_formula.py +138 -0
- quantized/calc/spectral.py +357 -0
- quantized/calc/statplots.py +214 -0
- quantized/calc/stats.py +399 -0
- quantized/calc/stats_anova2.py +202 -0
- quantized/calc/stats_anova_ext.py +338 -0
- quantized/calc/stats_dist.py +196 -0
- quantized/calc/stats_glm.py +245 -0
- quantized/calc/stats_multivar.py +289 -0
- quantized/calc/stats_roc.py +157 -0
- quantized/calc/stats_survival.py +261 -0
- quantized/calc/stats_tests.py +380 -0
- quantized/calc/substrates.py +181 -0
- quantized/calc/superconductor.py +359 -0
- quantized/calc/surface_fit.py +290 -0
- quantized/calc/surface_models.py +156 -0
- quantized/calc/thermal.py +119 -0
- quantized/calc/thin_film.py +425 -0
- quantized/calc/unit_convert.py +259 -0
- quantized/calc/units.py +80 -0
- quantized/calc/vacuum.py +290 -0
- quantized/calc/xray.py +169 -0
- quantized/cli.py +214 -0
- quantized/datastruct.py +153 -0
- quantized/io/__init__.py +11 -0
- quantized/io/_hdf5_layout.py +308 -0
- quantized/io/_jcamp_asdf.py +135 -0
- quantized/io/_xrdml_scan.py +291 -0
- quantized/io/base.py +82 -0
- quantized/io/bruker_brml.py +177 -0
- quantized/io/bruker_raw.py +158 -0
- quantized/io/cif.py +266 -0
- quantized/io/consolidated.py +122 -0
- quantized/io/delimited.py +222 -0
- quantized/io/excel.py +135 -0
- quantized/io/hdf5.py +192 -0
- quantized/io/import_filters.py +178 -0
- quantized/io/import_preview.py +262 -0
- quantized/io/jcamp.py +179 -0
- quantized/io/lakeshore.py +163 -0
- quantized/io/ncnr.py +278 -0
- quantized/io/netcdf.py +195 -0
- quantized/io/opus.py +231 -0
- quantized/io/origin.py +346 -0
- quantized/io/origin_com.py +194 -0
- quantized/io/origin_project/__init__.py +221 -0
- quantized/io/origin_project/annotation_marks.py +288 -0
- quantized/io/origin_project/container.py +262 -0
- quantized/io/origin_project/curve_style_color.py +359 -0
- quantized/io/origin_project/figure_geometry.py +108 -0
- quantized/io/origin_project/figure_layers.py +333 -0
- quantized/io/origin_project/figure_text.py +258 -0
- quantized/io/origin_project/figures.py +210 -0
- quantized/io/origin_project/figures_opju.py +440 -0
- quantized/io/origin_project/notes.py +302 -0
- quantized/io/origin_project/opj.py +459 -0
- quantized/io/origin_project/opj_curves.py +297 -0
- quantized/io/origin_project/opj_shapes.py +148 -0
- quantized/io/origin_project/opju.py +146 -0
- quantized/io/origin_project/opju_axis_real_form.py +418 -0
- quantized/io/origin_project/opju_axis_specimen_form.py +167 -0
- quantized/io/origin_project/opju_codec.py +370 -0
- quantized/io/origin_project/opju_curves.py +497 -0
- quantized/io/origin_project/opju_curves_allcols.py +258 -0
- quantized/io/origin_project/opju_figure_curves.py +302 -0
- quantized/io/origin_project/opju_figure_text.py +245 -0
- quantized/io/origin_project/opju_reports.py +129 -0
- quantized/io/origin_project/origin_richtext.py +145 -0
- quantized/io/origin_project/preview.py +132 -0
- quantized/io/origin_project/templates.py +314 -0
- quantized/io/origin_project/tree.py +379 -0
- quantized/io/origin_project/tree_opju.py +228 -0
- quantized/io/origin_project/windows.py +238 -0
- quantized/io/origin_project/windows_opju.py +393 -0
- quantized/io/origin_project/writer.py +156 -0
- quantized/io/origin_project/writer_blocks.py +282 -0
- quantized/io/qd.py +380 -0
- quantized/io/refl1d.py +132 -0
- quantized/io/registry.py +210 -0
- quantized/io/report_export.py +347 -0
- quantized/io/rigaku.py +100 -0
- quantized/io/sims.py +398 -0
- quantized/io/spc.py +311 -0
- quantized/io/xrd_csv.py +308 -0
- quantized/io/xrdml.py +394 -0
- quantized/jobs.py +173 -0
- quantized/plugins/__init__.py +50 -0
- quantized/plugins/contract.py +111 -0
- quantized/plugins/loader.py +394 -0
- quantized/plugins/steps.py +90 -0
- quantized/routes/__init__.py +7 -0
- quantized/routes/_bookcache.py +62 -0
- quantized/routes/_export_common.py +27 -0
- quantized/routes/_payload.py +58 -0
- quantized/routes/_uploadcache.py +59 -0
- quantized/routes/aggregate.py +46 -0
- quantized/routes/baseline.py +210 -0
- quantized/routes/books.py +117 -0
- quantized/routes/calc.py +56 -0
- quantized/routes/corrections.py +78 -0
- quantized/routes/crystallography.py +80 -0
- quantized/routes/diffusion.py +58 -0
- quantized/routes/electrical.py +101 -0
- quantized/routes/electrochemistry.py +83 -0
- quantized/routes/export.py +280 -0
- quantized/routes/export_facets.py +83 -0
- quantized/routes/export_figures.py +471 -0
- quantized/routes/export_page.py +125 -0
- quantized/routes/fitting.py +379 -0
- quantized/routes/fitting_bumps.py +97 -0
- quantized/routes/import_template.py +97 -0
- quantized/routes/import_wizard.py +150 -0
- quantized/routes/jobs_api.py +59 -0
- quantized/routes/magnetic.py +135 -0
- quantized/routes/magnetometry.py +133 -0
- quantized/routes/optics.py +98 -0
- quantized/routes/parsers.py +281 -0
- quantized/routes/peaks.py +184 -0
- quantized/routes/plot.py +103 -0
- quantized/routes/reductions.py +121 -0
- quantized/routes/reference.py +58 -0
- quantized/routes/reflectivity.py +91 -0
- quantized/routes/report_export.py +119 -0
- quantized/routes/rsm.py +136 -0
- quantized/routes/samples.py +32 -0
- quantized/routes/semiconductor.py +207 -0
- quantized/routes/sld.py +42 -0
- quantized/routes/spectral.py +54 -0
- quantized/routes/statplots.py +99 -0
- quantized/routes/stats.py +418 -0
- quantized/routes/stats_design.py +321 -0
- quantized/routes/substrates.py +46 -0
- quantized/routes/superconductor.py +139 -0
- quantized/routes/thermal.py +57 -0
- quantized/routes/thin_film.py +153 -0
- quantized/routes/vacuum.py +113 -0
- quantized/routes/xray.py +32 -0
- quantized/samples/demo_vsm.csv +42 -0
- quantized/server_launch.py +251 -0
- quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
- quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
- quantized/web/assets/index-BHmmCL-x.js +27 -0
- quantized/web/assets/index-BiZzN7J6.css +1 -0
- quantized/web/index.html +13 -0
- quantized/web/loading.html +69 -0
- quantized_lab-0.8.0.dist-info/METADATA +122 -0
- quantized_lab-0.8.0.dist-info/RECORD +233 -0
- quantized_lab-0.8.0.dist-info/WHEEL +4 -0
- quantized_lab-0.8.0.dist-info/entry_points.txt +4 -0
- quantized_lab-0.8.0.dist-info/licenses/LICENSE +201 -0
- 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
|