essdiffraction 23.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3
+
4
+ """
5
+ Components for diffraction experiments (powder and single crystal).
6
+ """
7
+
8
+ import importlib.metadata
9
+
10
+ from . import uncertainty
11
+ from .correction import (
12
+ normalize_by_monitor,
13
+ normalize_by_proton_charge,
14
+ normalize_by_vanadium,
15
+ )
16
+ from .correction import providers as correction_providers
17
+ from .filtering import crop_tof, filter_events
18
+ from .filtering import providers as filtering_providers
19
+ from .filtering import remove_bad_pulses
20
+ from .grouping import finalize_histogram, group_by_two_theta, merge_all_pixels
21
+ from .grouping import providers as grouping_providers
22
+ from .smoothing import lowpass
23
+
24
+ try:
25
+ __version__ = importlib.metadata.version(__package__ or __name__)
26
+ except importlib.metadata.PackageNotFoundError:
27
+ __version__ = "0.0.0"
28
+
29
+ del importlib
30
+
31
+ providers = (
32
+ *correction_providers,
33
+ *filtering_providers,
34
+ *grouping_providers,
35
+ *uncertainty.providers,
36
+ )
37
+ """Sciline providers for setting up a diffraction pipeline.
38
+
39
+ These implement basic diffraction data-reduction functionality and need to be
40
+ extended with instrument-specific and sub-technique-specific providers.
41
+ """
42
+ del correction_providers, filtering_providers, grouping_providers
43
+
44
+ __all__ = [
45
+ 'crop_tof',
46
+ 'filter_events',
47
+ 'finalize_histogram',
48
+ 'group_by_two_theta',
49
+ 'lowpass',
50
+ 'merge_all_pixels',
51
+ 'normalize_by_monitor',
52
+ 'normalize_by_proton_charge',
53
+ 'normalize_by_vanadium',
54
+ 'remove_bad_pulses',
55
+ 'uncertainty',
56
+ ]
@@ -0,0 +1,125 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3
+ from typing import Any, Dict, Optional
4
+
5
+ import scipp as sc
6
+ from scippneutron.conversion.graph import beamline, tof
7
+
8
+ from .logging import get_logger
9
+ from .smoothing import lowpass
10
+ from .types import (
11
+ AccumulatedProtonCharge,
12
+ DspacingBins,
13
+ FilteredData,
14
+ FocussedData,
15
+ NormalizedByProtonCharge,
16
+ NormalizedByVanadium,
17
+ RunType,
18
+ SampleRun,
19
+ VanadiumRun,
20
+ )
21
+
22
+
23
+ def normalize_by_monitor(
24
+ data: sc.DataArray,
25
+ *,
26
+ monitor: sc.DataArray,
27
+ wavelength_edges: Optional[sc.Variable] = None,
28
+ smooth_args: Optional[Dict[str, Any]] = None,
29
+ ) -> sc.DataArray:
30
+ """
31
+ Normalize event data by a monitor.
32
+
33
+ The input is converted to wavelength if it does not already contain wavelengths.
34
+
35
+ Parameters
36
+ ----------
37
+ data:
38
+ Input event data.
39
+ monitor:
40
+ A histogrammed monitor.
41
+ wavelength_edges:
42
+ If given, rebin the monitor with these edges.
43
+ smooth_args:
44
+ If given, the monitor histogram is smoothed with
45
+ :func:`ess.diffraction.lowpass` before dividing into `data`.
46
+ `smooth_args` is passed as keyword arguments to
47
+ :func:`ess.diffraction.lowpass`. If ``None``, the monitor is not smoothed.
48
+
49
+ Returns
50
+ -------
51
+ :
52
+ `data` normalized by a monitor.
53
+ """
54
+ if 'wavelength' not in monitor.coords:
55
+ monitor = monitor.transform_coords(
56
+ 'wavelength',
57
+ graph={**beamline.beamline(scatter=False), **tof.elastic("tof")},
58
+ keep_inputs=False,
59
+ keep_intermediate=False,
60
+ keep_aliases=False,
61
+ )
62
+
63
+ if wavelength_edges is not None:
64
+ monitor = monitor.rebin(wavelength=wavelength_edges)
65
+ if smooth_args is not None:
66
+ get_logger().info(
67
+ "Smoothing monitor for normalization using "
68
+ "ess.diffraction.smoothing.lowpass with %s.",
69
+ smooth_args,
70
+ )
71
+ monitor = lowpass(monitor, dim='wavelength', **smooth_args)
72
+ return data.bins / sc.lookup(func=monitor, dim='wavelength')
73
+
74
+
75
+ def normalize_by_vanadium(
76
+ data: FocussedData[SampleRun],
77
+ vanadium: FocussedData[VanadiumRun],
78
+ edges: DspacingBins,
79
+ ) -> NormalizedByVanadium:
80
+ """
81
+ Normalize sample data by a vanadium measurement.
82
+
83
+ Parameters
84
+ ----------
85
+ data:
86
+ Sample data.
87
+ vanadium:
88
+ Vanadium data.
89
+ edges:
90
+ `vanadium` is histogrammed into these bins before dividing the data by it.
91
+
92
+ Returns
93
+ -------
94
+ :
95
+ `data` normalized by `vanadium`.
96
+ """
97
+ norm = sc.lookup(vanadium.hist({edges.dim: edges}), dim=edges.dim)
98
+ # Converting to unit 'one' because the division might produce a unit
99
+ # with a large scale if the proton charges in data and vanadium were
100
+ # measured with different units.
101
+ return (data.bins / norm).to(unit='one', copy=False)
102
+
103
+
104
+ def normalize_by_proton_charge(
105
+ data: FilteredData[RunType], proton_charge: AccumulatedProtonCharge[RunType]
106
+ ) -> NormalizedByProtonCharge[RunType]:
107
+ """Normalize data by an accumulated proton charge.
108
+
109
+ Parameters
110
+ ----------
111
+ data:
112
+ Un-normalized data array as events or a histogram.
113
+ proton_charge:
114
+ Accumulated proton charge over the entire run.
115
+
116
+ Returns
117
+ -------
118
+ :
119
+ ``data / proton_charge``
120
+ """
121
+ return NormalizedByProtonCharge[RunType](data / proton_charge)
122
+
123
+
124
+ providers = (normalize_by_proton_charge, normalize_by_vanadium)
125
+ """Sciline providers for diffraction corrections."""
@@ -0,0 +1,141 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3
+ """
4
+ Components for non-ESS diffraction experiments.
5
+
6
+ WARNING This package will be removed in the future!
7
+ It only serves as helpers to develop workflows until it is determined,
8
+ which mechanisms and interfaces will be used at ESS.
9
+ """
10
+ from pathlib import Path
11
+ from typing import Dict, Optional, Union
12
+
13
+ import numpy as np
14
+ import scipp as sc
15
+ import scippneutron as scn
16
+
17
+ from ..logging import get_logger
18
+
19
+
20
+ def _as_boolean_mask(var: sc.Variable) -> sc.Variable:
21
+ if var.dtype in ('float32', 'float64'):
22
+ if sc.any(var != var.to(dtype='int64')).value:
23
+ raise ValueError(
24
+ 'Cannot construct boolean mask, the input mask has fractional values.'
25
+ )
26
+ return var.to(dtype=bool)
27
+
28
+
29
+ def _parse_calibration_instrument_args(
30
+ filename: Union[str, Path],
31
+ *,
32
+ instrument_filename: Optional[str] = None,
33
+ instrument_name: Optional[str] = None,
34
+ ) -> Dict[str, str]:
35
+ if instrument_filename is not None:
36
+ if instrument_name is not None:
37
+ raise ValueError(
38
+ 'Only one argument of `instrument_name` and '
39
+ '`instrument_filename` is allowed, got both.'
40
+ )
41
+ instrument_arg = {'InstrumentFilename': instrument_filename}
42
+ instrument_message = f'with instrument file {instrument_filename}'
43
+ else:
44
+ if instrument_name is None:
45
+ raise ValueError(
46
+ 'Need one argument of `instrument_name` and '
47
+ '`instrument_filename` is allowed, got neither.'
48
+ )
49
+ instrument_arg = {'InstrumentName': instrument_name}
50
+ instrument_message = f'with instrument {instrument_name}'
51
+
52
+ get_logger().info(
53
+ 'Loading calibration from file %s %s', filename, instrument_message
54
+ )
55
+ return instrument_arg
56
+
57
+
58
+ def load_calibration(
59
+ filename: Union[str, Path],
60
+ *,
61
+ instrument_filename: Optional[str] = None,
62
+ instrument_name: Optional[str] = None,
63
+ mantid_args: Optional[dict] = None,
64
+ ) -> sc.Dataset:
65
+ """
66
+ Load and return calibration data.
67
+
68
+ Warning
69
+ -------
70
+ This function is designed to work with calibration files used by Mantid,
71
+ specifically for POWGEN. It does not represent the interface used at ESS
72
+ and will be removed in the future.
73
+
74
+ Uses the Mantid algorithm `LoadDiffCal
75
+ <https://docs.mantidproject.org/nightly/algorithms/LoadDiffCal-v1.html>`_
76
+ and stores the data in a :class:`scipp.Dataset`
77
+
78
+ Note that this function requires mantid to be installed and available in
79
+ the same Python environment as ess.
80
+
81
+ Parameters
82
+ ----------
83
+ filename:
84
+ The name of the calibration file to load.
85
+ instrument_filename:
86
+ Instrument definition file.
87
+ instrument_name:
88
+ Name of the instrument.
89
+ mantid_args:
90
+ Dictionary with additional arguments for the
91
+ `LoadDiffCal` Mantid algorithm.
92
+
93
+ Returns
94
+ -------
95
+ :
96
+ A Dataset containing the calibration data and masking.
97
+ """
98
+
99
+ mantid_args = {} if mantid_args is None else mantid_args
100
+ mantid_args.update(
101
+ _parse_calibration_instrument_args(
102
+ filename,
103
+ instrument_filename=instrument_filename,
104
+ instrument_name=instrument_name,
105
+ )
106
+ )
107
+
108
+ with scn.mantid.run_mantid_alg(
109
+ 'LoadDiffCal',
110
+ Filename=str(filename),
111
+ MakeGroupingWorkspace=False,
112
+ **mantid_args,
113
+ ) as ws:
114
+ ds = scn.from_mantid(ws.OutputCalWorkspace)
115
+ mask_ws = ws.OutputMaskWorkspace
116
+ rows = mask_ws.getNumberHistograms()
117
+ mask = sc.array(
118
+ dims=['row'],
119
+ values=np.fromiter(
120
+ (mask_ws.readY(i)[0] for i in range(rows)), count=rows, dtype=np.bool_
121
+ ),
122
+ unit=None,
123
+ )
124
+ # This is deliberately not stored as a mask since that would make
125
+ # subsequent handling, e.g., with groupby, more complicated. The mask
126
+ # is conceptually not masking rows in this table, i.e., it is not
127
+ # marking invalid rows, but rather describes masking for other data.
128
+ ds["mask"] = _as_boolean_mask(mask)
129
+
130
+ # The file does not define units
131
+ # TODO why those units? Can we verify?
132
+ ds["difc"].unit = 'us / angstrom'
133
+ ds["difa"].unit = 'us / angstrom**2'
134
+ ds["tzero"].unit = 'us'
135
+
136
+ ds = ds.rename_dims({'row': 'detector'})
137
+ ds.coords['detector'] = ds['detid'].data
138
+ ds.coords['detector'].unit = None
139
+ del ds['detid']
140
+
141
+ return ds
@@ -0,0 +1,23 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3
+ """
4
+ Functions and classes for the POWGEN instrument.
5
+
6
+ Note that this module is temporary and will be removed in favor of
7
+ the ``dream`` module when that is available.
8
+ """
9
+
10
+ from . import beamline, data
11
+ from .instrument_view import instrument_view
12
+
13
+ providers = (
14
+ *beamline.providers,
15
+ *data.providers,
16
+ )
17
+ """Sciline Providers for POWGEN-specific functionality."""
18
+
19
+ __all__ = [
20
+ 'beamline',
21
+ 'data',
22
+ 'instrument_view',
23
+ ]
@@ -0,0 +1,65 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3
+ """
4
+ Beamline parameters and utilities for POWGEN.
5
+ """
6
+
7
+ import scipp as sc
8
+
9
+ from ...types import CalibrationData, RawCalibrationData
10
+ from .types import DetectorInfo
11
+
12
+
13
+ def map_detector_to_spectrum(
14
+ data: sc.Dataset, *, detector_info: sc.Dataset
15
+ ) -> sc.Dataset:
16
+ """
17
+ Transform 'detector' coords to 'spectrum'.
18
+
19
+ Parameters
20
+ ----------
21
+ data:
22
+ Input data whose 'detector' coord is transformed.
23
+ detector_info:
24
+ Defines mapping from detector numbers to spectra.
25
+
26
+ Returns
27
+ -------
28
+ :
29
+ `data` with 'detector' coord and dim replaced by 'spectrum'.
30
+ """
31
+ if not sc.identical(
32
+ data.coords['detector'].to(
33
+ dtype=detector_info.coords['detector'].dtype, copy=False
34
+ ),
35
+ detector_info.coords['detector'],
36
+ ):
37
+ raise sc.CoordError(
38
+ "The 'detector' coords of `data` and `detector_info` do not match."
39
+ )
40
+
41
+ out = data.copy(deep=False)
42
+ del out.coords['detector']
43
+ # Add 1 because spectrum numbers in the data start at 1 but
44
+ # detector_info contains spectrum indices which start at 0.
45
+ out.coords['spectrum'] = detector_info.coords['spectrum'] + sc.index(
46
+ 1, dtype=detector_info.coords['spectrum'].dtype
47
+ )
48
+
49
+ return out.rename_dims({'detector': 'spectrum'})
50
+
51
+
52
+ def preprocess_calibration_data(
53
+ data: RawCalibrationData, detector_info: DetectorInfo
54
+ ) -> CalibrationData:
55
+ """Convert calibration data to a format that can be used by Scipp.
56
+
57
+ The raw calibration data is encoded in terms of a `'detector'` coordinate.
58
+ This needs to be converted to a `'spectrum'` coordinate to align
59
+ if with sample data.
60
+ """
61
+ return CalibrationData(map_detector_to_spectrum(data, detector_info=detector_info))
62
+
63
+
64
+ providers = (preprocess_calibration_data,)
65
+ """Sciline providers for POWGEN beamline processing."""
@@ -0,0 +1,159 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3
+
4
+ """Utilities for loading example data for POWGEN."""
5
+
6
+ import scipp as sc
7
+
8
+ from ...types import (
9
+ AccumulatedProtonCharge,
10
+ CalibrationFilename,
11
+ Filename,
12
+ ProtonCharge,
13
+ RawCalibrationData,
14
+ RawData,
15
+ RawDataAndMetadata,
16
+ RawDataWithVariances,
17
+ RunType,
18
+ SampleRun,
19
+ VanadiumRun,
20
+ )
21
+ from .types import DetectorInfo
22
+
23
+ _version = '1'
24
+
25
+ __all__ = ['get_path']
26
+
27
+
28
+ def _make_pooch():
29
+ import pooch
30
+
31
+ return pooch.create(
32
+ path=pooch.os_cache('ess/powgen'),
33
+ env='ESS_DATA_DIR',
34
+ base_url='https://public.esss.dk/groups/scipp/ess/powgen/{version}/',
35
+ version=_version,
36
+ registry={
37
+ # Files loadable with Mantid
38
+ 'PG3_4844_event.nxs': 'md5:d5ae38871d0a09a28ae01f85d969de1e',
39
+ 'PG3_4866_event.nxs': 'md5:3d543bc6a646e622b3f4542bc3435e7e',
40
+ 'PG3_5226_event.nxs': 'md5:58b386ebdfeb728d34fd3ba00a2d4f1e',
41
+ 'PG3_FERNS_d4832_2011_08_24.cal': 'md5:c181221ebef9fcf30114954268c7a6b6',
42
+ # Zipped Scipp HDF5 files
43
+ 'PG3_4844_event.zip': 'md5:a644c74f5e740385469b67431b690a3e',
44
+ 'PG3_4866_event.zip': 'md5:5bc49def987f0faeb212a406b92b548e',
45
+ 'PG3_FERNS_d4832_2011_08_24.zip': 'md5:0fef4ed5f61465eaaa3f87a18f5bb80d',
46
+ },
47
+ )
48
+
49
+
50
+ _pooch = _make_pooch()
51
+
52
+
53
+ def get_path(name: str, unzip: bool = False) -> str:
54
+ """
55
+ Return the path to a data file bundled with scippneutron.
56
+
57
+ This function only works with example data and cannot handle
58
+ paths to custom files.
59
+ """
60
+ import pooch
61
+
62
+ return _pooch.fetch(name, processor=pooch.Unzip() if unzip else None)
63
+
64
+
65
+ def mantid_sample_file() -> str:
66
+ return get_path('PG3_4844_event.nxs')
67
+
68
+
69
+ def mantid_vanadium_file() -> str:
70
+ return get_path('PG3_4866_event.nxs')
71
+
72
+
73
+ def mantid_empty_instrument_file() -> str:
74
+ return get_path('PG3_5226_event.nxs')
75
+
76
+
77
+ def mantid_calibration_file() -> str:
78
+ return get_path('PG3_FERNS_d4832_2011_08_24.cal')
79
+
80
+
81
+ def sample_file() -> str:
82
+ (path,) = get_path('PG3_4844_event.zip', unzip=True)
83
+ return path
84
+
85
+
86
+ def vanadium_file() -> str:
87
+ (path,) = get_path('PG3_4866_event.zip', unzip=True)
88
+ return path
89
+
90
+
91
+ def calibration_file() -> str:
92
+ (path,) = get_path('PG3_FERNS_d4832_2011_08_24.zip', unzip=True)
93
+ return path
94
+
95
+
96
+ def pooch_load(filename: Filename[RunType]) -> RawDataAndMetadata[RunType]:
97
+ """Load a file with pooch.
98
+
99
+ If the file is a zip archive, it is extracted and a path to the contained
100
+ file is returned.
101
+
102
+ The loaded data holds both the events and any metadata from the file.
103
+ """
104
+ if filename.endswith('.zip'):
105
+ (path,) = get_path(filename, unzip=True)
106
+ else:
107
+ path = get_path(filename)
108
+ return RawDataAndMetadata[RunType](sc.io.load_hdf5(path))
109
+
110
+
111
+ def pooch_load_calibration(filename: CalibrationFilename) -> RawCalibrationData:
112
+ """Load the calibration data for the POWGEN test data."""
113
+ if filename.endswith('.zip'):
114
+ (path,) = get_path(filename, unzip=True)
115
+ else:
116
+ path = get_path(filename)
117
+ return RawCalibrationData(sc.io.load_hdf5(path))
118
+
119
+
120
+ # This can be generalized with https://github.com/scipp/sciline/issues/69.
121
+ def extract_raw_data_sample(dg: RawDataAndMetadata[SampleRun]) -> RawData[SampleRun]:
122
+ """Return the events from a loaded data group."""
123
+ return RawData[SampleRun](dg['data'])
124
+
125
+
126
+ def extract_raw_data_vanadium(
127
+ dg: RawDataAndMetadata[VanadiumRun],
128
+ ) -> RawDataWithVariances[VanadiumRun]:
129
+ """Return the events from a loaded data group."""
130
+ return RawDataWithVariances[VanadiumRun](dg['data'])
131
+
132
+
133
+ def extract_detector_info(dg: RawDataAndMetadata[SampleRun]) -> DetectorInfo:
134
+ """Return the detector info from a loaded data group."""
135
+ return DetectorInfo(dg['detector_info'])
136
+
137
+
138
+ def extract_proton_charge(dg: RawDataAndMetadata[RunType]) -> ProtonCharge[RunType]:
139
+ """Return the proton charge from a loaded data group."""
140
+ return ProtonCharge[RunType](dg['proton_charge'])
141
+
142
+
143
+ def extract_accumulated_proton_charge(
144
+ data: RawData[RunType],
145
+ ) -> AccumulatedProtonCharge[RunType]:
146
+ """Return the stored accumulated proton charge from a loaded data group."""
147
+ return AccumulatedProtonCharge[RunType](data.coords['gd_prtn_chrg'])
148
+
149
+
150
+ providers = (
151
+ pooch_load,
152
+ pooch_load_calibration,
153
+ extract_accumulated_proton_charge,
154
+ extract_detector_info,
155
+ extract_proton_charge,
156
+ extract_raw_data_sample,
157
+ extract_raw_data_vanadium,
158
+ )
159
+ """Sciline Providers for loading POWGEN data."""
@@ -0,0 +1,42 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3
+ from typing import Optional
4
+
5
+ import scipp as sc
6
+ import scippneutron as scn
7
+
8
+
9
+ def instrument_view(
10
+ da: sc.DataArray,
11
+ positions: str = "position",
12
+ pixel_size: Optional[float] = None,
13
+ components: Optional[dict] = None,
14
+ **kwargs,
15
+ ):
16
+ """
17
+ Instrument view for the POWGEN instrument, with adjusted default arguments.
18
+
19
+ Parameters
20
+ ----------
21
+ positions:
22
+ Key for coord holding positions to use for pixels
23
+ pixel_size:
24
+ Custom pixel size to use for detector pixels
25
+ components:
26
+ Dictionary containing display names and corresponding
27
+ settings (also a Dictionary) for additional components to display
28
+ items with known positions to be shown
29
+ kwargs:
30
+ See :func:`scippneutron.instrument_view`
31
+ """
32
+ # TODO: the camera argument does not work with the Plopp instrument view
33
+ # if 'camera' not in kwargs:
34
+ # kwargs = {
35
+ # **kwargs, 'camera': {
36
+ # 'position': sc.vector(value=[-3, 3, 3],
37
+ # unit=da.coords[positions].unit)
38
+ # }
39
+ # }
40
+ return scn.instrument_view(
41
+ da, positions=positions, components=components, pixel_size=pixel_size, **kwargs
42
+ )
@@ -0,0 +1,19 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3
+
4
+ """This module defines the domain types used by POWGEN.
5
+
6
+ The domain types are used to define parameters and to request results from a Sciline
7
+ pipeline.
8
+ """
9
+
10
+ from typing import NewType
11
+
12
+ import scipp as sc
13
+
14
+ # This is Mantid-specific and can probably be removed when the POWGEN
15
+ # workflow is removed.
16
+ DetectorInfo = NewType('DetectorInfo', sc.Dataset)
17
+ """Mapping between detector numbers and spectra."""
18
+
19
+ del sc, NewType