diff --git a/src/esssans/conversions.py b/src/esssans/conversions.py index 12a147f8..4e98904c 100644 --- a/src/esssans/conversions.py +++ b/src/esssans/conversions.py @@ -22,6 +22,7 @@ QxyBins, RawMonitor, RunType, + ScatteringRunType, WavelengthMask, WavelengthMonitor, ) @@ -204,8 +205,8 @@ def monitor_to_wavelength( def calibrate_positions( - detector: MaskedData[RunType], beam_center: BeamCenter -) -> CalibratedMaskedData[RunType]: + detector: MaskedData[ScatteringRunType], beam_center: BeamCenter +) -> CalibratedMaskedData[ScatteringRunType]: """ Calibrate pixel positions. @@ -220,17 +221,17 @@ def calibrate_positions( # for RawData, MaskedData, ... no reason to restrict necessarily. # Would we be fine with just choosing on option, or will this get in the way for users? def detector_to_wavelength( - detector: CalibratedMaskedData[RunType], + detector: CalibratedMaskedData[ScatteringRunType], graph: ElasticCoordTransformGraph, -) -> CleanWavelength[RunType, Numerator]: - return CleanWavelength[RunType, Numerator]( +) -> CleanWavelength[ScatteringRunType, Numerator]: + return CleanWavelength[ScatteringRunType, Numerator]( detector.transform_coords('wavelength', graph=graph) ) def mask_wavelength( - da: CleanWavelength[RunType, IofQPart], mask: Optional[WavelengthMask] -) -> CleanWavelengthMasked[RunType, IofQPart]: + da: CleanWavelength[ScatteringRunType, IofQPart], mask: Optional[WavelengthMask] +) -> CleanWavelengthMasked[ScatteringRunType, IofQPart]: if mask is not None: # If we have binned data and the wavelength coord is multi-dimensional, we need # to make a single wavelength bin before we can mask the range. @@ -239,19 +240,19 @@ def mask_wavelength( if (dim in da.bins.coords) and (dim in da.coords): da = da.bin({dim: 1}) da = mask_range(da, mask=mask) - return CleanWavelengthMasked[RunType, IofQPart](da) + return CleanWavelengthMasked[ScatteringRunType, IofQPart](da) def compute_Q( - data: CleanWavelengthMasked[RunType, IofQPart], + data: CleanWavelengthMasked[ScatteringRunType, IofQPart], graph: ElasticCoordTransformGraph, compute_Qxy: Optional[QxyBins], -) -> CleanQ[RunType, IofQPart]: +) -> CleanQ[ScatteringRunType, IofQPart]: """ Convert a data array from wavelength to Q. """ # Keep naming of wavelength dim, subsequent steps use a (Q[xy], wavelength) binning. - return CleanQ[RunType, IofQPart]( + return CleanQ[ScatteringRunType, IofQPart]( data.transform_coords( ('Qx', 'Qy') if compute_Qxy else 'Q', graph=graph, diff --git a/src/esssans/i_of_q.py b/src/esssans/i_of_q.py index 6db4b9a3..69ee1670 100644 --- a/src/esssans/i_of_q.py +++ b/src/esssans/i_of_q.py @@ -27,6 +27,7 @@ ReturnEvents, RunType, SampleRun, + ScatteringRunType, UncertaintyBroadcastMode, WavelengthBins, WavelengthMonitor, @@ -132,11 +133,11 @@ def resample_direct_beam( def merge_spectra( - data: CleanQ[RunType, IofQPart], + data: CleanQ[ScatteringRunType, IofQPart], q_bins: Optional[QBins], qxy_bins: Optional[QxyBins], dims_to_keep: Optional[DimsToKeep], -) -> CleanSummedQ[RunType, IofQPart]: +) -> CleanSummedQ[ScatteringRunType, IofQPart]: """ Merges all spectra: @@ -210,7 +211,7 @@ def merge_spectra( .group(*[flat.coords[dim] for dim in flat.dims if dim != helper_dim]) .hist(**edges) ) - return CleanSummedQ[RunType, IofQPart](out.squeeze()) + return CleanSummedQ[ScatteringRunType, IofQPart](out.squeeze()) def subtract_background( diff --git a/src/esssans/isis/components.py b/src/esssans/isis/components.py index dfcd5277..8a0730c5 100644 --- a/src/esssans/isis/components.py +++ b/src/esssans/isis/components.py @@ -5,11 +5,11 @@ import sciline import scipp as sc -from ..types import RawData, RunType +from ..types import RawData, ScatteringRunType class RawDataWithComponentUserOffsets( - sciline.Scope[RunType, sc.DataArray], sc.DataArray + sciline.Scope[ScatteringRunType, sc.DataArray], sc.DataArray ): """Raw data with applied user configuration for component positions.""" @@ -19,10 +19,10 @@ class RawDataWithComponentUserOffsets( def apply_component_user_offsets_to_raw_data( - data: RawData[RunType], + data: RawData[ScatteringRunType], sample_offset: SampleOffset, detector_bank_offset: DetectorBankOffset, -) -> RawDataWithComponentUserOffsets[RunType]: +) -> RawDataWithComponentUserOffsets[ScatteringRunType]: """Apply user configuration to raw data. Parameters @@ -41,4 +41,4 @@ def apply_component_user_offsets_to_raw_data( ) pos = data.coords['position'] data.coords['position'] = pos + detector_bank_offset.to(unit=pos.unit, copy=False) - return RawDataWithComponentUserOffsets[RunType](data) + return RawDataWithComponentUserOffsets[ScatteringRunType](data) diff --git a/src/esssans/isis/masking.py b/src/esssans/isis/masking.py index 2d4f27ab..fe377fdb 100644 --- a/src/esssans/isis/masking.py +++ b/src/esssans/isis/masking.py @@ -6,7 +6,7 @@ import sciline import scipp as sc -from ..types import MaskedData, RawData, RunType, SampleRun +from ..types import MaskedData, RawData, SampleRun, ScatteringRunType from .components import RawDataWithComponentUserOffsets from .io import MaskedDetectorIDs @@ -30,9 +30,9 @@ def to_pixel_mask(data: RawData[SampleRun], masked: MaskedDetectorIDs) -> PixelM def apply_pixel_masks( - data: RawDataWithComponentUserOffsets[RunType], + data: RawDataWithComponentUserOffsets[ScatteringRunType], masks: sciline.Series[str, PixelMask], -) -> MaskedData[RunType]: +) -> MaskedData[ScatteringRunType]: """Apply pixel-specific masks to raw data. This depends on the configured raw data (which has been configured with component @@ -49,7 +49,7 @@ def apply_pixel_masks( data = data.copy(deep=False) for name, mask in masks.items(): data.masks[name] = mask - return MaskedData[RunType](data) + return MaskedData[ScatteringRunType](data) providers = ( diff --git a/src/esssans/loki/general.py b/src/esssans/loki/general.py index 1575bf25..1998d4f4 100644 --- a/src/esssans/loki/general.py +++ b/src/esssans/loki/general.py @@ -17,6 +17,7 @@ RawData, RawMonitor, RunType, + ScatteringRunType, TransformationPath, Transmission, ) @@ -35,10 +36,10 @@ def get_detector_data( - dg: LoadedFileContents[RunType], detector_name: NeXusDetectorName -) -> RawData[RunType]: + dg: LoadedFileContents[ScatteringRunType], detector_name: NeXusDetectorName +) -> RawData[ScatteringRunType]: da = dg[NEXUS_INSTRUMENT_PATH][detector_name][f'{detector_name}_events'] - return RawData[RunType](da) + return RawData[ScatteringRunType](da) def get_monitor_data( @@ -51,19 +52,19 @@ def get_monitor_data( def detector_pixel_shape( - dg: LoadedFileContents[RunType], detector_name: NeXusDetectorName -) -> DetectorPixelShape[RunType]: - return DetectorPixelShape[RunType]( + dg: LoadedFileContents[ScatteringRunType], detector_name: NeXusDetectorName +) -> DetectorPixelShape[ScatteringRunType]: + return DetectorPixelShape[ScatteringRunType]( dg[NEXUS_INSTRUMENT_PATH][detector_name]['pixel_shape'] ) def detector_lab_frame_transform( - dg: LoadedFileContents[RunType], + dg: LoadedFileContents[ScatteringRunType], detector_name: NeXusDetectorName, transform_path: TransformationPath, -) -> LabFrameTransform[RunType]: - return LabFrameTransform[RunType]( +) -> LabFrameTransform[ScatteringRunType]: + return LabFrameTransform[ScatteringRunType]( dg[NEXUS_INSTRUMENT_PATH][detector_name][transform_path] ) diff --git a/src/esssans/loki/masking.py b/src/esssans/loki/masking.py index 03c6f34f..b92c1a43 100644 --- a/src/esssans/loki/masking.py +++ b/src/esssans/loki/masking.py @@ -13,8 +13,8 @@ BeamStopRadius, MaskedData, RawData, - RunType, SampleRun, + ScatteringRunType, ) DetectorLowCountsStrawMask = NewType('DetectorLowCountsStrawMask', sc.Variable) @@ -80,11 +80,11 @@ def detector_tube_edge_mask( def mask_detectors( - da: RawData[RunType], + da: RawData[ScatteringRunType], lowcounts_straw_mask: Optional[DetectorLowCountsStrawMask], beam_stop_mask: Optional[DetectorBeamStopMask], tube_edge_mask: Optional[DetectorTubeEdgeMask], -) -> MaskedData[RunType]: +) -> MaskedData[ScatteringRunType]: """Apply pixel-specific masks to raw data. Parameters @@ -105,7 +105,7 @@ def mask_detectors( da.masks['beam_stop'] = beam_stop_mask if tube_edge_mask is not None: da.masks['tube_edges'] = tube_edge_mask - return MaskedData[RunType](da) + return MaskedData[ScatteringRunType](da) providers = ( diff --git a/src/esssans/normalization.py b/src/esssans/normalization.py index 46c1078f..d476abbc 100644 --- a/src/esssans/normalization.py +++ b/src/esssans/normalization.py @@ -22,7 +22,7 @@ Numerator, ProcessedWavelengthBands, ReturnEvents, - RunType, + ScatteringRunType, SolidAngle, Transmission, TransmissionFraction, @@ -39,10 +39,10 @@ def solid_angle( - data: CalibratedMaskedData[RunType], - pixel_shape: DetectorPixelShape[RunType], - transform: LabFrameTransform[RunType], -) -> SolidAngle[RunType]: + data: CalibratedMaskedData[ScatteringRunType], + pixel_shape: DetectorPixelShape[ScatteringRunType], + transform: LabFrameTransform[ScatteringRunType], +) -> SolidAngle[ScatteringRunType]: """ Solid angle for cylindrical pixels. @@ -80,7 +80,7 @@ def solid_angle( radius=radius, length=length, ) - return SolidAngle[RunType]( + return SolidAngle[ScatteringRunType]( concepts.rewrap_reduced_data( prototype=data, data=omega, dim=set(data.dims) - set(omega.dims) ) @@ -127,11 +127,13 @@ def _approximate_solid_angle_for_cylinder_shaped_pixel_of_detector( def transmission_fraction( - sample_incident_monitor: CleanMonitor[TransmissionRun[RunType], Incident], - sample_transmission_monitor: CleanMonitor[TransmissionRun[RunType], Transmission], + sample_incident_monitor: CleanMonitor[TransmissionRun[ScatteringRunType], Incident], + sample_transmission_monitor: CleanMonitor[ + TransmissionRun[ScatteringRunType], Transmission + ], direct_incident_monitor: CleanMonitor[EmptyBeamRun, Incident], direct_transmission_monitor: CleanMonitor[EmptyBeamRun, Transmission], -) -> TransmissionFraction[RunType]: +) -> TransmissionFraction[ScatteringRunType]: """ Approximation based on equations in `CalculateTransmission `_ @@ -160,7 +162,7 @@ def transmission_fraction( frac = (sample_transmission_monitor / direct_transmission_monitor) * ( direct_incident_monitor / sample_incident_monitor ) - return TransmissionFraction[RunType](frac) + return TransmissionFraction[ScatteringRunType](frac) _broadcasters = { @@ -171,11 +173,11 @@ def transmission_fraction( def iofq_norm_wavelength_term( - incident_monitor: CleanMonitor[RunType, Incident], - transmission_fraction: TransmissionFraction[RunType], + incident_monitor: CleanMonitor[ScatteringRunType, Incident], + transmission_fraction: TransmissionFraction[ScatteringRunType], direct_beam: Optional[CleanDirectBeam], uncertainties: UncertaintyBroadcastMode, -) -> NormWavelengthTerm[RunType]: +) -> NormWavelengthTerm[ScatteringRunType]: """ Compute the wavelength-dependent contribution to the denominator term for the I(Q) normalization. @@ -224,14 +226,14 @@ def iofq_norm_wavelength_term( out = direct_beam * broadcast(out, sizes=direct_beam.sizes) # Convert wavelength coordinate to midpoints for future histogramming out.coords['wavelength'] = sc.midpoints(out.coords['wavelength']) - return NormWavelengthTerm[RunType](out) + return NormWavelengthTerm[ScatteringRunType](out) def iofq_denominator( - wavelength_term: NormWavelengthTerm[RunType], - solid_angle: SolidAngle[RunType], + wavelength_term: NormWavelengthTerm[ScatteringRunType], + solid_angle: SolidAngle[ScatteringRunType], uncertainties: UncertaintyBroadcastMode, -) -> CleanWavelength[RunType, Denominator]: +) -> CleanWavelength[ScatteringRunType, Denominator]: """ Compute the denominator term for the I(Q) normalization. @@ -297,7 +299,7 @@ def iofq_denominator( """ # noqa: E501 broadcast = _broadcasters[uncertainties] denominator = solid_angle * broadcast(wavelength_term, sizes=solid_angle.sizes) - return CleanWavelength[RunType, Denominator](denominator) + return CleanWavelength[ScatteringRunType, Denominator](denominator) def process_wavelength_bands( @@ -337,12 +339,12 @@ def process_wavelength_bands( def normalize( - numerator: CleanSummedQ[RunType, Numerator], - denominator: CleanSummedQ[RunType, Denominator], + numerator: CleanSummedQ[ScatteringRunType, Numerator], + denominator: CleanSummedQ[ScatteringRunType, Denominator], return_events: ReturnEvents, uncertainties: UncertaintyBroadcastMode, wavelength_bands: ProcessedWavelengthBands, -) -> IofQ[RunType]: +) -> IofQ[ScatteringRunType]: """ Perform normalization of counts as a function of Q. If the numerator contains events, we use the sc.lookup function to perform the @@ -406,7 +408,7 @@ def _reduce(da: sc.DataArray) -> sc.DataArray: ) elif numerator.bins is not None: numerator = numerator.hist() - return IofQ[RunType](numerator / denominator) + return IofQ[ScatteringRunType](numerator / denominator) providers = ( diff --git a/src/esssans/sans2d/general.py b/src/esssans/sans2d/general.py index 18f28a0a..0c8f7b5b 100644 --- a/src/esssans/sans2d/general.py +++ b/src/esssans/sans2d/general.py @@ -19,6 +19,7 @@ RunTitle, RunType, SampleRun, + ScatteringRunType, Transmission, ) @@ -29,9 +30,9 @@ def get_detector_data( - dg: LoadedFileContents[RunType], -) -> RawData[RunType]: - return RawData[RunType](dg['data']) + dg: LoadedFileContents[ScatteringRunType], +) -> RawData[ScatteringRunType]: + return RawData[ScatteringRunType](dg['data']) def get_monitor( @@ -52,7 +53,7 @@ def run_title(dg: LoadedFileContents[SampleRun]) -> RunTitle: return RunTitle(dg['run_title'].value) -def sans2d_tube_detector_pixel_shape() -> DetectorPixelShape[RunType]: +def sans2d_tube_detector_pixel_shape() -> DetectorPixelShape[ScatteringRunType]: # Pixel radius and length # found here: # https://github.com/mantidproject/mantid/blob/main/instrument/SANS2D_Definition_Tubes.xml @@ -79,7 +80,7 @@ def sans2d_tube_detector_pixel_shape() -> DetectorPixelShape[RunType]: return pixel_shape -def lab_frame_transform() -> LabFrameTransform[RunType]: +def lab_frame_transform() -> LabFrameTransform[ScatteringRunType]: # Rotate +y to -x return sc.spatial.rotation(value=[0, 0, 1 / 2**0.5, 1 / 2**0.5]) diff --git a/src/esssans/sans2d/masking.py b/src/esssans/sans2d/masking.py index 007ed446..c49a9371 100644 --- a/src/esssans/sans2d/masking.py +++ b/src/esssans/sans2d/masking.py @@ -6,7 +6,7 @@ import scipp as sc -from ..types import MaskedData, RawData, RunType, SampleRun +from ..types import MaskedData, RawData, SampleRun, ScatteringRunType DetectorEdgeMask = NewType('DetectorEdgeMask', sc.Variable) """Detector edge mask""" @@ -42,10 +42,10 @@ def sample_holder_mask( def mask_detectors( - da: RawData[RunType], + da: RawData[ScatteringRunType], edge_mask: Optional[DetectorEdgeMask], holder_mask: Optional[SampleHolderMask], -) -> MaskedData[RunType]: +) -> MaskedData[ScatteringRunType]: """Apply pixel-specific masks to raw data. Parameters @@ -62,7 +62,7 @@ def mask_detectors( da.masks['edges'] = edge_mask if holder_mask is not None: da.masks['holder_mask'] = holder_mask - return MaskedData[RunType](da) + return MaskedData[ScatteringRunType](da) providers = (detector_edge_mask, sample_holder_mask, mask_detectors) diff --git a/src/esssans/types.py b/src/esssans/types.py index 4ba01d91..700ec10b 100644 --- a/src/esssans/types.py +++ b/src/esssans/types.py @@ -24,21 +24,30 @@ """Sample run: the run with the sample placed in the solvent inside the sample holder. """ -RunType = TypeVar( - 'RunType', - BackgroundRun, - EmptyBeamRun, +ScatteringRunType = TypeVar( + 'ScatteringRunType', SampleRun, + BackgroundRun, ) -"""TypeVar used for specifying BackgroundRun, EmptyBeamRun or SampleRun""" -class TransmissionRun(sciline.Scope[RunType, int], int): - """Mapping between RunType and transmission run. +class TransmissionRun(sciline.Scope[ScatteringRunType, int], int): + """Mapping between ScatteringRunType and transmission run. In the case where no transmission run is provided, the transmission run should be the same as the measurement (sample or background) run.""" +RunType = TypeVar( + 'RunType', + BackgroundRun, + EmptyBeamRun, + SampleRun, + # Note that mypy does not seem to like this nesting, may need to find a workaround + TransmissionRun[SampleRun], + TransmissionRun[BackgroundRun], +) +"""TypeVar used for specifying BackgroundRun, EmptyBeamRun or SampleRun""" + # 1.2 Monitor types Incident = NewType('Incident', int) """Incident monitor""" @@ -169,7 +178,9 @@ class SourcePosition(sciline.Scope[RunType, sc.Variable], sc.Variable): """Direct beam""" -class TransmissionFraction(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class TransmissionFraction( + sciline.Scope[ScatteringRunType, sc.DataArray], sc.DataArray +): """Transmission fraction""" @@ -177,16 +188,16 @@ class TransmissionFraction(sciline.Scope[RunType, sc.DataArray], sc.DataArray): """Direct beam after resampling to required wavelength bins""" -class DetectorPixelShape(sciline.Scope[RunType, sc.DataGroup], sc.DataGroup): +class DetectorPixelShape(sciline.Scope[ScatteringRunType, sc.DataGroup], sc.DataGroup): """Geometry of the detector from description in nexus file.""" -class LabFrameTransform(sciline.Scope[RunType, sc.Variable], sc.Variable): +class LabFrameTransform(sciline.Scope[ScatteringRunType, sc.Variable], sc.Variable): """Coordinate transformation from detector local coordinates to the sample frame of reference.""" -class SolidAngle(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class SolidAngle(sciline.Scope[ScatteringRunType, sc.DataArray], sc.DataArray): """Solid angle of detector pixels seen from sample position""" @@ -194,24 +205,26 @@ class LoadedFileContents(sciline.Scope[RunType, sc.DataGroup], sc.DataGroup): """The entire contents of a loaded file""" -class RawData(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class RawData(sciline.Scope[ScatteringRunType, sc.DataArray], sc.DataArray): """Raw data""" -class MaskedData(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class MaskedData(sciline.Scope[ScatteringRunType, sc.DataArray], sc.DataArray): """Raw data with pixel-specific masks applied""" -class CalibratedMaskedData(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class CalibratedMaskedData( + sciline.Scope[ScatteringRunType, sc.DataArray], sc.DataArray +): """Raw data with pixel-specific masks applied and calibrated pixel positions""" -class NormWavelengthTerm(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class NormWavelengthTerm(sciline.Scope[ScatteringRunType, sc.DataArray], sc.DataArray): """Normalization term (numerator) for IofQ before scaling with solid-angle.""" class CleanWavelength( - sciline.ScopeTwoParams[RunType, IofQPart, sc.DataArray], sc.DataArray + sciline.ScopeTwoParams[ScatteringRunType, IofQPart, sc.DataArray], sc.DataArray ): """ Prerequisite for IofQ numerator or denominator. @@ -223,22 +236,24 @@ class CleanWavelength( class CleanWavelengthMasked( - sciline.ScopeTwoParams[RunType, IofQPart, sc.DataArray], sc.DataArray + sciline.ScopeTwoParams[ScatteringRunType, IofQPart, sc.DataArray], sc.DataArray ): """Result of applying wavelength masking to :py:class:`CleanWavelength`""" -class CleanQ(sciline.ScopeTwoParams[RunType, IofQPart, sc.DataArray], sc.DataArray): +class CleanQ( + sciline.ScopeTwoParams[ScatteringRunType, IofQPart, sc.DataArray], sc.DataArray +): """Result of converting :py:class:`CleanWavelengthMasked` to Q""" class CleanSummedQ( - sciline.ScopeTwoParams[RunType, IofQPart, sc.DataArray], sc.DataArray + sciline.ScopeTwoParams[ScatteringRunType, IofQPart, sc.DataArray], sc.DataArray ): """Result of histogramming/binning :py:class:`CleanQ` over all pixels into Q bins""" -class IofQ(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class IofQ(sciline.Scope[ScatteringRunType, sc.DataArray], sc.DataArray): """I(Q)"""