Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions docs/api-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
:recursive:

providers
batch_compute
batch_processor
scale_for_reflectivity_overlap
linlogspace
combine_curves
```

## Submodules
Expand Down Expand Up @@ -79,3 +84,21 @@
normalization
workflow
```

## Offspec

```{eval-rst}
.. currentmodule:: ess.offspec

.. autosummary::
:toctree: ../generated/modules
:template: module-template.rst
:recursive:

conversions
data
load
normalization
types
workflow
```
2 changes: 1 addition & 1 deletion docs/user-guide/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ including dependencies used in the graphical user interface for batch reduction,
`````{tab-set}
````{tab-item} pip
```sh
pip install essreflectometry[all]
pip install essreflectometry[gui]
```
````
````{tab-item} conda
Expand Down
11 changes: 7 additions & 4 deletions src/ess/amor/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
)


def theta(wavelength, pixel_divergence_angle, L2, sample_rotation, detector_rotation):
def theta(
wavelength, pixel_divergence_angle, L2, sample_rotation, detector_rotation
) -> sc.Variable:
'''
Angle of reflection.

Expand Down Expand Up @@ -83,7 +85,7 @@ def theta(wavelength, pixel_divergence_angle, L2, sample_rotation, detector_rota

def theta_no_gravity(
wavelength, pixel_divergence_angle, sample_rotation, detector_rotation
):
) -> sc.Variable:
'''
Angle of reflection.

Expand All @@ -101,7 +103,7 @@ def theta_no_gravity(
return theta


def divergence_angle(theta, sample_rotation, detector_rotation):
def divergence_angle(theta, sample_rotation, detector_rotation) -> sc.Variable:
"""
Difference between the incident angle and the center of the incident beam.
Useful for filtering parts of the beam that have too high divergence.
Expand All @@ -118,7 +120,7 @@ def divergence_angle(theta, sample_rotation, detector_rotation):

def wavelength(
event_time_offset, pixel_divergence_angle, L1, L2, chopper_phase, chopper_frequency
):
) -> sc.Variable:
"Converts event_time_offset to wavelength using the chopper settings."
out = event_time_offset.to(unit="ns", dtype="float64", copy=True)
unit = out.bins.unit
Expand Down Expand Up @@ -157,6 +159,7 @@ def coordinate_transformation_graph(
sample_size: SampleSize[RunType],
beam_size: BeamSize[RunType],
) -> CoordTransformationGraph[RunType]:
"""Build the coordinate transformation graph for Amor."""
return {
"wavelength": wavelength,
"theta": theta if gravity else theta_no_gravity,
Expand Down
4 changes: 4 additions & 0 deletions src/ess/amor/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,17 @@


def amor_old_sample_run() -> Path:
"""Return path to a legacy sample run used in tests/examples."""
return _registry.get_path("sample.nxs")


def amor_old_reference_run() -> Path:
"""Return path to a legacy reference run used in tests/examples."""
return _registry.get_path("reference.nxs")


def amor_run(number: int | str) -> Path:
"""Return path to an Amor NeXus file by run number."""
fnames = [
name
for name in _files.keys()
Expand All @@ -87,6 +90,7 @@ def amor_run(number: int | str) -> Path:


def amor_psi_software_result(number: int | str) -> Path:
"""Return path to a PSI reduction result (.ort) by run number."""
return _registry.get_path(f"{int(number):03d}.Rqz.ort")


Expand Down
12 changes: 12 additions & 0 deletions src/ess/amor/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
def load_detector(
file_path: Filename[RunType], detector_name: NeXusDetectorName
) -> NeXusComponent[snx.NXdetector, RunType]:
"""Load the detector group from a NeXus file."""
return next(load_nx(file_path, f"NXentry/NXinstrument/{detector_name}"))


def load_events(
detector: NeXusComponent[snx.NXdetector, RunType],
) -> RawDetector[RunType]:
"""Load and reshape event data from a NeXus detector group."""
event_data = detector["data"]
if 'event_time_zero' in event_data.coords:
event_data.bins.coords['event_time_zero'] = sc.bins_like(
Expand All @@ -62,34 +64,40 @@ def load_events(


def amor_chopper(f: Filename[RunType]) -> RawChopper[RunType]:
"""Load the chopper group from a NeXus file."""
return next(load_nx(f, "NXentry/NXinstrument/NXdisk_chopper"))


def load_amor_chopper_distance(ch: RawChopper[RunType]) -> ChopperDistance[RunType]:
"""Extract the chopper distance from the chopper group."""
# We know the value has unit 'mm'
return sc.scalar(ch["distance"], unit="mm")


def load_amor_chopper_separation(ch: RawChopper[RunType]) -> ChopperSeparation[RunType]:
"""Extract the chopper pair separation from the chopper group."""
# We know the value has unit 'mm'
return sc.scalar(ch["pair_separation"], unit="mm")


def load_amor_ch_phase(ch: RawChopper[RunType]) -> ChopperPhase[RunType]:
"""Extract chopper phase from the chopper group."""
p = ch["phase"]["value"].coords["average_value"].value
if getattr(p, "unit", None):
return p
raise ValueError("No unit was found for the chopper phase")


def load_amor_ch_frequency(ch: RawChopper[RunType]) -> ChopperFrequency[RunType]:
"""Extract chopper frequency from the chopper group."""
f = ch["rotation_speed"]["value"].coords["average_value"]
if getattr(f, "unit", None):
return f
raise ValueError("No unit was found for the chopper frequency")


def load_amor_sample_rotation(fp: Filename[RunType]) -> RawSampleRotation[RunType]:
"""Load sample rotation log and return the first value."""
(mu,) = load_nx(fp, "NXentry/NXinstrument/master_parameters/mu")
# Jochens Amor code reads the first value of this log
# see https://github.com/jochenstahn/amor/blob/140e3192ddb7e7f28acee87e2acaee65ce1332aa/libeos/file_reader.py#L272 # noqa: E501
Expand All @@ -98,6 +106,7 @@ def load_amor_sample_rotation(fp: Filename[RunType]) -> RawSampleRotation[RunTyp


def load_amor_detector_rotation(fp: Filename[RunType]) -> DetectorRotation[RunType]:
"""Load detector rotation log and return the first value."""
(nu,) = load_nx(fp, "NXentry/NXinstrument/master_parameters/nu")
# Jochens Amor code reads the first value of this log
# see https://github.com/jochenstahn/amor/blob/140e3192ddb7e7f28acee87e2acaee65ce1332aa/libeos/file_reader.py#L272 # noqa: E501
Expand All @@ -108,19 +117,22 @@ def load_amor_detector_rotation(fp: Filename[RunType]) -> DetectorRotation[RunTy
def load_amor_proton_current(
fp: Filename[RunType],
) -> ProtonCurrent[RunType]:
"""Load proton current log from the NeXus detector group."""
(pc,) = load_nx(fp, 'NXentry/NXinstrument/NXdetector/proton_current')
pc = pc['value']['dim_1', 0]
pc.data.unit = 'mA/s'
return pc


def load_beamline_metadata(filename: Filename[RunType]) -> Beamline[RunType]:
"""Load beamline metadata from a NeXus file."""
return nexus_workflow.load_beamline_metadata_from_nexus(
NeXusFileSpec[SampleRun](filename)
)


def load_measurement_metadata(filename: Filename[RunType]) -> Measurement[RunType]:
"""Load measurement metadata from a NeXus file."""
return nexus_workflow.load_measurement_metadata_from_nexus(
NeXusFileSpec[SampleRun](filename)
)
Expand Down
1 change: 1 addition & 0 deletions src/ess/amor/orso.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


def orso_amor_corrections() -> OrsoCorrectionList:
"""Return list of corrections applied in Amor reductions."""
return OrsoCorrectionList(
[
"chopper ToF correction",
Expand Down
2 changes: 2 additions & 0 deletions src/ess/estia/calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

@dataclass
class PolarizationCalibrationParameters:
"""Calibration parameters for polarized reflectometry."""

I0: sc.DataArray
'''Reference intensity.'''
Pp: sc.DataArray
Expand Down
18 changes: 13 additions & 5 deletions src/ess/estia/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,21 @@


def estia_mcstas_reference_run() -> Filename[ReferenceRun]:
"""Return path to the McStas reference events file."""
return Filename[ReferenceRun](
_registry.get_path("218610_tof_detector_list.p.x.y.t.L.sx.sy")
)


def estia_mcstas_sample_run(number: int | str) -> Filename[SampleRun]:
"""Return path to a McStas sample events file by run number."""
return Filename[SampleRun](
_registry.get_path(f"2186{int(number):02d}_tof_detector_list.p.x.y.t.L.sx.sy")
)


def estia_mcstas_nexus_reference_example() -> Path:
"""Return path to a NeXus reference example file."""
return _registry.get_path("examples/220573.nx")


Expand All @@ -98,6 +101,7 @@ def estia_mcstas_nexus_sample_example(name: str) -> list[Path]:


def estia_mcstas_reference_example() -> Path:
"""Return path to a McStas reference example file."""
return _registry.get_path("examples/220573/mccode.h5")


Expand Down Expand Up @@ -127,7 +131,7 @@ def estia_mcstas_sample_example(name: str) -> list[Path]:
raise ValueError(f'"{name}" is not a valid sample name')


def estia_mcstas_groundtruth(name):
def estia_mcstas_groundtruth(name) -> sc.DataArray:
"""Returns the ground truth reflectivity curve for the sample."""

def parse(fname):
Expand All @@ -152,11 +156,13 @@ def parse(fname):
raise ValueError(f'"{name}" is not a valid sample name')


def estia_mcstas_spin_flip_example(sample, flipper_setting):
def estia_mcstas_spin_flip_example(sample, flipper_setting) -> Path:
"""Return path to a spin-flip McStas example file."""
return _registry.get_path(f'spin_flip_example/{sample}_{flipper_setting}.h5')


def estia_mcstas_spin_flip_example_groundtruth(up_or_down):
def estia_mcstas_spin_flip_example_groundtruth(up_or_down) -> Path:
"""Return path to the spin-flip ground truth reflectivity curve."""
if up_or_down == 'down':
return _registry.get_path(
'spin_flip_example/ground_truth_spin_down_reflectivity.h5'
Expand All @@ -172,7 +178,8 @@ def _refresh_cache(args):
estia_mcstas_spin_flip_example(*args)


def estia_mcstas_spin_flip_example_download_all_to_cache():
def estia_mcstas_spin_flip_example_download_all_to_cache() -> None:
"""Download all spin-flip example files into the local cache."""
# Run once to create the folder structure without conflicts
_refresh_cache(('supermirror', 'offoff'))
with ThreadPool(20) as pool:
Expand All @@ -192,7 +199,8 @@ def estia_mcstas_spin_flip_example_download_all_to_cache():
pass


def estia_tof_lookup_table():
def estia_tof_lookup_table() -> Path:
"""Return path to the ESTIA time-of-flight lookup table."""
return _registry.get_path('estia-tof-lookup-table-pulse-stride-1.h5')


Expand Down
2 changes: 2 additions & 0 deletions src/ess/estia/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
def load_sample_rotation(
sample: NeXusComponent[NXsample, RunType],
) -> RawSampleRotation[RunType]:
"""Load sample rotation from the NeXus sample group."""
return sample['sample_rotation'][0].data


def load_detector_rotation(
detector: NeXusComponent[NXdetector, RunType],
) -> DetectorRotation[RunType]:
"""Load detector rotation from the NeXus detector group."""
return detector['transformations']['detector_rotation'].value[0].data


Expand Down
15 changes: 12 additions & 3 deletions src/ess/estia/mcstas.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
from .conversions import coordinate_transformation_graph


def parse_metadata_ascii(lines):
def parse_metadata_ascii(lines) -> dict:
"""Parse McStas ASCII metadata sections from a file-like iterator."""
data = {}
section = None
for line in lines:
Expand All @@ -41,7 +42,8 @@ def parse_metadata_ascii(lines):
return data


def parse_events_ascii(lines):
def parse_events_ascii(lines) -> sc.DataArray:
"""Parse McStas ASCII events into a Scipp DataArray."""
meta = {}
data = []
for line in lines:
Expand Down Expand Up @@ -81,7 +83,8 @@ def parse_events_ascii(lines):
raise ValueError('Could not parse the file as a list of events.')


def parse_events_h5(f, events_to_sample_per_unit_weight=None):
def parse_events_h5(f, events_to_sample_per_unit_weight=None) -> sc.DataArray:
"""Parse McStas HDF5 events into a Scipp DataArray."""
if isinstance(f, str):
with h5py.File(f) as ff:
return parse_events_h5(ff)
Expand Down Expand Up @@ -233,30 +236,35 @@ def load_mcstas(


def load_sample_rotation(da: RawDetector[RunType]) -> SampleRotation[RunType]:
"""Extract sample rotation from McStas-derived raw detector data."""
return da.coords['sample_rotation']


def load_detector_rotation(
da: RawDetector[RunType],
) -> DetectorRotation[RunType]:
"""Extract detector rotation from McStas-derived raw detector data."""
return da.coords['detector_rotation']


def load_source_position(
da: RawDetector[RunType],
) -> Position[NXsource, RunType]:
"""Extract source position from McStas-derived raw detector data."""
return da.coords['source_position']


def load_sample_position(
da: RawDetector[RunType],
) -> Position[NXsample, RunType]:
"""Extract sample position from McStas-derived raw detector data."""
return da.coords['sample_position']


def detector_ltotal_from_raw(
da: RawDetector[RunType], graph: CoordTransformationGraph[RunType]
) -> DetectorLtotal[RunType]:
"""Compute detector total flight path length from raw data."""
return da.transform_coords(
['Ltotal'],
graph=graph,
Expand All @@ -270,6 +278,7 @@ def mcstas_wavelength_coordinate_transformation_graph(
detector_rotation: DetectorRotation[RunType],
detector_bank_sizes: DetectorBankSizes,
) -> CoordTransformationGraph[RunType]:
"""Build a coordinate transformation graph using McStas wavelengths."""
return {
**coordinate_transformation_graph(
source_position,
Expand Down
1 change: 1 addition & 0 deletions src/ess/estia/orso.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


def orso_estia_corrections() -> OrsoCorrectionList:
"""Return list of corrections applied in Estia reductions."""
return OrsoCorrectionList(
[
"chopper ToF correction",
Expand Down
Loading
Loading