|
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