From 0577a8d48aaea155cddad3ab9a6094f7aecd2ff3 Mon Sep 17 00:00:00 2001 From: Simon Heybrock Date: Thu, 18 Jan 2024 08:42:00 +0100 Subject: [PATCH] Refactor to read xml directly --- src/esssans/isis/__init__.py | 25 ++++++++------- src/esssans/isis/io.py | 55 ++++++++++++++++++++++++++++++++ src/esssans/isis/mantidio.py | 62 ++++-------------------------------- src/esssans/isis/masking.py | 32 +++++++++++++++++-- 4 files changed, 105 insertions(+), 69 deletions(-) create mode 100644 src/esssans/isis/io.py diff --git a/src/esssans/isis/__init__.py b/src/esssans/isis/__init__.py index e0d7c125..1fce7d17 100644 --- a/src/esssans/isis/__init__.py +++ b/src/esssans/isis/__init__.py @@ -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', diff --git a/src/esssans/isis/io.py b/src/esssans/isis/io.py new file mode 100644 index 00000000..cdfa6e5e --- /dev/null +++ b/src/esssans/isis/io.py @@ -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: + + + + + 1400203-1400218,1401199,1402190-1402223 + + + + 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,) diff --git a/src/esssans/isis/mantidio.py b/src/esssans/isis/mantidio.py index 8049a101..0af5a493 100644 --- a/src/esssans/isis/mantidio.py +++ b/src/esssans/isis/mantidio.py @@ -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 ( @@ -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""" @@ -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]}) @@ -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: @@ -101,7 +67,8 @@ 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 @@ -109,24 +76,11 @@ def from_data_workspace( 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): @@ -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, ) diff --git a/src/esssans/isis/masking.py b/src/esssans/isis/masking.py index eb1fcd0e..03e17595 100644 --- a/src/esssans/isis/masking.py +++ b/src/esssans/isis/masking.py @@ -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( @@ -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, +)