Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
db01fec
feat: add notebook for loading mcstas data into CODA nexus file
jokasimr Dec 2, 2025
be64ad4
feat: Nexus file writer for McStas data
jokasimr Dec 3, 2025
7a45ed0
fix: make coord transformation RunType dependent (same as in esssans …
jokasimr Dec 4, 2025
93ce3c9
WIP: writing mcstas to nexus
jokasimr Dec 4, 2025
ed220dc
WIP
jokasimr Dec 4, 2025
5738b83
fix: sample number of events per weight instead of number of events
jokasimr Dec 10, 2025
57adaba
fix: order matching estia Nexus file
jokasimr Dec 10, 2025
0f3edfd
feat: add loaders for sample- and detector rotation
jokasimr Dec 10, 2025
1b22f60
fix: add providers for geometry data to mcstas workflow
jokasimr Dec 10, 2025
8927c6e
fix: add default sample rotation offset
jokasimr Dec 10, 2025
1e9bb6c
feat: mcstas to nexus notebook
jokasimr Dec 10, 2025
396784b
fix: move geometry information from amor load function to coordtransf…
jokasimr Dec 10, 2025
62c71fc
docs: set reference detector to get parameters
jokasimr Dec 11, 2025
ead08a4
Merge remote-tracking branch 'origin/main' into mcstas-to-nexus
jokasimr Dec 11, 2025
98b7680
refactor: improve variable naming
jokasimr Dec 11, 2025
0507320
fix: use coordtransformationgraph type from ess.reflectometry in ess.…
jokasimr Dec 11, 2025
ef6ad61
fix: make sample rotation group a NXlog
jokasimr Dec 11, 2025
046a8fb
refactor: move mcstas related code to mcstas module
jokasimr Dec 12, 2025
5d6042a
refactor: move mcstas related into mcstas module
jokasimr Dec 12, 2025
bbeb8c7
docs: remove instrument view and reduction from mcstas->nexus notebook
jokasimr Dec 12, 2025
ec449ed
fix: import
jokasimr Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/user-guide/estia/estia-advanced-mcstas-reduction.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"\n",
"# If you want to use the ground truth wavelengths from the McStas simulation\n",
"# instead of the wavelengths from the frame unwrapping, uncomment the lines below:\n",
"#from ess.estia.conversions import mcstas_wavelength_coordinate_transformation_graph\n",
"#from ess.estia.mcstas import mcstas_wavelength_coordinate_transformation_graph\n",
"#wf.insert( mcstas_wavelength_coordinate_transformation_graph )\n",
"\n",
"wf[TimeOfFlightLookupTableFilename] = estia_tof_lookup_table()\n",
Expand Down Expand Up @@ -448,7 +448,7 @@
"\n",
"# If you want to use the ground truth wavelengths from the McStas simulation\n",
"# instead of the wavelengths from the frame unwrapping, uncomment the lines below:\n",
"#from ess.estia.conversions import mcstas_wavelength_coordinate_transformation_graph\n",
"#from ess.estia.mcstas import mcstas_wavelength_coordinate_transformation_graph\n",
"#wf.insert( mcstas_wavelength_coordinate_transformation_graph )\n",
"\n",
"wf[TimeOfFlightLookupTableFilename] = estia_tof_lookup_table()\n",
Expand Down
4 changes: 3 additions & 1 deletion docs/user-guide/estia/simulated-spin-flip-sample.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
" estia_tof_lookup_table,\n",
")\n",
"from ess.reflectometry.figures import wavelength_z_figure\n",
"from ess.estia.conversions import mcstas_wavelength_coordinate_transformation_graph\n",
"from ess.estia.mcstas import mcstas_wavelength_coordinate_transformation_graph\n",
"\n",
"from ess.polarization import ( # noqa: F401\n",
" Up, Down, ReducedSampleDataBySpinChannel, SupermirrorEfficiencyFunction, Polarizer, Analyzer, PolarizationAnalysisWorkflow,\n",
Expand Down Expand Up @@ -353,6 +353,8 @@
"mask = sc.isnan(I0).sum(('blade', 'wire')) > sc.scalar(0, unit=None)\n",
"wf[ReducedReference] = I0.assign_masks(nopolcal=mask.data).assign_coords(wavelength=wf.compute(WavelengthBins))\n",
"\n",
"# Required to read sample rotation / similar parameters associated with the reference measurement\n",
"wf[RawDetector] = sc.io.load_hdf5(estia_mcstas_spin_flip_example('supermirror', 'offoff'))\n",
"\n",
"sample_name = 'spin_flip_sample'\n",
"\n",
Expand Down
66 changes: 64 additions & 2 deletions src/ess/amor/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@
from ..reflectometry.conversions import reflectometry_q
from ..reflectometry.types import (
BeamDivergenceLimits,
BeamSize,
CoordTransformationGraph,
DetectorRotation,
RunType,
SampleRotation,
SampleSize,
WavelengthBins,
YIndexLimits,
ZIndexLimits,
)
from .geometry import Detector
from .types import GravityToggle
from .types import (
ChopperDistance,
ChopperFrequency,
ChopperPhase,
ChopperSeparation,
GravityToggle,
)


def theta(wavelength, pixel_divergence_angle, L2, sample_rotation, detector_rotation):
Expand Down Expand Up @@ -135,17 +146,68 @@ def wavelength(
return out.to(unit='angstrom', copy=False)


def coordinate_transformation_graph(gravity: GravityToggle) -> CoordTransformationGraph:
def coordinate_transformation_graph(
gravity: GravityToggle,
detector_rotation: DetectorRotation[RunType],
sample_rotation: SampleRotation[RunType],
chopper_phase: ChopperPhase[RunType],
chopper_frequency: ChopperFrequency[RunType],
chopper_distance: ChopperDistance[RunType],
chopper_separation: ChopperSeparation[RunType],
sample_size: SampleSize[RunType],
beam_size: BeamSize[RunType],
) -> CoordTransformationGraph[RunType]:
return {
"wavelength": wavelength,
"theta": theta if gravity else theta_no_gravity,
"divergence_angle": divergence_angle,
"Q": reflectometry_q,
"L1": lambda chopper_distance: sc.abs(chopper_distance),
"L2": lambda distance_in_detector: distance_in_detector + Detector.distance,
"sample_rotation": lambda: sample_rotation.to(unit='rad'),
"detector_rotation": lambda: detector_rotation.to(unit='rad'),
"chopper_phase": lambda: chopper_phase,
"chopper_frequency": lambda: chopper_frequency,
"chopper_separation": lambda: chopper_separation,
"chopper_distance": lambda: chopper_distance,
"sample_size": lambda: sample_size,
"beam_size": lambda: beam_size,
}


def add_coords(
da: sc.DataArray,
graph: dict,
) -> sc.DataArray:
"Adds scattering coordinates to the raw detector data."
return da.transform_coords(
(
"wavelength",
"theta",
"divergence_angle",
"Q",
"L1",
"L2",
"blade",
"wire",
"strip",
"z_index",
"sample_rotation",
"detector_rotation",
"sample_size",
"beam_size",
"chopper_phase",
"chopper_frequency",
"chopper_separation",
"chopper_distance",
),
graph,
rename_dims=False,
keep_intermediate=False,
keep_aliases=False,
)


def _not_between(v, a, b):
return (v < a) | (v > b)

Expand Down
19 changes: 0 additions & 19 deletions src/ess/amor/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from ..reflectometry.load import load_nx
from ..reflectometry.types import (
Beamline,
BeamSize,
DetectorRotation,
Filename,
Measurement,
Expand All @@ -20,9 +19,7 @@
RawDetector,
RawSampleRotation,
RunType,
SampleRotation,
SampleRun,
SampleSize,
)
from .geometry import pixel_coordinates_in_detector_system
from .types import (
Expand All @@ -41,14 +38,6 @@ def load_detector(

def load_events(
detector: NeXusComponent[snx.NXdetector, RunType],
detector_rotation: DetectorRotation[RunType],
sample_rotation: SampleRotation[RunType],
chopper_phase: ChopperPhase[RunType],
chopper_frequency: ChopperFrequency[RunType],
chopper_distance: ChopperDistance[RunType],
chopper_separation: ChopperSeparation[RunType],
sample_size: SampleSize[RunType],
beam_size: BeamSize[RunType],
) -> RawDetector[RunType]:
event_data = detector["data"]
if 'event_time_zero' in event_data.coords:
Expand All @@ -69,14 +58,6 @@ def load_events(
"data"
].data.values

data.coords["sample_rotation"] = sample_rotation.to(unit='rad')
data.coords["detector_rotation"] = detector_rotation.to(unit='rad')
data.coords["chopper_phase"] = chopper_phase
data.coords["chopper_frequency"] = chopper_frequency
data.coords["chopper_separation"] = chopper_separation
data.coords["chopper_distance"] = chopper_distance
data.coords["sample_size"] = sample_size
data.coords["beam_size"] = beam_size
return RawDetector[RunType](data)


Expand Down
3 changes: 2 additions & 1 deletion src/ess/amor/normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ReducedReference,
ReducibleData,
Reference,
ReferenceRun,
Sample,
SampleRun,
)
Expand Down Expand Up @@ -71,7 +72,7 @@ def evaluate_reference_at_sample_coords(
reference: ReducedReference,
sample: ReducibleData[SampleRun],
detector_spatial_resolution: DetectorSpatialResolution[SampleRun],
graph: CoordTransformationGraph,
graph: CoordTransformationGraph[ReferenceRun],
) -> Reference:
"""
Adds a :math:`Q` and :math:`Q`-resolution coordinate to each bin of the ideal
Expand Down
5 changes: 2 additions & 3 deletions src/ess/amor/workflow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
from ..reflectometry.conversions import (
add_coords,
add_proton_current_coord,
add_proton_current_mask,
)
Expand All @@ -16,7 +15,7 @@
YIndexLimits,
ZIndexLimits,
)
from .conversions import add_masks
from .conversions import add_coords, add_masks


def add_coords_masks_and_apply_corrections(
Expand All @@ -26,7 +25,7 @@ def add_coords_masks_and_apply_corrections(
bdlim: BeamDivergenceLimits,
wbins: WavelengthBins,
proton_current: ProtonCurrent[RunType],
graph: CoordTransformationGraph,
graph: CoordTransformationGraph[RunType],
) -> ReducibleData[RunType]:
"""
Computes coordinates, masks and corrections that are
Expand Down
2 changes: 1 addition & 1 deletion src/ess/estia/beamline.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

DETECTOR_BANK_SIZES = {
"multiblade_detector": {
"strip": 64,
"blade": 48,
"wire": 32,
"strip": 64,
},
}

Expand Down
81 changes: 57 additions & 24 deletions src/ess/estia/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
import scipp as sc
from scippneutron.conversion.tof import wavelength_from_tof
from scippnexus import NXsample, NXsource

from ess.reduce.nexus.types import DetectorBankSizes, Position

from ..reflectometry.conversions import reflectometry_q
from ..reflectometry.types import (
CoordTransformationGraph,
DetectorLtotal,
RawDetector,
DetectorRotation,
RunType,
SampleRotation,
)


Expand Down Expand Up @@ -62,34 +65,64 @@ def divergence_angle(
return sc.atan2(y=p.fields.x, x=p.fields.z) - detector_rotation.to(unit='rad')


def detector_ltotal_from_raw(
da: RawDetector[RunType], graph: CoordTransformationGraph
) -> DetectorLtotal[RunType]:
return da.transform_coords(['Ltotal'], graph=graph).coords['Ltotal']


def coordinate_transformation_graph() -> CoordTransformationGraph:
def coordinate_transformation_graph(
source_position: Position[NXsource, RunType],
sample_position: Position[NXsample, RunType],
sample_rotation: SampleRotation[RunType],
detector_rotation: DetectorRotation[RunType],
detector_bank_sizes: DetectorBankSizes,
) -> CoordTransformationGraph[RunType]:
bank = detector_bank_sizes['multiblade_detector']
return {
"wavelength": wavelength_from_tof,
"theta": theta,
"divergence_angle": divergence_angle,
"Q": reflectometry_q,
"L1": lambda source_position, sample_position: sc.norm(
sample_position - source_position
sample_position - source_position.to(unit=sample_position.unit)
), # + extra correction for guides?
"L2": lambda position, sample_position: sc.norm(position - sample_position),
"Ltotal": lambda L1, L2: L1 + L2,
}


def mcstas_wavelength_coordinate_transformation_graph() -> CoordTransformationGraph:
return {
**coordinate_transformation_graph(),
"wavelength": lambda wavelength_from_mcstas: wavelength_from_mcstas,
"L2": lambda position, sample_position: sc.norm(
position - sample_position.to(unit=position.unit)
),
"Ltotal": lambda L1, L2: L1.to(unit=L2.unit) + L2,
'sample_size': lambda: sc.scalar(20.0, unit='mm'),
'blade': lambda: sc.arange('blade', bank['blade'] - 1, -1, -1),
'wire': lambda: sc.arange('wire', bank['wire'] - 1, -1, -1),
'strip': lambda: sc.arange('strip', bank['strip'] - 1, -1, -1),
'z_index': lambda blade, wire: blade * wire,
"wavelength": wavelength_from_tof,
'sample_rotation': lambda: sample_rotation,
'detector_rotation': lambda: detector_rotation,
'source_position': lambda: source_position,
'sample_position': lambda: sample_position,
}


providers = (
coordinate_transformation_graph,
detector_ltotal_from_raw,
)
def add_coords(
da: sc.DataArray,
graph: dict,
) -> sc.DataArray:
"Adds scattering coordinates to the raw detector data."
return da.transform_coords(
(
"wavelength",
"theta",
"divergence_angle",
"Q",
"L1",
"L2",
"blade",
"wire",
"strip",
"z_index",
"sample_rotation",
"detector_rotation",
"sample_size",
),
graph,
rename_dims=False,
keep_intermediate=False,
keep_aliases=False,
)


providers = (coordinate_transformation_graph,)
4 changes: 2 additions & 2 deletions src/ess/estia/corrections.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import scipp as sc

from ..reflectometry.conversions import (
add_coords,
add_proton_current_coord,
add_proton_current_mask,
)
Expand All @@ -19,6 +18,7 @@
YIndexLimits,
ZIndexLimits,
)
from .conversions import add_coords
from .maskings import add_masks


Expand All @@ -29,7 +29,7 @@ def add_coords_masks_and_apply_corrections(
bdlim: BeamDivergenceLimits,
wbins: WavelengthBins,
proton_current: ProtonCurrent[RunType],
graph: CoordTransformationGraph,
graph: CoordTransformationGraph[RunType],
) -> ReducibleData[RunType]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR, but this reminds me that ReducibleData should probably be renamed to CorrectedDetector? (according to the new naming scheme)

"""
Computes coordinates, masks and corrections that are
Expand Down
Loading
Loading