|
1 | 1 | # SPDX-License-Identifier: BSD-3-Clause |
2 | 2 | # Copyright (c) 2024 Scipp contributors (https://github.com/scipp) |
3 | | -"""ORSO utilities for Amor.""" |
| 3 | +from ess.reflectometry.orso import OrsoCorrectionList |
4 | 4 |
|
5 | | -import numpy as np |
6 | | -import scipp as sc |
7 | | -from orsopy.fileio import base as orso_base |
8 | | -from orsopy.fileio import data_source as orso_data_source |
9 | | -from orsopy.fileio.orso import Column, Orso, OrsoDataset |
10 | 5 |
|
11 | | -from ..reflectometry.orso import ( |
12 | | - OrsoDataSource, |
13 | | - OrsoInstrument, |
14 | | - OrsoIofQDataset, |
15 | | - OrsoReduction, |
16 | | -) |
17 | | -from ..reflectometry.types import ReflectivityOverQ |
18 | | - |
19 | | - |
20 | | -def build_orso_instrument(events: ReflectivityOverQ) -> OrsoInstrument: |
21 | | - """Build ORSO instrument metadata from intermediate reduction results for Amor. |
22 | | -
|
23 | | - This assumes specular reflection and sets the incident angle equal to the computed |
24 | | - scattering angle. |
25 | | - """ |
26 | | - return OrsoInstrument( |
27 | | - orso_data_source.InstrumentSettings( |
28 | | - wavelength=orso_base.ValueRange(*_limits_of_coord(events, "wavelength")), |
29 | | - incident_angle=orso_base.ValueRange(*_limits_of_coord(events, "theta")), |
30 | | - polarization=None, # TODO how can we determine this from the inputs? |
31 | | - ) |
32 | | - ) |
33 | | - |
34 | | - |
35 | | -def build_orso_iofq_dataset( |
36 | | - iofq: ReflectivityOverQ, |
37 | | - data_source: OrsoDataSource, |
38 | | - reduction: OrsoReduction, |
39 | | -) -> OrsoIofQDataset: |
40 | | - """Build an ORSO dataset for reduced I-of-Q data and associated metadata.""" |
41 | | - header = Orso( |
42 | | - data_source=data_source, |
43 | | - reduction=reduction, |
44 | | - columns=[ |
45 | | - Column("Qz", "1/angstrom", "wavevector transfer"), |
46 | | - Column("R", None, "reflectivity"), |
47 | | - Column("sR", None, "standard deviation of reflectivity"), |
48 | | - Column( |
49 | | - "sQz", |
50 | | - "1/angstrom", |
51 | | - "standard deviation of wavevector transfer resolution", |
52 | | - ), |
53 | | - ], |
| 6 | +def orso_amor_corrections() -> OrsoCorrectionList: |
| 7 | + return OrsoCorrectionList( |
| 8 | + [ |
| 9 | + "chopper ToF correction", |
| 10 | + "footprint correction", |
| 11 | + "supermirror calibration", |
| 12 | + ] |
54 | 13 | ) |
55 | | - iofq = iofq.hist() |
56 | | - |
57 | | - qz = iofq.coords["Q"].to(unit="1/angstrom", copy=False) |
58 | | - if iofq.coords.is_edges("Q"): |
59 | | - qz = sc.midpoints(qz) |
60 | | - r = sc.values(iofq.data) |
61 | | - sr = sc.stddevs(iofq.data) |
62 | | - sqz = iofq.coords["Q_resolution"].to(unit="1/angstrom", copy=False) |
63 | | - |
64 | | - data = np.column_stack(tuple(map(_extract_values_array, (qz, r, sr, sqz)))) |
65 | | - data = data[np.isfinite(data).all(axis=-1)] |
66 | | - ds = OrsoIofQDataset(OrsoDataset(header, data)) |
67 | | - ds.info.reduction.corrections = [ |
68 | | - "chopper ToF correction", |
69 | | - "footprint correction", |
70 | | - "supermirror calibration", |
71 | | - ] |
72 | | - return ds |
73 | | - |
74 | | - |
75 | | -def _extract_values_array(var: sc.Variable) -> np.ndarray: |
76 | | - if var.variances is not None: |
77 | | - raise sc.VariancesError( |
78 | | - "ORT columns must not have variances. " |
79 | | - "Store the uncertainties as standard deviations in a separate column." |
80 | | - ) |
81 | | - if var.ndim != 1: |
82 | | - raise sc.DimensionError(f"ORT columns must be one-dimensional, got {var.sizes}") |
83 | | - return var.values |
84 | | - |
85 | | - |
86 | | -def _limits_of_coord(data: sc.DataArray, name: str) -> tuple[float, float, str] | None: |
87 | | - if (coord := _get_coord(data, name)) is None: |
88 | | - return None |
89 | | - min_ = coord.min().value |
90 | | - max_ = coord.max().value |
91 | | - # Explicit conversions to float because orsopy does not like np.float* types. |
92 | | - return float(min_), float(max_), _ascii_unit(coord.unit) |
93 | | - |
94 | | - |
95 | | -def _get_coord(data: sc.DataArray, name: str) -> sc.Variable | None: |
96 | | - if name in data.coords: |
97 | | - return sc.DataArray(data=data.coords[name], masks=data.masks) |
98 | | - if (data.bins is not None) and (name in data.bins.coords): |
99 | | - # Note that .bins.concat() applies the top-level masks |
100 | | - events = data.bins.concat().value |
101 | | - return sc.DataArray(data=events.coords[name], masks=events.masks) |
102 | | - return None |
103 | | - |
104 | | - |
105 | | -def _ascii_unit(unit: sc.Unit) -> str: |
106 | | - unit = str(unit) |
107 | | - if unit == "Å": |
108 | | - return "angstrom" |
109 | | - return unit |
110 | 14 |
|
111 | 15 |
|
112 | | -providers = (build_orso_instrument, build_orso_iofq_dataset) |
| 16 | +providers = (orso_amor_corrections,) |
0 commit comments