Skip to content

Commit

Permalink
Refactor to read xml directly
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonHeybrock committed Jan 18, 2024
1 parent cddd003 commit 0577a8d
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 69 deletions.
25 changes: 14 additions & 11 deletions src/esssans/isis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@
lab_frame_transform,
sans2d_tube_detector_pixel_shape,
)
from . import io, mantidio, masking
from .common import transmission_from_background_run, transmission_from_sample_run
from .mantidio import CalibrationFilename, Filename, PixelMask, PixelMaskFilename
from .mantidio import providers as mantidio_providers
from .masking import apply_pixel_masks
from .io import PixelMaskFilename
from .mantidio import CalibrationFilename, Filename
from .masking import PixelMask

providers = (
apply_pixel_masks,
get_detector_data,
get_monitor,
lab_frame_transform,
sans2d_tube_detector_pixel_shape,
) + mantidio_providers

del mantidio_providers
(
get_detector_data,
get_monitor,
lab_frame_transform,
sans2d_tube_detector_pixel_shape,
)
+ mantidio.providers
+ masking.providers
+ io.providers
)

__all__ = [
'Filename',
Expand Down
55 changes: 55 additions & 0 deletions src/esssans/isis/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
"""
File loading functions for ISIS data, NOT using Mantid.
"""
from typing import NewType

import scipp as sc

PixelMaskFilename = NewType('PixelMaskFilename', str)


MaskedDetectorIDs = NewType('MaskedDetectorIDs', sc.Variable)
"""1-D variable listing all masked detector IDs."""


def read_xml_detector_masking(filename: PixelMaskFilename) -> MaskedDetectorIDs:
"""Read a pixel mask from an XML file.
The format is as follows, where the detids are inclusive ranges of detector IDs:
<?xml version="1.0"?>
<detector-masking>
<group>
<detids>1400203-1400218,1401199,1402190-1402223</detids>
</group>
</detector-masking>
Parameters
----------
filename:
Path to the XML file.
"""
import xml.etree.ElementTree as ET # nosec

tree = ET.parse(filename) # nosec
root = tree.getroot()

masked_detids = []
for group in root.findall('group'):
for detids in group.findall('detids'):
for detid in detids.text.split(','):
detid = detid.strip()
if '-' in detid:
start, stop = detid.split('-')
masked_detids += list(range(int(start), int(stop) + 1))
else:
masked_detids.append(int(detid))

return MaskedDetectorIDs(
sc.array(dims=['detector_id'], values=masked_detids, unit=None, dtype='int32')
)


providers = (read_xml_detector_masking,)
62 changes: 6 additions & 56 deletions src/esssans/isis/mantidio.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import scipp as sc
import scippneutron as scn
from mantid.api import MatrixWorkspace, Workspace
from mantid.simpleapi import CopyInstrumentParameters, Load, LoadMask
from mantid.simpleapi import CopyInstrumentParameters, Load
from scipp.constants import g

from ..types import (
Expand All @@ -20,18 +20,9 @@
SampleRun,
)

IDFFilename = NewType('IDFFilename', str)
PixelMaskFilename = NewType('PixelMaskFilename', str)
PixelMask = NewType('PixelMask', sc.Variable)

PixelMaskWorkspace = NewType('PixelMaskWorkspace', MatrixWorkspace)

CalibrationFilename = NewType('CalibrationFilename', str)
CalibrationWorkspace = NewType('CalibrationWorkspace', MatrixWorkspace)

DetectorIDs = NewType('DetectorIDs', sc.Variable)
"""Detector ID corresponding to each spectrum."""


class DataWorkspace(sciline.Scope[RunType, MatrixWorkspace], MatrixWorkspace):
"""Workspace containing data"""
Expand All @@ -42,7 +33,7 @@ def _make_detector_info(ws: MatrixWorkspace) -> sc.DataGroup:
return sc.DataGroup(det_info.coords)


def get_detector_ids(ws: DataWorkspace[SampleRun]) -> DetectorIDs:
def get_detector_ids(ws: DataWorkspace[SampleRun]) -> sc.Variable:
det_info = _make_detector_info(ws)
dim = 'spectrum'
da = sc.DataArray(det_info['detector'], coords={dim: det_info[dim]})
Expand All @@ -52,32 +43,7 @@ def get_detector_ids(ws: DataWorkspace[SampleRun]) -> DetectorIDs:
sc.arange('detector', da.sizes['detector'], dtype='int32', unit=None),
):
raise ValueError("Spectrum-detector mapping is not 1:1, this is not supported.")
return DetectorIDs(da.data.rename_dims(detector='spectrum'))


def get_idf_filename(ws: DataWorkspace[SampleRun]) -> IDFFilename:
lines = repr(ws).split('\n')
line = [line for line in lines if 'Instrument from:' in line][0]
path = line.split('Instrument from:')[1].strip()
return IDFFilename(path)


def from_pixel_mask_workspace(ws: PixelMaskWorkspace, detids: DetectorIDs) -> PixelMask:
mask = scn.from_mantid(ws)['data']
# The 'spectrum' coord of `mask` is the spectrum *number*, but the detector info
# uses the spectrum *index*, i.e., a simple 0-based index.
mask.coords['spectrum'] = sc.arange(
'spectrum', mask.sizes['spectrum'], dtype='int32', unit=None
)
index_to_mask = sc.lookup(mask, dim='spectrum', mode='previous')
mask_det_info = _make_detector_info(ws)
det_mask = sc.DataArray(
index_to_mask[mask_det_info['spectrum']],
coords={'detector': mask_det_info['detector']},
)
det_mask = sc.sort(det_mask, 'detector')
detid_to_mask = sc.lookup(det_mask, dim='detector', mode='previous')
return PixelMask(detid_to_mask[detids])
return da.data.rename_dims(detector='spectrum')


def load_calibration(filename: CalibrationFilename) -> CalibrationWorkspace:
Expand All @@ -101,32 +67,20 @@ class Filename(sciline.Scope[RunType, str], str):


def from_data_workspace(
ws: DataWorkspace[RunType], calibration: CalibrationWorkspace
ws: DataWorkspace[RunType],
calibration: CalibrationWorkspace,
) -> LoadedFileContents[RunType]:
CopyInstrumentParameters(
InputWorkspace=calibration, OutputWorkspace=ws, StoreInADS=False
)
up = ws.getInstrument().getReferenceFrame().vecPointingUp()
dg = scn.from_mantid(ws)
dg['data'] = dg['data'].squeeze()
dg['data'].coords['detector_id'] = get_detector_ids(ws)
dg['data'].coords['gravity'] = sc.vector(value=-up) * g
return LoadedFileContents[RunType](dg)


def load_pixel_mask(
filename: PixelMaskFilename,
idf_path: IDFFilename,
ref_workspace: DataWorkspace[SampleRun],
) -> PixelMaskWorkspace:
mask = LoadMask(
Instrument=idf_path,
InputFile=str(filename),
RefWorkspace=ref_workspace,
StoreInADS=False,
)
return PixelMaskWorkspace(mask)


def load_run(filename: Filename[RunType]) -> DataWorkspace[RunType]:
loaded = Load(Filename=str(filename), LoadMonitors=True, StoreInADS=False)
if isinstance(loaded, Workspace):
Expand All @@ -140,11 +94,7 @@ def load_run(filename: Filename[RunType]) -> DataWorkspace[RunType]:

providers = (
from_data_workspace,
from_pixel_mask_workspace,
get_detector_ids,
get_idf_filename,
load_calibration,
load_direct_beam,
load_pixel_mask,
load_run,
)
32 changes: 30 additions & 2 deletions src/esssans/isis/masking.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
from typing import NewType

import numpy as np
import sciline
import scipp as sc

from ..types import MaskedData, RawData, RunType, SampleRun
from .io import MaskedDetectorIDs

PixelMask = NewType('PixelMask', sc.Variable)

from ..types import MaskedData, RawData, RunType
from .mantidio import PixelMask

def to_pixel_mask(data: RawData[SampleRun], masked: MaskedDetectorIDs) -> PixelMask:
"""Convert a list of masked detector IDs to a pixel mask.
Parameters
----------
data:
Raw data.
masked:
The masked detector IDs.
"""
ids = data.coords['detector_id']
mask = sc.zeros(sizes=ids.sizes, dtype='bool')
mask.values[np.isin(ids.values, masked.values)] = True
return PixelMask(mask)


def apply_pixel_masks(
Expand All @@ -22,3 +44,9 @@ def apply_pixel_masks(
for name, mask in masks.items():
data.masks[name] = mask
return MaskedData[RunType](data)


providers = (
to_pixel_mask,
apply_pixel_masks,
)

0 comments on commit 0577a8d

Please sign in to comment.