From ec6b305017026a4a885430c54e7b9c763991c0a6 Mon Sep 17 00:00:00 2001 From: Johannes Kasimir Date: Wed, 4 Feb 2026 16:00:36 +0100 Subject: [PATCH 1/3] docs: add missing docstrings and add offspec to index --- docs/api-reference/index.md | 23 +++++++++++++++++++++ docs/user-guide/installation.md | 2 +- src/ess/amor/conversions.py | 1 + src/ess/amor/data.py | 4 ++++ src/ess/amor/load.py | 12 +++++++++++ src/ess/amor/orso.py | 1 + src/ess/estia/calibration.py | 2 ++ src/ess/estia/data.py | 8 ++++++++ src/ess/estia/load.py | 2 ++ src/ess/estia/mcstas.py | 9 +++++++++ src/ess/estia/orso.py | 1 + src/ess/estia/workflow.py | 2 ++ src/ess/offspec/conversions.py | 2 ++ src/ess/offspec/data.py | 2 ++ src/ess/offspec/load.py | 2 ++ src/ess/reflectometry/corrections.py | 1 + src/ess/reflectometry/figures.py | 4 ++++ src/ess/reflectometry/gui.py | 6 ++++++ src/ess/reflectometry/load.py | 28 ++++++++++++++++++++++++++ src/ess/reflectometry/normalization.py | 18 +++++++++++++++++ src/ess/reflectometry/types.py | 2 +- 21 files changed, 130 insertions(+), 2 deletions(-) diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index 6dc282a3..5e762d1a 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -19,6 +19,11 @@ :recursive: providers + batch_compute + batch_processor + scale_for_reflectivity_overlap + linlogspace + combine_curves ``` ## Submodules @@ -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 +``` diff --git a/docs/user-guide/installation.md b/docs/user-guide/installation.md index 70ca8950..e601a4dd 100644 --- a/docs/user-guide/installation.md +++ b/docs/user-guide/installation.md @@ -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 diff --git a/src/ess/amor/conversions.py b/src/ess/amor/conversions.py index d531a763..24c4ce8a 100644 --- a/src/ess/amor/conversions.py +++ b/src/ess/amor/conversions.py @@ -157,6 +157,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, diff --git a/src/ess/amor/data.py b/src/ess/amor/data.py index ad5a3e84..97ccdd04 100644 --- a/src/ess/amor/data.py +++ b/src/ess/amor/data.py @@ -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() @@ -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") diff --git a/src/ess/amor/load.py b/src/ess/amor/load.py index 3fcd8ffd..97d5b781 100644 --- a/src/ess/amor/load.py +++ b/src/ess/amor/load.py @@ -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( @@ -62,20 +64,24 @@ 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 @@ -83,6 +89,7 @@ def load_amor_ch_phase(ch: RawChopper[RunType]) -> ChopperPhase[RunType]: 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 @@ -90,6 +97,7 @@ def load_amor_ch_frequency(ch: RawChopper[RunType]) -> ChopperFrequency[RunType] 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 @@ -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 @@ -108,6 +117,7 @@ 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' @@ -115,12 +125,14 @@ def load_amor_proton_current( 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) ) diff --git a/src/ess/amor/orso.py b/src/ess/amor/orso.py index f796703e..6ae82242 100644 --- a/src/ess/amor/orso.py +++ b/src/ess/amor/orso.py @@ -4,6 +4,7 @@ def orso_amor_corrections() -> OrsoCorrectionList: + """Return list of corrections applied in Amor reductions.""" return OrsoCorrectionList( [ "chopper ToF correction", diff --git a/src/ess/estia/calibration.py b/src/ess/estia/calibration.py index 24b3a686..9c186665 100644 --- a/src/ess/estia/calibration.py +++ b/src/ess/estia/calibration.py @@ -6,6 +6,8 @@ @dataclass class PolarizationCalibrationParameters: + """Calibration parameters for polarized reflectometry.""" + I0: sc.DataArray '''Reference intensity.''' Pp: sc.DataArray diff --git a/src/ess/estia/data.py b/src/ess/estia/data.py index 24418d06..6d57d496 100644 --- a/src/ess/estia/data.py +++ b/src/ess/estia/data.py @@ -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") @@ -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") @@ -153,10 +157,12 @@ def parse(fname): def estia_mcstas_spin_flip_example(sample, flipper_setting): + """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): + """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' @@ -173,6 +179,7 @@ def _refresh_cache(args): def estia_mcstas_spin_flip_example_download_all_to_cache(): + """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: @@ -193,6 +200,7 @@ def estia_mcstas_spin_flip_example_download_all_to_cache(): def estia_tof_lookup_table(): + """Return path to the ESTIA time-of-flight lookup table.""" return _registry.get_path('estia-tof-lookup-table-pulse-stride-1.h5') diff --git a/src/ess/estia/load.py b/src/ess/estia/load.py index e60f6d78..65144cd1 100644 --- a/src/ess/estia/load.py +++ b/src/ess/estia/load.py @@ -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 diff --git a/src/ess/estia/mcstas.py b/src/ess/estia/mcstas.py index 6ba24b16..e8b31126 100644 --- a/src/ess/estia/mcstas.py +++ b/src/ess/estia/mcstas.py @@ -25,6 +25,7 @@ def parse_metadata_ascii(lines): + """Parse McStas ASCII metadata sections from a file-like iterator.""" data = {} section = None for line in lines: @@ -42,6 +43,7 @@ def parse_metadata_ascii(lines): def parse_events_ascii(lines): + """Parse McStas ASCII events into a Scipp DataArray.""" meta = {} data = [] for line in lines: @@ -82,6 +84,7 @@ def parse_events_ascii(lines): def parse_events_h5(f, events_to_sample_per_unit_weight=None): + """Parse McStas HDF5 events into a Scipp DataArray.""" if isinstance(f, str): with h5py.File(f) as ff: return parse_events_h5(ff) @@ -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, @@ -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, diff --git a/src/ess/estia/orso.py b/src/ess/estia/orso.py index 7e53a8ab..85ac355a 100644 --- a/src/ess/estia/orso.py +++ b/src/ess/estia/orso.py @@ -4,6 +4,7 @@ def orso_estia_corrections() -> OrsoCorrectionList: + """Return list of corrections applied in Estia reductions.""" return OrsoCorrectionList( [ "chopper ToF correction", diff --git a/src/ess/estia/workflow.py b/src/ess/estia/workflow.py index f6ab5f03..0a11e519 100644 --- a/src/ess/estia/workflow.py +++ b/src/ess/estia/workflow.py @@ -53,6 +53,7 @@ def mcstas_default_parameters() -> dict: + """Return default parameters for the McStas Estia workflow.""" return { supermirror.MValue: sc.scalar(5, unit=sc.units.dimensionless), # The reference sample in the McStas simulation has R=1 everywhere @@ -71,6 +72,7 @@ def mcstas_default_parameters() -> dict: def default_parameters() -> dict: + """Return default parameters for the NeXus Estia workflow.""" return { NeXusDetectorName: "multiblade_detector", SampleRotationOffset[RunType]: sc.scalar(0.0, unit='deg'), diff --git a/src/ess/offspec/conversions.py b/src/ess/offspec/conversions.py index cc373068..984c7e80 100644 --- a/src/ess/offspec/conversions.py +++ b/src/ess/offspec/conversions.py @@ -10,6 +10,7 @@ def coordinate_transformation_graph_sample() -> CoordTransformationGraph[SampleRun]: + """Build coordinate transformation graph for OFFSPEC sample runs.""" return { **beamline.beamline(scatter=True), **tof.elastic_wavelength("tof"), @@ -19,6 +20,7 @@ def coordinate_transformation_graph_sample() -> CoordTransformationGraph[SampleR def coordinate_transformation_graph_reference() -> CoordTransformationGraph[ ReferenceRun ]: + """Build coordinate transformation graph for OFFSPEC reference runs.""" return { **beamline.beamline(scatter=False), **tof.elastic_wavelength("tof"), diff --git a/src/ess/offspec/data.py b/src/ess/offspec/data.py index f3c15cf2..886e61ee 100644 --- a/src/ess/offspec/data.py +++ b/src/ess/offspec/data.py @@ -15,10 +15,12 @@ def offspec_sample_run() -> Filename[SampleRun]: + """Return path to the OFFSPEC sample run example file.""" return Filename[SampleRun](_registry.get_path("sample.h5")) def offspec_direct_beam_run() -> Filename[ReferenceRun]: + """Return path to the OFFSPEC direct-beam reference file.""" return Filename[ReferenceRun](_registry.get_path("direct_beam.h5")) diff --git a/src/ess/offspec/load.py b/src/ess/offspec/load.py index c617a580..a10eea7d 100644 --- a/src/ess/offspec/load.py +++ b/src/ess/offspec/load.py @@ -15,6 +15,7 @@ def load_offspec_events( filename: Filename[RunType], ) -> RawDetector[RunType]: + """Load OFFSPEC event data from an HDF5 file.""" full = sc.io.load_hdf5(filename) da = full['data'] da.coords['theta'] = full.pop('Theta')[-1].data @@ -27,6 +28,7 @@ def load_offspec_monitor( graph: CoordTransformationGraph[ReferenceRun], monitor_name: NeXusMonitorName, ) -> MonitorData[RunType]: + """Load OFFSPEC monitor data and convert to wavelength.""" full = sc.io.load_hdf5(filename) mon = full["monitors"][monitor_name]["data"].transform_coords( "wavelength", graph=graph diff --git a/src/ess/reflectometry/corrections.py b/src/ess/reflectometry/corrections.py index 71f23c96..13eccf15 100644 --- a/src/ess/reflectometry/corrections.py +++ b/src/ess/reflectometry/corrections.py @@ -51,6 +51,7 @@ def correct_by_proton_current(da: sc.DataArray) -> sc.DataArray: def correct_sample_rotation( mu: RawSampleRotation[RunType], mu_offset: SampleRotationOffset[RunType] ) -> SampleRotation[RunType]: + """Apply a rotation offset to the raw sample rotation.""" return mu + mu_offset.to(unit=mu.unit, dtype='float64') diff --git a/src/ess/reflectometry/figures.py b/src/ess/reflectometry/figures.py index 742e7fe0..2b869da8 100644 --- a/src/ess/reflectometry/figures.py +++ b/src/ess/reflectometry/figures.py @@ -273,6 +273,7 @@ def wavelength_theta_diagnostic_figure( wbins: WavelengthBins, thbins: ThetaBins[SampleRun], ) -> WavelengthThetaFigure: + """Create a wavelength-theta diagnostic figure for a sample/reference pair.""" s = da.hist(wavelength=wbins, theta=thbins) r = ref.hist(theta=s.coords['theta'], wavelength=s.coords['wavelength']).data return wavelength_theta_figure(s / r) @@ -284,6 +285,7 @@ def q_theta_diagnostic_figure( thbins: ThetaBins[SampleRun], qbins: QBins, ) -> QThetaFigure: + """Create a Q-theta diagnostic figure for a sample/reference pair.""" s = da.hist(theta=thbins, Q=qbins) r = ref.hist(theta=s.coords['theta'], Q=s.coords['Q']).data return q_theta_figure(s / r) @@ -292,6 +294,7 @@ def q_theta_diagnostic_figure( def wavelength_z_diagnostic_figure( da: ReflectivityOverZW, ) -> WavelengthZIndexFigure: + """Create a wavelength vs. detector z-index diagnostic figure.""" return wavelength_z_figure(da) @@ -301,6 +304,7 @@ def diagnostic_view( qth: QThetaFigure, ioq: ReflectivityOverQ, ) -> ReflectivityDiagnosticsView: + """Compose a multi-panel diagnostic view for reflectometry reduction.""" ioq = ioq.hist().plot(norm="log") return (ioq + laz) / (lath + qth) diff --git a/src/ess/reflectometry/gui.py b/src/ess/reflectometry/gui.py index 3dc456fc..11cc4f30 100644 --- a/src/ess/reflectometry/gui.py +++ b/src/ess/reflectometry/gui.py @@ -67,6 +67,8 @@ def _get_selected_rows(grid): class DetectorView(widgets.HBox): + """Interactive detector view for selected runs.""" + is_active_tab = Bool(False).tag(sync=True) def __init__( @@ -153,6 +155,8 @@ def run_workflow(self): class NexusExplorer(widgets.VBox): + """Interactive browser for inspecting NeXus file structure.""" + def __init__( self, runs_table: DataGrid, run_to_filepath: Callable[[str], str], **kwargs ): @@ -701,6 +705,8 @@ def hide_progress(self): class AmorBatchReductionGUI(ReflectometryBatchReductionGUI): + """Amor-specific batch reduction GUI.""" + def __init__(self): super().__init__() self.nexus_explorer = NexusExplorer(self.runs_table, self.get_filepath_from_run) diff --git a/src/ess/reflectometry/load.py b/src/ess/reflectometry/load.py index 51495854..3485bf85 100644 --- a/src/ess/reflectometry/load.py +++ b/src/ess/reflectometry/load.py @@ -13,6 +13,15 @@ def load_nx(group: snx.Group | str | Path, *paths: str): + """Yield NeXus groups or arrays at the provided paths. + + Parameters + ---------- + group: + NeXus group or file path. + *paths: + One or more NeXus paths to load. + """ with open_nexus_file(group) as g: for path in paths: for p in path.strip('/').split('/'): @@ -38,6 +47,15 @@ def _unique_child_group( def load_h5(group: h5py.Group | str, *paths: str): + """Yield HDF5 groups or datasets at the provided paths. + + Parameters + ---------- + group: + HDF5 group or file path. + *paths: + One or more HDF5 paths to load. + """ if isinstance(group, str): with h5py.File(group) as group: yield from load_h5(group, *paths) @@ -66,9 +84,19 @@ def _unique_child_group_h5( def save_reference(pl: sciline.Pipeline, fname: str): + """Compute and save a reduced reference to HDF5. + + Parameters + ---------- + pl: + Sciline pipeline that can compute ``ReducedReference``. + fname: + Output file path. + """ pl.compute(ReducedReference).save_hdf5(fname) return fname def load_reference(fname: ReferenceFilePath) -> ReducedReference: + """Load a reduced reference from HDF5.""" return sc.io.hdf5.load_hdf5(fname) diff --git a/src/ess/reflectometry/normalization.py b/src/ess/reflectometry/normalization.py index 1e864c18..876c65e2 100644 --- a/src/ess/reflectometry/normalization.py +++ b/src/ess/reflectometry/normalization.py @@ -21,12 +21,30 @@ def reduce_to_q(da: sc.DataArray, qbins: int | sc.Variable): + """Reduce data to a histogram over Q. + + Parameters + ---------- + da: + Input events or histogram. + qbins: + Q bin edges or number of bins. + """ if da.bins: return da.bin(Q=qbins, dim=da.dims) return da.hist(Q=qbins) def reduce_from_events_to_lz(da: sc.DataArray, wbins: int | sc.Variable): + """Reduce events to wavelength vs. detector strip bins. + + Parameters + ---------- + da: + Input events. + wbins: + Wavelength bin edges or number of bins. + """ out = da.bin(wavelength=wbins, dim=('strip',)) if 'position' in da.coords: out.coords['position'] = da.coords['position'].mean('strip') diff --git a/src/ess/reflectometry/types.py b/src/ess/reflectometry/types.py index 62f2a972..9fbc444e 100644 --- a/src/ess/reflectometry/types.py +++ b/src/ess/reflectometry/types.py @@ -31,7 +31,7 @@ class CoordTransformationGraph(sciline.Scope[RunType, dict], dict): - pass + """Coordinate transformation graph for converting raw to physical coordinates.""" class RawChopper(sciline.Scope[RunType, sc.DataGroup], sc.DataGroup): From ec851575b893f7015be6172ff6bffe941da718c9 Mon Sep 17 00:00:00 2001 From: Johannes Kasimir Date: Wed, 4 Feb 2026 16:11:32 +0100 Subject: [PATCH 2/3] docs: add missing return types --- src/ess/amor/conversions.py | 10 ++++++---- src/ess/estia/data.py | 10 +++++----- src/ess/estia/mcstas.py | 6 +++--- src/ess/reflectometry/figures.py | 7 ++++--- src/ess/reflectometry/load.py | 11 ++++++++--- src/ess/reflectometry/normalization.py | 6 ++++-- 6 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/ess/amor/conversions.py b/src/ess/amor/conversions.py index 24c4ce8a..6a11da97 100644 --- a/src/ess/amor/conversions.py +++ b/src/ess/amor/conversions.py @@ -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. @@ -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. @@ -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. @@ -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 diff --git a/src/ess/estia/data.py b/src/ess/estia/data.py index 6d57d496..52b11deb 100644 --- a/src/ess/estia/data.py +++ b/src/ess/estia/data.py @@ -131,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): @@ -156,12 +156,12 @@ 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( @@ -178,7 +178,7 @@ 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')) @@ -199,7 +199,7 @@ 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') diff --git a/src/ess/estia/mcstas.py b/src/ess/estia/mcstas.py index e8b31126..a2ac8c8a 100644 --- a/src/ess/estia/mcstas.py +++ b/src/ess/estia/mcstas.py @@ -24,7 +24,7 @@ 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 @@ -42,7 +42,7 @@ 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 = [] @@ -83,7 +83,7 @@ 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: diff --git a/src/ess/reflectometry/figures.py b/src/ess/reflectometry/figures.py index 2b869da8..5d0bd56e 100644 --- a/src/ess/reflectometry/figures.py +++ b/src/ess/reflectometry/figures.py @@ -4,6 +4,7 @@ import numpy as np import plopp as pp import scipp as sc +from plopp.core.typing import FigureLike from .types import ( QBins, @@ -63,7 +64,7 @@ def wavelength_theta_figure( q_edges_to_display: Sequence[sc.Variable] = (), linewidth: float = 1.0, **kwargs, -): +) -> FigureLike: ''' Creates a figure displaying a histogram over :math:`\\theta` and :math:`\\lambda`. @@ -152,7 +153,7 @@ def q_theta_figure( q_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None, theta_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None, **kwargs, -): +) -> FigureLike: ''' Creates a figure displaying a histogram over :math:`\\theta` and :math:`Q`. @@ -216,7 +217,7 @@ def wavelength_z_figure( *, wavelength_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None, **kwargs, -): +) -> FigureLike: ''' Creates a figure displaying a histogram over the detector "Z"-direction, corresponding to the combination of the logical detector coordinates diff --git a/src/ess/reflectometry/load.py b/src/ess/reflectometry/load.py index 3485bf85..a70f573f 100644 --- a/src/ess/reflectometry/load.py +++ b/src/ess/reflectometry/load.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2024 Scipp contributors (https://github.com/scipp) +from collections.abc import Iterator from pathlib import Path import h5py @@ -12,7 +13,9 @@ from .types import ReducedReference, ReferenceFilePath -def load_nx(group: snx.Group | str | Path, *paths: str): +def load_nx( + group: snx.Group | str | Path, *paths: str +) -> Iterator[sc.DataGroup | sc.DataArray | None]: """Yield NeXus groups or arrays at the provided paths. Parameters @@ -46,7 +49,9 @@ def _unique_child_group( return next(iter(children.values())) # type: ignore[return-value] -def load_h5(group: h5py.Group | str, *paths: str): +def load_h5( + group: h5py.Group | str, *paths: str +) -> Iterator[h5py.Group | h5py.Dataset | None]: """Yield HDF5 groups or datasets at the provided paths. Parameters @@ -83,7 +88,7 @@ def _unique_child_group_h5( return out -def save_reference(pl: sciline.Pipeline, fname: str): +def save_reference(pl: sciline.Pipeline, fname: str) -> str: """Compute and save a reduced reference to HDF5. Parameters diff --git a/src/ess/reflectometry/normalization.py b/src/ess/reflectometry/normalization.py index 876c65e2..cb6bfb0c 100644 --- a/src/ess/reflectometry/normalization.py +++ b/src/ess/reflectometry/normalization.py @@ -20,7 +20,7 @@ ) -def reduce_to_q(da: sc.DataArray, qbins: int | sc.Variable): +def reduce_to_q(da: sc.DataArray, qbins: int | sc.Variable) -> sc.DataArray: """Reduce data to a histogram over Q. Parameters @@ -35,7 +35,9 @@ def reduce_to_q(da: sc.DataArray, qbins: int | sc.Variable): return da.hist(Q=qbins) -def reduce_from_events_to_lz(da: sc.DataArray, wbins: int | sc.Variable): +def reduce_from_events_to_lz( + da: sc.DataArray, wbins: int | sc.Variable +) -> sc.DataArray: """Reduce events to wavelength vs. detector strip bins. Parameters From f005edea606dbc0c762950b1cd2e21bfa556ddc4 Mon Sep 17 00:00:00 2001 From: Johannes Kasimir Date: Wed, 4 Feb 2026 16:15:05 +0100 Subject: [PATCH 3/3] docs: add missing return section in docsstrings --- src/ess/reflectometry/figures.py | 32 ++++++++++++++++++++++---- src/ess/reflectometry/load.py | 23 +++++++++++++++++- src/ess/reflectometry/normalization.py | 10 ++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/ess/reflectometry/figures.py b/src/ess/reflectometry/figures.py index 5d0bd56e..a5fbe252 100644 --- a/src/ess/reflectometry/figures.py +++ b/src/ess/reflectometry/figures.py @@ -274,7 +274,13 @@ def wavelength_theta_diagnostic_figure( wbins: WavelengthBins, thbins: ThetaBins[SampleRun], ) -> WavelengthThetaFigure: - """Create a wavelength-theta diagnostic figure for a sample/reference pair.""" + """Create a wavelength-theta diagnostic figure for a sample/reference pair. + + Returns + ------- + : + Diagnostic wavelength-theta figure. + """ s = da.hist(wavelength=wbins, theta=thbins) r = ref.hist(theta=s.coords['theta'], wavelength=s.coords['wavelength']).data return wavelength_theta_figure(s / r) @@ -286,7 +292,13 @@ def q_theta_diagnostic_figure( thbins: ThetaBins[SampleRun], qbins: QBins, ) -> QThetaFigure: - """Create a Q-theta diagnostic figure for a sample/reference pair.""" + """Create a Q-theta diagnostic figure for a sample/reference pair. + + Returns + ------- + : + Diagnostic Q-theta figure. + """ s = da.hist(theta=thbins, Q=qbins) r = ref.hist(theta=s.coords['theta'], Q=s.coords['Q']).data return q_theta_figure(s / r) @@ -295,7 +307,13 @@ def q_theta_diagnostic_figure( def wavelength_z_diagnostic_figure( da: ReflectivityOverZW, ) -> WavelengthZIndexFigure: - """Create a wavelength vs. detector z-index diagnostic figure.""" + """Create a wavelength vs. detector z-index diagnostic figure. + + Returns + ------- + : + Diagnostic wavelength vs. z-index figure. + """ return wavelength_z_figure(da) @@ -305,7 +323,13 @@ def diagnostic_view( qth: QThetaFigure, ioq: ReflectivityOverQ, ) -> ReflectivityDiagnosticsView: - """Compose a multi-panel diagnostic view for reflectometry reduction.""" + """Compose a multi-panel diagnostic view for reflectometry reduction. + + Returns + ------- + : + Composite diagnostics view. + """ ioq = ioq.hist().plot(norm="log") return (ioq + laz) / (lath + qth) diff --git a/src/ess/reflectometry/load.py b/src/ess/reflectometry/load.py index a70f573f..c8dbfc28 100644 --- a/src/ess/reflectometry/load.py +++ b/src/ess/reflectometry/load.py @@ -24,6 +24,11 @@ def load_nx( NeXus group or file path. *paths: One or more NeXus paths to load. + + Returns + ------- + : + Iterator of loaded NeXus objects (group or array), or ``None`` if missing. """ with open_nexus_file(group) as g: for path in paths: @@ -60,6 +65,11 @@ def load_h5( HDF5 group or file path. *paths: One or more HDF5 paths to load. + + Returns + ------- + : + Iterator of loaded HDF5 objects (group or dataset), or ``None`` if missing. """ if isinstance(group, str): with h5py.File(group) as group: @@ -97,11 +107,22 @@ def save_reference(pl: sciline.Pipeline, fname: str) -> str: Sciline pipeline that can compute ``ReducedReference``. fname: Output file path. + + Returns + ------- + : + The output file path. """ pl.compute(ReducedReference).save_hdf5(fname) return fname def load_reference(fname: ReferenceFilePath) -> ReducedReference: - """Load a reduced reference from HDF5.""" + """Load a reduced reference from HDF5. + + Returns + ------- + : + The reduced reference data. + """ return sc.io.hdf5.load_hdf5(fname) diff --git a/src/ess/reflectometry/normalization.py b/src/ess/reflectometry/normalization.py index cb6bfb0c..e2d34e8c 100644 --- a/src/ess/reflectometry/normalization.py +++ b/src/ess/reflectometry/normalization.py @@ -29,6 +29,11 @@ def reduce_to_q(da: sc.DataArray, qbins: int | sc.Variable) -> sc.DataArray: Input events or histogram. qbins: Q bin edges or number of bins. + + Returns + ------- + : + Histogrammed data over Q. """ if da.bins: return da.bin(Q=qbins, dim=da.dims) @@ -46,6 +51,11 @@ def reduce_from_events_to_lz( Input events. wbins: Wavelength bin edges or number of bins. + + Returns + ------- + : + Binned data over wavelength and detector strip. """ out = da.bin(wavelength=wbins, dim=('strip',)) if 'position' in da.coords: