Source code for ess.isissans.sans2d
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
from typing import NewType
import sciline
import scipp as sc
from ess.reduce.nexus.workflow import GenericNeXusWorkflow
from ess.reduce.workflow import register_workflow
from ess.sans import providers as sans_providers
from ess.sans.parameters import typical_outputs
from ess.sans.types import BeamCenter, CalibratedDetector, DetectorMasks, SampleRun
from .general import default_parameters
from .io import load_tutorial_direct_beam, load_tutorial_run
from .mantidio import providers as mantid_providers
DetectorEdgeMask = NewType('DetectorEdgeMask', sc.Variable | None)
"""Detector edge mask"""
LowCountThreshold = NewType('LowCountThreshold', sc.Variable)
"""Threshold below which detector pixels should be masked
(low-counts on the edges of the detector panel, and the beam stop)"""
SampleHolderMask = NewType('SampleHolderMask', sc.Variable | None)
"""Sample holder mask"""
[docs]
def detector_edge_mask(
beam_center: BeamCenter, sample: CalibratedDetector[SampleRun]
) -> DetectorEdgeMask:
# These values were determined by hand before the beam center was available.
# We therefore undo the shift introduced by the beam center.
raw_pos = sample.coords['position'] + beam_center
mask_edges = (sc.abs(raw_pos.fields.x) > sc.scalar(0.48, unit='m')) | (
sc.abs(raw_pos.fields.y) > sc.scalar(0.45, unit='m')
)
return DetectorEdgeMask(mask_edges)
[docs]
def sample_holder_mask(
beam_center: BeamCenter,
sample: CalibratedDetector[SampleRun],
low_counts_threshold: LowCountThreshold,
) -> SampleHolderMask:
# These values were determined by hand before the beam center was available.
# We therefore undo the shift introduced by the beam center.
raw_pos = sample.coords['position'] + beam_center
summed = sample.hist()
holder_mask = (
(summed.data < low_counts_threshold)
& (raw_pos.fields.x > sc.scalar(0, unit='m'))
& (raw_pos.fields.x < sc.scalar(0.42, unit='m'))
& (raw_pos.fields.y < sc.scalar(0.05, unit='m'))
& (raw_pos.fields.y > sc.scalar(-0.15, unit='m'))
)
return SampleHolderMask(holder_mask)
[docs]
def to_detector_masks(
edge_mask: DetectorEdgeMask, holder_mask: SampleHolderMask
) -> DetectorMasks:
"""Gather detector masks.
Unlike :py:func:`ess.sans.masking.to_detector_mask`, this function does not rely on
mapping using a table of mask filenames. Instead it directly returns a dictionary
if multiple masks.
Parameters
----------
edge_mask:
Mask for detector edges.
holder_mask:
Mask for sample holder.
"""
masks = {}
if edge_mask is not None:
masks['edges'] = edge_mask
if holder_mask is not None:
masks['holder_mask'] = holder_mask
return DetectorMasks(masks)
providers = (detector_edge_mask, sample_holder_mask, to_detector_masks)
[docs]
@register_workflow
def Sans2dWorkflow() -> sciline.Pipeline:
"""Create Sans2d workflow with default parameters."""
from . import providers as isis_providers
# Note that the actual NeXus loading in this workflow will not be used for the
# ISIS files, the providers inserted below will replace those steps.
workflow = GenericNeXusWorkflow()
for provider in sans_providers + isis_providers + mantid_providers + providers:
workflow.insert(provider)
for key, param in default_parameters().items():
workflow[key] = param
workflow.typical_outputs = typical_outputs
return workflow
[docs]
@register_workflow
def Sans2dTutorialWorkflow() -> sciline.Pipeline:
"""
Create Sans2d tutorial workflow.
Equivalent to :func:`Sans2dWorkflow`, but with loaders for tutorial data instead
of Mantid-based loaders.
"""
workflow = Sans2dWorkflow()
workflow.insert(load_tutorial_run)
workflow.insert(load_tutorial_direct_beam)
return workflow