From a983ef7d313a2da2e1b03771be231a253b3779fd Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 6 Jun 2023 13:25:24 +0200 Subject: [PATCH 01/34] add initial tools functions --- src/ess/dream/__init__.py | 2 + src/ess/dream/tools.py | 129 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/ess/dream/tools.py diff --git a/src/ess/dream/__init__.py b/src/ess/dream/__init__.py index df1769802..59d031dda 100644 --- a/src/ess/dream/__init__.py +++ b/src/ess/dream/__init__.py @@ -1,2 +1,4 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + +from . import tools diff --git a/src/ess/dream/tools.py b/src/ess/dream/tools.py new file mode 100644 index 000000000..4f3e38aa2 --- /dev/null +++ b/src/ess/dream/tools.py @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + +from typing import Union + +import plopp as pp +import scipp as sc + + +def _group_and_concat(da: sc.DataArray, dims: tuple) -> sc.DataArray: + """ + Group the input data using groups specified by the ``dims``. + Then, remove any extra dimension that is not in ``dims`` by concatenating the + event data in the bins. + + Parameters + ---------- + da: + DataArray to group. + dims: + Coordinates to use for grouping. + """ + in_dims = set(da.dims) + out_dims = set(dims) + if (not dims) or (in_dims == out_dims): + return da + grouped = da.group(*list(out_dims - in_dims)) + return grouped.bins.concat(list(set(grouped.dims) - out_dims)) + + +def to_logical_dims(da: sc.DataArray) -> sc.DataArray: + """ + Reshape the input data to logical dimensions. + + Parameters + ---------- + da: + DataArray to reshape. + """ + return da.group('module', 'segment', 'counter', 'wire', 'strip') + + +def wire_vs_strip(da: sc.DataArray) -> sc.DataArray: + """ + Reshape the input data to wire vs strip dimensions. + + Parameters + ---------- + da: + DataArray to reshape. + """ + return _group_and_concat(da, dims=('wire', 'strip')) + + +def module_vs_segment(da: sc.DataArray) -> sc.DataArray: + """ + Reshape the input data to module vs segment dimensions. + + Parameters + ---------- + da: + DataArray to reshape. + """ + return _group_and_concat(da, dims=('module', 'segment')) + + +def module_vs_wire(da: sc.DataArray) -> sc.DataArray: + """ + Reshape the input data to module vs wire dimensions. + + Parameters + ---------- + da: + DataArray to reshape. + """ + return _group_and_concat(da, dims=('module', 'wire')) + + +def module_vs_strip(da: sc.DataArray) -> sc.DataArray: + """ + Reshape the input data to module vs strip dimensions. + + Parameters + ---------- + da: + DataArray to reshape. + """ + return _group_and_concat(da, dims=('module', 'strip')) + + +def segment_vs_strip(da: sc.DataArray) -> sc.DataArray: + """ + Reshape the input data to segment vs strip dimensions. + + Parameters + ---------- + da: + DataArray to reshape. + """ + return _group_and_concat(da, dims=('segment', 'strip')) + + +def segment_vs_wire(da: sc.DataArray) -> sc.DataArray: + """ + Reshape the input data to segment vs wire dimensions. + + Parameters + ---------- + da: + DataArray to reshape. + """ + return _group_and_concat(da, dims=('segment', 'wire')) + + +def tof_navigator( + da: sc.DataArray, bins: Union[int, sc.Variable] = 300 +) -> sc.DataArray: + """ + Make a plot of the data with a slider to navigate the time-of-flight dimension. + + Parameters + ---------- + da: + DataArray to plot. + bins: + Binning to use for histogramming along the `tof` dimension before plotting. + """ + dims = list(set(da.dims) - {'tof'}) + return pp.slicer(da.hist(tof=bins), keep=dims) From 0709aceb2978a9b6602437e673d0c2525e184907 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 6 Jun 2023 13:52:29 +0200 Subject: [PATCH 02/34] add tests --- tests/dream/tools_test.py | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/dream/tools_test.py diff --git a/tests/dream/tools_test.py b/tests/dream/tools_test.py new file mode 100644 index 000000000..c75541480 --- /dev/null +++ b/tests/dream/tools_test.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) +import numpy as np +import pytest +import scipp as sc + +from ess.dream import tools as tls + + +def make_fake_dream_data(nevents): + params = {'module': 14, 'segment': 6, 'counter': 2, 'wire': 32, 'strip': 256} + coords = { + key: sc.array( + dims=['row'], values=np.random.choice(np.arange(1, val + 1), size=nevents) + ) + for key, val in params.items() + } + return sc.DataArray(data=sc.ones(sizes={'row': nevents}), coords=coords) + + +def test_to_logical_dims(): + da = tls.to_logical_dims(make_fake_dream_data(1000)) + assert set(da.dims) == {'module', 'segment', 'counter', 'wire', 'strip'} + assert da.bins is not None + + +COMBINATIONS = [ + ('wire', 'strip'), + ('module', 'segment'), + ('module', 'wire'), + ('module', 'strip'), + ('segment', 'wire'), + ('segment', 'strip'), +] + + +@pytest.mark.parametrize("dims", COMBINATIONS) +def test_wire_vs_strip_from_table(dims): + func = getattr(tls, dims[0] + '_vs_' + dims[1]) + da = func(make_fake_dream_data(1000)) + assert set(da.dims) == set(dims) + + +@pytest.mark.parametrize("dims", COMBINATIONS) +def test_wire_vs_strip_from_logical(dims): + func = getattr(tls, dims[0] + '_vs_' + dims[1]) + da = func(tls.to_logical_dims(make_fake_dream_data(1000))) + assert set(da.dims) == set(dims) + + +@pytest.mark.parametrize("dims", COMBINATIONS) +def test_wire_vs_strip_first_dim_exists(dims): + func = getattr(tls, dims[0] + '_vs_' + dims[1]) + da = func(make_fake_dream_data(1000).group(dims[0])) + assert set(da.dims) == set(dims) + + +@pytest.mark.parametrize("dims", COMBINATIONS) +def test_wire_vs_strip_second_dim_exists(dims): + func = getattr(tls, dims[0] + '_vs_' + dims[1]) + da = func(make_fake_dream_data(1000).group(dims[1])) + assert set(da.dims) == set(dims) From 595f6a49bf21ef933db8e2ac983b3cc3aaec00d7 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Thu, 8 Jun 2023 16:00:27 +0200 Subject: [PATCH 03/34] add initial instrument view --- src/ess/dream/__init__.py | 3 ++- src/ess/dream/{tools.py => diagnostics.py} | 4 +-- src/ess/dream/instrument_view.py | 29 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) rename src/ess/dream/{tools.py => diagnostics.py} (96%) create mode 100644 src/ess/dream/instrument_view.py diff --git a/src/ess/dream/__init__.py b/src/ess/dream/__init__.py index 59d031dda..bfc260c88 100644 --- a/src/ess/dream/__init__.py +++ b/src/ess/dream/__init__.py @@ -1,4 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -from . import tools +from . import diagnostics +from .instrument_view import instrument_view diff --git a/src/ess/dream/tools.py b/src/ess/dream/diagnostics.py similarity index 96% rename from src/ess/dream/tools.py rename to src/ess/dream/diagnostics.py index 4f3e38aa2..1c70e0f0b 100644 --- a/src/ess/dream/tools.py +++ b/src/ess/dream/diagnostics.py @@ -112,9 +112,7 @@ def segment_vs_wire(da: sc.DataArray) -> sc.DataArray: return _group_and_concat(da, dims=('segment', 'wire')) -def tof_navigator( - da: sc.DataArray, bins: Union[int, sc.Variable] = 300 -) -> sc.DataArray: +def tof_slicer(da: sc.DataArray, bins: Union[int, sc.Variable] = 300) -> sc.DataArray: """ Make a plot of the data with a slider to navigate the time-of-flight dimension. diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py new file mode 100644 index 000000000..1ccc7b4c8 --- /dev/null +++ b/src/ess/dream/instrument_view.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + +import plopp as pp +import scipp as sc + + +def instrument_view(data, dim=None, bins=50, pixel_size=10, **kwargs): + from plopp.graphics import figure3d + import plopp.widgets as pw + + if not isinstance(data, sc.DataArray): + data = sc.concat([da.flatten(to='pixel') for da in data.values()], dim='pixel') + else: + data = data.flatten(to='pixel') + + if dim is not None: + slider_widget = pw.SliceWidget( + sc.DataArray(data=bins, coords={dim: bins}), dims=[dim] + ) + slider_node = pp.widget_node(slider_widget) + nodes = [pw.slice_dims(data_array=data.hist({dim: bins}), slices=slider_node)] + else: + nodes = [pp.Node(data.hist())] + + out = [figure3d(*nodes, x='x', y='y', z='z', pixel_size=pixel_size, **kwargs)] + if dim is not None: + out.append(slider_widget) + return pw.Box(out) From 31e621a6bb6a92c97ca084e848ca8918ea2ac1fa Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Thu, 8 Jun 2023 16:17:30 +0200 Subject: [PATCH 04/34] add initial notebook --- docs/index.rst | 1 + .../instruments/dream/dream-diagnostics.ipynb | 105 ++++++++++++++++++ docs/instruments/dream/dream.rst | 7 ++ src/ess/dream/instrument_view.py | 7 +- 4 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 docs/instruments/dream/dream-diagnostics.ipynb create mode 100644 docs/instruments/dream/dream.rst diff --git a/docs/index.rst b/docs/index.rst index d75ef2708..e8f65eee6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,7 @@ This involves a "filtering" process, since scope and contribution guidelines are :caption: Instruments instruments/amor/amor + instruments/dream/dream instruments/loki/loki instruments/external/external diff --git a/docs/instruments/dream/dream-diagnostics.ipynb b/docs/instruments/dream/dream-diagnostics.ipynb new file mode 100644 index 000000000..e17dc41c4 --- /dev/null +++ b/docs/instruments/dream/dream-diagnostics.ipynb @@ -0,0 +1,105 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4852fe41-3f9a-4cdb-8aba-ff7c7f198e6c", + "metadata": {}, + "source": [ + "# DREAM diagnostics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f416595-83b4-44d1-b506-9ba73bf0786e", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib widget\n", + "import pandas as pd\n", + "import scipp as sc\n", + "import plopp as pp\n", + "import matplotlib.pyplot as plt\n", + "from ess.dream import diagnostics as diags\n", + "from ess import dream" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebef41be-fe1c-46d8-80d6-da43b4ff9a73", + "metadata": {}, + "outputs": [], + "source": [ + "filename = 'data_dream_HF_mil_closed_alldets_1e9.csv.zip'\n", + "df = pd.read_table(filename)\n", + "ds = sc.compat.from_pandas(df)\n", + "del ds.coords['row'] # we have no use for this row index\n", + "for key in list(ds):\n", + " name, *remainder = key.split(' ')\n", + " ds[name] = ds.pop(key)\n", + " ds[name].unit = remainder[0][1:-1] if remainder else None\n", + "ds['lambda'].unit = 'angstrom'\n", + "table = sc.DataArray(sc.ones(sizes=ds.sizes, unit='counts'))\n", + "for name in ds:\n", + " table.coords[name] = ds[name].data\n", + "\n", + "mapping = {'sumo0': 0, 'sumo1': 1, 'sumo2': 2, 'sumo3': 3, 'mantle': 4, 'high_res': 5}\n", + "da = table.group('det')\n", + "dg = sc.DataGroup({key: da['det', val] for key, val in mapping.items()})\n", + "dg = sc.DataGroup({key: diags.to_logical_dims(da) for key, da in dg.items()})\n", + "for da in dg.values():\n", + " da.coords['x'] = da.bins.coords.pop('x_pos').bins.mean()\n", + " da.coords['y'] = da.bins.coords.pop('y_pos').bins.mean()\n", + " da.coords['z'] = da.bins.coords.pop('z_pos').bins.mean()\n", + "dg" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01ec9dda-6537-405e-afa3-0240268f5b3e", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(4, len(dg), figsize=(len(dg) * 3, 4 * 3))\n", + "for i, (name, da) in enumerate(dg.items()):\n", + " da.bins.concat(list(set(da.dims) - {'module'})).hist().plot(ax=ax[0, i], title=name)\n", + " diags.module_vs_segment(da).hist().plot(ax=ax[1, i])\n", + " da.bins.concat(list(set(da.dims) - {'strip'})).hist().plot(ax=ax[2, i])\n", + " diags.wire_vs_strip(da).hist().plot(ax=ax[3, i])\n", + "fig.subplots_adjust(wspace=0.5, hspace=0.5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd768225-1776-41e3-9fe0-03dae565e5b9", + "metadata": {}, + "outputs": [], + "source": [ + "dream.instrument_view(dg, dim='tof', bins=10)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/instruments/dream/dream.rst b/docs/instruments/dream/dream.rst new file mode 100644 index 000000000..b6dd5d1b1 --- /dev/null +++ b/docs/instruments/dream/dream.rst @@ -0,0 +1,7 @@ +DREAM +===== + +.. toctree:: + :maxdepth: 4 + + dream_diagnostics diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 1ccc7b4c8..457f9392a 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -15,11 +15,10 @@ def instrument_view(data, dim=None, bins=50, pixel_size=10, **kwargs): data = data.flatten(to='pixel') if dim is not None: - slider_widget = pw.SliceWidget( - sc.DataArray(data=bins, coords={dim: bins}), dims=[dim] - ) + histogrammed = data.hist({dim: bins}) + slider_widget = pw.SliceWidget(histogrammed, dims=[dim]) slider_node = pp.widget_node(slider_widget) - nodes = [pw.slice_dims(data_array=data.hist({dim: bins}), slices=slider_node)] + nodes = [pw.slice_dims(data_array=histogrammed, slices=slider_node)] else: nodes = [pp.Node(data.hist())] From cc054d3ed56501201350d6497be00075985825c5 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 12 Jun 2023 12:19:41 +0200 Subject: [PATCH 05/34] update notebook and instrument_view --- .../instruments/dream/dream-diagnostics.ipynb | 80 ++++++++++++++++++- src/ess/dream/instrument_view.py | 40 +++++++++- 2 files changed, 113 insertions(+), 7 deletions(-) diff --git a/docs/instruments/dream/dream-diagnostics.ipynb b/docs/instruments/dream/dream-diagnostics.ipynb index e17dc41c4..a884871fb 100644 --- a/docs/instruments/dream/dream-diagnostics.ipynb +++ b/docs/instruments/dream/dream-diagnostics.ipynb @@ -5,7 +5,11 @@ "id": "4852fe41-3f9a-4cdb-8aba-ff7c7f198e6c", "metadata": {}, "source": [ - "# DREAM diagnostics" + "# DREAM diagnostics\n", + "\n", + "This notebook contains a set of visualizations for the DREAM detectors.\n", + "The main purpose of such visualizations are to provide quick diagnostics on the detector data,\n", + "which help determine if some of the detector components (wires, strips, modules...) are faulty or broken." ] }, { @@ -24,6 +28,21 @@ "from ess import dream" ] }, + { + "cell_type": "markdown", + "id": "228a23dc-17f2-4273-a4c9-3e3276db8c54", + "metadata": {}, + "source": [ + "## Load and reshape the data\n", + "\n", + "We load a data set from a Geant4 simulation (stored as a `.csv` file).\n", + "After loading, we manipulate and reshape the data to obtain a data group that contains one entry per detector bank.\n", + "In each detector bank, the data is organised by `wire`, `strip`, `module`, `segment`, and `counter`.\n", + "\n", + "It is assumed that Nexus files from the DREAM beamline will be loaded as such by Scippnexus,\n", + "and that the manipulations required below would not be needed for production datasets." + ] + }, { "cell_type": "code", "execution_count": null, @@ -35,6 +54,7 @@ "df = pd.read_table(filename)\n", "ds = sc.compat.from_pandas(df)\n", "del ds.coords['row'] # we have no use for this row index\n", + "# Some logic to parse units in the file header\n", "for key in list(ds):\n", " name, *remainder = key.split(' ')\n", " ds[name] = ds.pop(key)\n", @@ -43,7 +63,7 @@ "table = sc.DataArray(sc.ones(sizes=ds.sizes, unit='counts'))\n", "for name in ds:\n", " table.coords[name] = ds[name].data\n", - "\n", + "# Group pixels according to their bank identifier\n", "mapping = {'sumo0': 0, 'sumo1': 1, 'sumo2': 2, 'sumo3': 3, 'mantle': 4, 'high_res': 5}\n", "da = table.group('det')\n", "dg = sc.DataGroup({key: da['det', val] for key, val in mapping.items()})\n", @@ -55,6 +75,17 @@ "dg" ] }, + { + "cell_type": "markdown", + "id": "5ac73609-f473-4c73-b445-7a92fe9de45a", + "metadata": {}, + "source": [ + "## Detector diagnostic plots\n", + "\n", + "We now make a single large figure showing some of the most commonly used plots,\n", + "with one column per detector bank." + ] + }, { "cell_type": "code", "execution_count": null, @@ -71,6 +102,31 @@ "fig.subplots_adjust(wspace=0.5, hspace=0.5)" ] }, + { + "cell_type": "markdown", + "id": "49595486-ab4e-4662-86f7-d301aedcf974", + "metadata": {}, + "source": [ + "## Instrument view\n", + "\n", + "We also show the 3D view of the instrument pixels.\n", + "The DREAM-specific instrument view is also capable of slicing the data along the `tof` dimension by using the argument `dim='tof'`\n", + "(use the `bins` argument to change how many bins will be used to histogram the `tof` dimension)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34d80492-7593-4969-ae97-f292bde5f66b", + "metadata": { + "nbsphinx": "hidden" + }, + "outputs": [], + "source": [ + "# In order to keep the size of the doc pages down, we only keep 1 in 10 pixels\n", + "dg = sc.DataGroup({key: da['pixel', ::10] for key, da in dg.flatten(to='pixel').items()})" + ] + }, { "cell_type": "code", "execution_count": null, @@ -78,7 +134,25 @@ "metadata": {}, "outputs": [], "source": [ - "dream.instrument_view(dg, dim='tof', bins=10)" + "dream.instrument_view(dg, dim='tof', bins=10, pixel_size=20)" + ] + }, + { + "cell_type": "markdown", + "id": "76d53a44-979c-4968-902e-e625ef5a0cbd", + "metadata": {}, + "source": [ + "Finally, it is also possible to look at a single bank using" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43f9ffbc-6bf5-4407-b3ad-5d1626efc43d", + "metadata": {}, + "outputs": [], + "source": [ + "dream.instrument_view(dg['mantle'], pixel_size=20)" ] } ], diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 457f9392a..d82729a05 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -5,7 +5,31 @@ import scipp as sc -def instrument_view(data, dim=None, bins=50, pixel_size=10, **kwargs): +def instrument_view( + data: Union[sc.DataArray, sc.DataGroup], + dim: Optional[str] = None, + bins: Union[sc.Variable, int] = 50, + pixel_size: Union[sc.Variable, float] = 10, + **kwargs +): + """ + Three-dimensional visualization of the DREAM instrument pixels. + By default, the data counts will be integrated for all tofs/wavelengths. + It is possible to add a tof/wavelength slider by specifying the ``dim`` and ``bins`` + arguments (see parameters below). + + Parameters + ---------- + data: + Data to visualize. + dim: + Dimension to use for the slider. + bins: + Number of bins to use for the slider. + pixel_size: + Size of the pixels. If a float is provided, it will assume the same unit as the + pixel coordinates. + """ from plopp.graphics import figure3d import plopp.widgets as pw @@ -15,14 +39,22 @@ def instrument_view(data, dim=None, bins=50, pixel_size=10, **kwargs): data = data.flatten(to='pixel') if dim is not None: - histogrammed = data.hist({dim: bins}) + histogrammed = data.hist({dim: bins}) if data.bins is not None else data slider_widget = pw.SliceWidget(histogrammed, dims=[dim]) + slider_widget.controls[dim]['slider'].layout = {"width": "400px"} slider_node = pp.widget_node(slider_widget) nodes = [pw.slice_dims(data_array=histogrammed, slices=slider_node)] else: - nodes = [pp.Node(data.hist())] + nodes = [pp.Node(data.hist() if data.bins is not None else data)] - out = [figure3d(*nodes, x='x', y='y', z='z', pixel_size=pixel_size, **kwargs)] + fig = figure3d(*nodes, x='x', y='y', z='z', pixel_size=pixel_size, **kwargs) + tri_cutter = pw.TriCutTool(fig) + fig.toolbar['cut3d'] = pw.ToggleTool( + callback=tri_cutter.toggle_visibility, + icon='cube', + tooltip='Hide/show spatial cutting tool', + ) + out = [fig, tri_cutter] if dim is not None: out.append(slider_widget) return pw.Box(out) From f90bbc8067fa4ff6fb1de0c70a95e349b25941c5 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 12 Jun 2023 15:44:37 +0200 Subject: [PATCH 06/34] add pooch data registry for dream --- .../instruments/dream/dream-diagnostics.ipynb | 3 +- src/ess/__init__.py | 1 + src/ess/data.py | 45 +++++++++++++++++++ src/ess/dream/__init__.py | 1 + src/ess/dream/data_files.py | 12 +++++ src/ess/dream/instrument_view.py | 2 + 6 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/ess/data.py create mode 100644 src/ess/dream/data_files.py diff --git a/docs/instruments/dream/dream-diagnostics.ipynb b/docs/instruments/dream/dream-diagnostics.ipynb index a884871fb..8d916c106 100644 --- a/docs/instruments/dream/dream-diagnostics.ipynb +++ b/docs/instruments/dream/dream-diagnostics.ipynb @@ -19,7 +19,6 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib widget\n", "import pandas as pd\n", "import scipp as sc\n", "import plopp as pp\n", @@ -50,7 +49,7 @@ "metadata": {}, "outputs": [], "source": [ - "filename = 'data_dream_HF_mil_closed_alldets_1e9.csv.zip'\n", + "filename = dream.data['data_dream_HF_mil_closed_alldets_1e9.csv.zip']\n", "df = pd.read_table(filename)\n", "ds = sc.compat.from_pandas(df)\n", "del ds.coords['row'] # we have no use for this row index\n", diff --git a/src/ess/__init__.py b/src/ess/__init__.py index 329ab9f39..fb9fac8c2 100644 --- a/src/ess/__init__.py +++ b/src/ess/__init__.py @@ -10,5 +10,6 @@ except importlib.metadata.PackageNotFoundError: __version__ = "0.0.0" +from . import data from . import logging from .logging import get_logger diff --git a/src/ess/data.py b/src/ess/data.py new file mode 100644 index 000000000..408ed91ae --- /dev/null +++ b/src/ess/data.py @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + +from typing import Dict + +__all__ = ['Registry'] + + +class Registry: + def __init__(self, instrument: str, files: Dict[str, str], version: str = '1'): + import pooch + + self._registry = pooch.create( + path=pooch.os_cache(f'ess/{instrument}'), + env=f'ESS_{instrument.upper()}_DATA_DIR', + base_url=f'https://public.esss.dk/groups/scipp/ess/{instrument}/' + + '{version}/', + version=version, + registry=files, + ) + + def __getitem__(self, name: str) -> str: + """ + Get the path to a file in the registry. + + Parameters + ---------- + name: + Name of the file to get the path for. + """ + return self._registry.fetch(name) + + def get_path(self, name: str) -> str: + """ + Get the path to a file in the registry. + This is the deprecated way of getting a file path, and is mostly there for + backwards compatibility. + Use ``__getitem__`` instead. + + Parameters + ---------- + name: + Name of the file to get the path for. + """ + return self[name] diff --git a/src/ess/dream/__init__.py b/src/ess/dream/__init__.py index bfc260c88..77d465639 100644 --- a/src/ess/dream/__init__.py +++ b/src/ess/dream/__init__.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) +from .data_files import data_registry as data from . import diagnostics from .instrument_view import instrument_view diff --git a/src/ess/dream/data_files.py b/src/ess/dream/data_files.py new file mode 100644 index 000000000..5c5ff6833 --- /dev/null +++ b/src/ess/dream/data_files.py @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + +from ..data import Registry + +data_registry = Registry( + instrument='dream', + files={ + 'data_dream_HF_mil_closed_alldets_1e9.csv.zip': 'md5:2997f33d3ddc792083bfab661cf1d93a' + }, + version='1', +) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index d82729a05..1b156c2c0 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) +from typing import Optional, Union + import plopp as pp import scipp as sc From 8cbbf28e9863db446b5cae8f1cb3e95bff8a34bd Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 12 Jun 2023 15:46:48 +0200 Subject: [PATCH 07/34] update amor data registry --- src/ess/amor/__init__.py | 3 ++- src/ess/amor/data.py | 33 --------------------------------- src/ess/amor/data_files.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 34 deletions(-) delete mode 100644 src/ess/amor/data.py create mode 100644 src/ess/amor/data_files.py diff --git a/src/ess/amor/__init__.py b/src/ess/amor/__init__.py index a74676e05..f0a8f20b6 100644 --- a/src/ess/amor/__init__.py +++ b/src/ess/amor/__init__.py @@ -1,7 +1,8 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) # flake8: noqa: F401 -from . import calibrations, conversions, data, normalize, resolution, tools +from . import calibrations, conversions, normalize, resolution, tools +from .data_files import data_registry as data from .beamline import instrument_view_components, make_beamline from .instrument_view import instrument_view from .load import load diff --git a/src/ess/amor/data.py b/src/ess/amor/data.py deleted file mode 100644 index a6f608fa9..000000000 --- a/src/ess/amor/data.py +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -_version = '1' - -__all__ = ['get_path'] - - -def _make_pooch(): - import pooch - - return pooch.create( - path=pooch.os_cache('ess/amor'), - env='ESS_AMOR_DATA_DIR', - base_url='https://public.esss.dk/groups/scipp/ess/amor/{version}/', - version=_version, - registry={ - "reference.nxs": "md5:56d493c8051e1c5c86fb7a95f8ec643b", - "sample.nxs": "md5:4e07ccc87b5c6549e190bc372c298e83", - }, - ) - - -_pooch = _make_pooch() - - -def get_path(name: str) -> str: - """ - Return the path to a data file bundled with scippneutron. - - This function only works with example data and cannot handle - paths to custom files. - """ - return _pooch.fetch(name) diff --git a/src/ess/amor/data_files.py b/src/ess/amor/data_files.py new file mode 100644 index 000000000..b52406265 --- /dev/null +++ b/src/ess/amor/data_files.py @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + + +from ..data import Registry + +data_registry = Registry( + instrument='amor', + files={ + "reference.nxs": "md5:56d493c8051e1c5c86fb7a95f8ec643b", + "sample.nxs": "md5:4e07ccc87b5c6549e190bc372c298e83", + }, + version='1', +) From 3e8d2038476b7bcdd442bdf71f7af33cb272580b Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 12 Jun 2023 15:49:00 +0200 Subject: [PATCH 08/34] update loki data registry --- src/ess/loki/__init__.py | 2 +- src/ess/loki/data.py | 40 -------------------------------------- src/ess/loki/data_files.py | 21 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 41 deletions(-) delete mode 100644 src/ess/loki/data.py create mode 100644 src/ess/loki/data_files.py diff --git a/src/ess/loki/__init__.py b/src/ess/loki/__init__.py index 069bb5ebe..a443d88c5 100644 --- a/src/ess/loki/__init__.py +++ b/src/ess/loki/__init__.py @@ -3,5 +3,5 @@ # flake8: noqa: F401 -from . import data +from .data_files import data_registry as data from .io import load_rkh_q, load_rkh_wav, load_sans2d diff --git a/src/ess/loki/data.py b/src/ess/loki/data.py deleted file mode 100644 index e53370c55..000000000 --- a/src/ess/loki/data.py +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -_version = '1' - -__all__ = ['get_path'] - - -def _make_pooch(): - import pooch - - return pooch.create( - path=pooch.os_cache('ess/loki'), - env='ESS_LOKI_DATA_DIR', - base_url='https://public.esss.dk/groups/scipp/ess/loki/{version}/', - version=_version, - registry={ - 'DIRECT_SANS2D_REAR_34327_4m_8mm_16Feb16.dat': 'md5:d64495831325a63e1b961776a8544599', # noqa: E501 - 'DIRECT_SANS2D_REAR_34327_4m_8mm_16Feb16.hdf5': 'md5:43f4188301d709aa49df0631d03a67cb', # noqa: E501 - 'SANS2D00063091.nxs': 'md5:05929753ea06eca5fe4be164cb06b4d6', - 'SANS2D00063091.hdf5': 'md5:c212f1c8c68db69eae88eca90a19e7e6', - 'SANS2D00063114.nxs': 'md5:b3a3f7527ef871d728942cac3da52289', - 'SANS2D00063114.hdf5': 'md5:806a5780ff02676afcea1c3d8777ee21', - 'SANS2D00063159.nxs': 'md5:c0a0f376964bd9e8c6364552bc1f94e1', - 'SANS2D00063159.hdf5': 'md5:7be098bcc1f4ca73394584076a99146d', - 'SANS2D_Definition_Tubes.xml': 'md5:ea988c64119bf9eaaa004e5bc41b8c40', - }, - ) - - -_pooch = _make_pooch() - - -def get_path(name: str) -> str: - """ - Return the path to a data file. - - This function only works with example data and cannot handle - paths to custom files. - """ - return _pooch.fetch(name) diff --git a/src/ess/loki/data_files.py b/src/ess/loki/data_files.py new file mode 100644 index 000000000..3ab554c52 --- /dev/null +++ b/src/ess/loki/data_files.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + + +from ..data import Registry + +data_registry = Registry( + instrument='loki', + files={ + 'DIRECT_SANS2D_REAR_34327_4m_8mm_16Feb16.dat': 'md5:d64495831325a63e1b961776a8544599', # noqa: E501 + 'DIRECT_SANS2D_REAR_34327_4m_8mm_16Feb16.hdf5': 'md5:43f4188301d709aa49df0631d03a67cb', # noqa: E501 + 'SANS2D00063091.nxs': 'md5:05929753ea06eca5fe4be164cb06b4d6', + 'SANS2D00063091.hdf5': 'md5:c212f1c8c68db69eae88eca90a19e7e6', + 'SANS2D00063114.nxs': 'md5:b3a3f7527ef871d728942cac3da52289', + 'SANS2D00063114.hdf5': 'md5:806a5780ff02676afcea1c3d8777ee21', + 'SANS2D00063159.nxs': 'md5:c0a0f376964bd9e8c6364552bc1f94e1', + 'SANS2D00063159.hdf5': 'md5:7be098bcc1f4ca73394584076a99146d', + 'SANS2D_Definition_Tubes.xml': 'md5:ea988c64119bf9eaaa004e5bc41b8c40', + }, + version='1', +) From e8a3da101fcc848d1231e38fd7754cee3cbf282d Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 12 Jun 2023 16:29:53 +0200 Subject: [PATCH 09/34] go back to old function naming get_path --- src/ess/amor/__init__.py | 3 +- src/ess/amor/{data_files.py => data.py} | 4 ++- src/ess/data.py | 18 ++--------- src/ess/dream/__init__.py | 2 +- src/ess/dream/{data_files.py => data.py} | 4 ++- src/ess/external/powgen/data.py | 41 +++++++----------------- src/ess/loki/__init__.py | 2 +- src/ess/loki/{data_files.py => data.py} | 5 ++- 8 files changed, 27 insertions(+), 52 deletions(-) rename src/ess/amor/{data_files.py => data.py} (86%) rename src/ess/dream/{data_files.py => data.py} (84%) rename src/ess/loki/{data_files.py => data.py} (94%) diff --git a/src/ess/amor/__init__.py b/src/ess/amor/__init__.py index f0a8f20b6..a74676e05 100644 --- a/src/ess/amor/__init__.py +++ b/src/ess/amor/__init__.py @@ -1,8 +1,7 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) # flake8: noqa: F401 -from . import calibrations, conversions, normalize, resolution, tools -from .data_files import data_registry as data +from . import calibrations, conversions, data, normalize, resolution, tools from .beamline import instrument_view_components, make_beamline from .instrument_view import instrument_view from .load import load diff --git a/src/ess/amor/data_files.py b/src/ess/amor/data.py similarity index 86% rename from src/ess/amor/data_files.py rename to src/ess/amor/data.py index b52406265..8210677c3 100644 --- a/src/ess/amor/data_files.py +++ b/src/ess/amor/data.py @@ -4,7 +4,7 @@ from ..data import Registry -data_registry = Registry( +_registry = Registry( instrument='amor', files={ "reference.nxs": "md5:56d493c8051e1c5c86fb7a95f8ec643b", @@ -12,3 +12,5 @@ }, version='1', ) + +get_path = _registry.get_path diff --git a/src/ess/data.py b/src/ess/data.py index 408ed91ae..43cb6559e 100644 --- a/src/ess/data.py +++ b/src/ess/data.py @@ -7,7 +7,7 @@ class Registry: - def __init__(self, instrument: str, files: Dict[str, str], version: str = '1'): + def __init__(self, instrument: str, files: Dict[str, str], version: str): import pooch self._registry = pooch.create( @@ -19,27 +19,13 @@ def __init__(self, instrument: str, files: Dict[str, str], version: str = '1'): registry=files, ) - def __getitem__(self, name: str) -> str: - """ - Get the path to a file in the registry. - - Parameters - ---------- - name: - Name of the file to get the path for. - """ - return self._registry.fetch(name) - def get_path(self, name: str) -> str: """ Get the path to a file in the registry. - This is the deprecated way of getting a file path, and is mostly there for - backwards compatibility. - Use ``__getitem__`` instead. Parameters ---------- name: Name of the file to get the path for. """ - return self[name] + return self._registry.fetch(name) diff --git a/src/ess/dream/__init__.py b/src/ess/dream/__init__.py index 77d465639..f54e74717 100644 --- a/src/ess/dream/__init__.py +++ b/src/ess/dream/__init__.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -from .data_files import data_registry as data +from . import data from . import diagnostics from .instrument_view import instrument_view diff --git a/src/ess/dream/data_files.py b/src/ess/dream/data.py similarity index 84% rename from src/ess/dream/data_files.py rename to src/ess/dream/data.py index 5c5ff6833..b44159865 100644 --- a/src/ess/dream/data_files.py +++ b/src/ess/dream/data.py @@ -3,10 +3,12 @@ from ..data import Registry -data_registry = Registry( +_registry = Registry( instrument='dream', files={ 'data_dream_HF_mil_closed_alldets_1e9.csv.zip': 'md5:2997f33d3ddc792083bfab661cf1d93a' }, version='1', ) + +get_path = _registry.get_path diff --git a/src/ess/external/powgen/data.py b/src/ess/external/powgen/data.py index b1bb085f2..de21b1636 100644 --- a/src/ess/external/powgen/data.py +++ b/src/ess/external/powgen/data.py @@ -1,38 +1,21 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -_version = '1' -__all__ = ['get_path'] +from ...data import Registry -def _make_pooch(): - import pooch +_registry = Registry( + instrument='powgen', + files={ + 'PG3_4844_event.nxs': 'md5:d5ae38871d0a09a28ae01f85d969de1e', + 'PG3_4866_event.nxs': 'md5:3d543bc6a646e622b3f4542bc3435e7e', + 'PG3_5226_event.nxs': 'md5:58b386ebdfeb728d34fd3ba00a2d4f1e', + 'PG3_FERNS_d4832_2011_08_24.cal': 'md5:c181221ebef9fcf30114954268c7a6b6', + }, + version='1', +) - return pooch.create( - path=pooch.os_cache('ess/powgen'), - env='ESS_DATA_DIR', - base_url='https://public.esss.dk/groups/scipp/ess/powgen/{version}/', - version=_version, - registry={ - 'PG3_4844_event.nxs': 'md5:d5ae38871d0a09a28ae01f85d969de1e', - 'PG3_4866_event.nxs': 'md5:3d543bc6a646e622b3f4542bc3435e7e', - 'PG3_5226_event.nxs': 'md5:58b386ebdfeb728d34fd3ba00a2d4f1e', - 'PG3_FERNS_d4832_2011_08_24.cal': 'md5:c181221ebef9fcf30114954268c7a6b6', - }, - ) - - -_pooch = _make_pooch() - - -def get_path(name: str) -> str: - """ - Return the path to a data file bundled with scippneutron. - - This function only works with example data and cannot handle - paths to custom files. - """ - return _pooch.fetch(name) +get_path = _registry.get_path def sample_file() -> str: diff --git a/src/ess/loki/__init__.py b/src/ess/loki/__init__.py index a443d88c5..069bb5ebe 100644 --- a/src/ess/loki/__init__.py +++ b/src/ess/loki/__init__.py @@ -3,5 +3,5 @@ # flake8: noqa: F401 -from .data_files import data_registry as data +from . import data from .io import load_rkh_q, load_rkh_wav, load_sans2d diff --git a/src/ess/loki/data_files.py b/src/ess/loki/data.py similarity index 94% rename from src/ess/loki/data_files.py rename to src/ess/loki/data.py index 3ab554c52..3fda3007b 100644 --- a/src/ess/loki/data_files.py +++ b/src/ess/loki/data.py @@ -4,7 +4,7 @@ from ..data import Registry -data_registry = Registry( +_registry = Registry( instrument='loki', files={ 'DIRECT_SANS2D_REAR_34327_4m_8mm_16Feb16.dat': 'md5:d64495831325a63e1b961776a8544599', # noqa: E501 @@ -19,3 +19,6 @@ }, version='1', ) + + +get_path = _registry.get_path From 914ce18fd22a928d708cef83b7fdb8b0c05165cf Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 12 Jun 2023 16:34:31 +0200 Subject: [PATCH 10/34] fix notebook syntax --- docs/instruments/dream/dream-diagnostics.ipynb | 5 +++-- docs/instruments/dream/dream.rst | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/instruments/dream/dream-diagnostics.ipynb b/docs/instruments/dream/dream-diagnostics.ipynb index 8d916c106..7ccabb724 100644 --- a/docs/instruments/dream/dream-diagnostics.ipynb +++ b/docs/instruments/dream/dream-diagnostics.ipynb @@ -49,7 +49,7 @@ "metadata": {}, "outputs": [], "source": [ - "filename = dream.data['data_dream_HF_mil_closed_alldets_1e9.csv.zip']\n", + "filename = dream.data.get_path('data_dream_HF_mil_closed_alldets_1e9.csv.zip')\n", "df = pd.read_table(filename)\n", "ds = sc.compat.from_pandas(df)\n", "del ds.coords['row'] # we have no use for this row index\n", @@ -122,7 +122,8 @@ }, "outputs": [], "source": [ - "# In order to keep the size of the doc pages down, we only keep 1 in 10 pixels\n", + "# In order to keep the size of the doc pages down, we only keep 1 in 10 pixels.\n", + "# Note that this cell is hidden in the docs.\n", "dg = sc.DataGroup({key: da['pixel', ::10] for key, da in dg.flatten(to='pixel').items()})" ] }, diff --git a/docs/instruments/dream/dream.rst b/docs/instruments/dream/dream.rst index b6dd5d1b1..edbe8bd16 100644 --- a/docs/instruments/dream/dream.rst +++ b/docs/instruments/dream/dream.rst @@ -4,4 +4,4 @@ DREAM .. toctree:: :maxdepth: 4 - dream_diagnostics + dream-diagnostics From c0a1716865994a9bd7f0f21d162bbce0bf675728 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 28 Jun 2023 10:16:05 +0200 Subject: [PATCH 11/34] generalize instrument view --- src/ess/dream/instrument_view.py | 42 ++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 1b156c2c0..b59075857 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -9,10 +9,14 @@ def instrument_view( data: Union[sc.DataArray, sc.DataGroup], + x: Optional[str] = None, + y: Optional[str] = None, + z: Optional[str] = None, + pos: Optional[str] = None, dim: Optional[str] = None, bins: Union[sc.Variable, int] = 50, pixel_size: Union[sc.Variable, float] = 10, - **kwargs + **kwargs, ): """ Three-dimensional visualization of the DREAM instrument pixels. @@ -24,6 +28,17 @@ def instrument_view( ---------- data: Data to visualize. + x: + The name of the coordinate that is to be used for the X positions. + Default is 'x'. + y: + The name of the coordinate that is to be used for the Y positions. + Default is 'y'. + z: + The name of the coordinate that is to be used for the Z positions. + Default is 'z'. + pos: + The name of the vector coordinate that is to be used for the positions. dim: Dimension to use for the slider. bins: @@ -31,6 +46,9 @@ def instrument_view( pixel_size: Size of the pixels. If a float is provided, it will assume the same unit as the pixel coordinates. + **kwargs: + Additional arguments to pass to the plopp figure + (see https://scipp.github.io/plopp/about/generated/plopp.scatter3d.html). """ from plopp.graphics import figure3d import plopp.widgets as pw @@ -40,6 +58,26 @@ def instrument_view( else: data = data.flatten(to='pixel') + if pos is not None: + if any((x, y, z)): + raise ValueError( + f'If pos ({pos}) is defined, all of ' + f'x ({x}), y ({y}), and z ({z}) must be None.' + ) + coords = { + (x := f'{pos}.x'): data.coords[pos].fields.x, + (y := f'{pos}.y'): data.coords[pos].fields.y, + (z := f'{pos}.z'): data.coords[pos].fields.z, + } + else: + x = x if x is not None else 'x' + y = y if y is not None else 'y' + z = z if z is not None else 'z' + coords = {k: data.coords[k] for k in (x, y, z)} + + # No need to make a copy here because one was made higher up with `flatten`. + data.coords.update(coords) + if dim is not None: histogrammed = data.hist({dim: bins}) if data.bins is not None else data slider_widget = pw.SliceWidget(histogrammed, dims=[dim]) @@ -49,7 +87,7 @@ def instrument_view( else: nodes = [pp.Node(data.hist() if data.bins is not None else data)] - fig = figure3d(*nodes, x='x', y='y', z='z', pixel_size=pixel_size, **kwargs) + fig = figure3d(*nodes, x=x, y=y, z=z, pixel_size=pixel_size, **kwargs) tri_cutter = pw.TriCutTool(fig) fig.toolbar['cut3d'] = pw.ToggleTool( callback=tri_cutter.toggle_visibility, From 0be277060abd14ed2cdd3e370b4de9f81d61a3c0 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 5 Sep 2023 16:03:04 +0200 Subject: [PATCH 12/34] improve instrument view --- src/ess/dream/instrument_view.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index b59075857..83620bb12 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -14,8 +14,7 @@ def instrument_view( z: Optional[str] = None, pos: Optional[str] = None, dim: Optional[str] = None, - bins: Union[sc.Variable, int] = 50, - pixel_size: Union[sc.Variable, float] = 10, + pixel_size: Union[sc.Variable, float] = sc.scalar(1.0, unit='cm'), **kwargs, ): """ @@ -41,8 +40,6 @@ def instrument_view( The name of the vector coordinate that is to be used for the positions. dim: Dimension to use for the slider. - bins: - Number of bins to use for the slider. pixel_size: Size of the pixels. If a float is provided, it will assume the same unit as the pixel coordinates. @@ -53,10 +50,14 @@ def instrument_view( from plopp.graphics import figure3d import plopp.widgets as pw + dims = list(data.dims) + if dim is not None: + dims.remove(dim) + to = 'pixel' if not isinstance(data, sc.DataArray): - data = sc.concat([da.flatten(to='pixel') for da in data.values()], dim='pixel') + data = sc.concat([da.flatten(dims=dims, to=to) for da in data.values()], dim=to) else: - data = data.flatten(to='pixel') + data = data.flatten(dims=dims, to=to) if pos is not None: if any((x, y, z)): @@ -79,13 +80,12 @@ def instrument_view( data.coords.update(coords) if dim is not None: - histogrammed = data.hist({dim: bins}) if data.bins is not None else data - slider_widget = pw.SliceWidget(histogrammed, dims=[dim]) + slider_widget = pw.SliceWidget(data, dims=[dim]) slider_widget.controls[dim]['slider'].layout = {"width": "400px"} slider_node = pp.widget_node(slider_widget) - nodes = [pw.slice_dims(data_array=histogrammed, slices=slider_node)] + nodes = [pw.slice_dims(data_array=data, slices=slider_node)] else: - nodes = [pp.Node(data.hist() if data.bins is not None else data)] + nodes = [pp.Node(data)] fig = figure3d(*nodes, x=x, y=y, z=z, pixel_size=pixel_size, **kwargs) tri_cutter = pw.TriCutTool(fig) From 7929cf4b3efb6f11e060bd37816c5ba278f8ba6c Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 13 Sep 2023 16:57:11 +0200 Subject: [PATCH 13/34] we know which dimensions to flatten --- src/ess/dream/instrument_view.py | 52 +++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 83620bb12..6de99caa9 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -1,11 +1,45 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -from typing import Optional, Union +from typing import List, Optional, Union import plopp as pp import scipp as sc +DREAM_DETECTOR_DIMENSIONS = ('module', 'segment', 'counter', 'wire', 'strip') + + +def _preprocess_data( + data: sc.DataArray, to_be_flattened: List[str], dim: str, to: str +) -> sc.DataArray: + """ + The 3D scatter visualization requires a flattened one-dimensional data array. + This function flattens the data array along the dimensions that are known to be + detector dimensions. + Because flattening can only happen for contiguous dimensions, the data array is + transposed to the correct dimension order before flattening. + + Parameters + ---------- + data: + Data to be flattened. + to_be_flattened: + List of dimensions to be flattened. + dim: + Dimension to be used for the slider (this will not be flattened) + to: + Name of the new dimension to which the data will be flattened. + """ + if not to_be_flattened: + # Need to return a copy here because `flatten` makes a copy below. + return data.copy(deep=False) + transpose = list(data.dims) + if dim is not None: + # Move slider dim to the end of the list to allow flattening of the other dims + transpose.remove(dim) + transpose.append(dim) + return data.transpose(dims=transpose).flatten(dims=to_be_flattened, to=to) + def instrument_view( data: Union[sc.DataArray, sc.DataGroup], @@ -50,14 +84,18 @@ def instrument_view( from plopp.graphics import figure3d import plopp.widgets as pw - dims = list(data.dims) - if dim is not None: - dims.remove(dim) + dims = [d for d in data.dims if (d in DREAM_DETECTOR_DIMENSIONS) and (d != dim)] to = 'pixel' if not isinstance(data, sc.DataArray): - data = sc.concat([da.flatten(dims=dims, to=to) for da in data.values()], dim=to) + data = sc.concat( + [ + _preprocess_data(data=da, to_be_flattened=dims, dim=dim, to=to) + for da in data.values() + ], + dim=to, + ) else: - data = data.flatten(dims=dims, to=to) + data = _preprocess_data(data=data, to_be_flattened=dims, dim=dim, to=to) if pos is not None: if any((x, y, z)): @@ -76,7 +114,7 @@ def instrument_view( z = z if z is not None else 'z' coords = {k: data.coords[k] for k in (x, y, z)} - # No need to make a copy here because one was made higher up with `flatten`. + # No need to make a copy here because one was made higher up with `preprocess_data`. data.coords.update(coords) if dim is not None: From 12307fadc8230b56225b2218e4ae876e517266d3 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 13 Sep 2023 17:01:13 +0200 Subject: [PATCH 14/34] remove diagnostics --- docs/index.rst | 1 - .../instruments/dream/dream-diagnostics.ipynb | 179 ------------------ docs/instruments/dream/dream.rst | 7 - src/ess/dream/__init__.py | 1 - src/ess/dream/diagnostics.py | 127 ------------- tests/dream/tools_test.py | 62 ------ 6 files changed, 377 deletions(-) delete mode 100644 docs/instruments/dream/dream-diagnostics.ipynb delete mode 100644 docs/instruments/dream/dream.rst delete mode 100644 src/ess/dream/diagnostics.py delete mode 100644 tests/dream/tools_test.py diff --git a/docs/index.rst b/docs/index.rst index e8f65eee6..d75ef2708 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,7 +50,6 @@ This involves a "filtering" process, since scope and contribution guidelines are :caption: Instruments instruments/amor/amor - instruments/dream/dream instruments/loki/loki instruments/external/external diff --git a/docs/instruments/dream/dream-diagnostics.ipynb b/docs/instruments/dream/dream-diagnostics.ipynb deleted file mode 100644 index 7ccabb724..000000000 --- a/docs/instruments/dream/dream-diagnostics.ipynb +++ /dev/null @@ -1,179 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "4852fe41-3f9a-4cdb-8aba-ff7c7f198e6c", - "metadata": {}, - "source": [ - "# DREAM diagnostics\n", - "\n", - "This notebook contains a set of visualizations for the DREAM detectors.\n", - "The main purpose of such visualizations are to provide quick diagnostics on the detector data,\n", - "which help determine if some of the detector components (wires, strips, modules...) are faulty or broken." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3f416595-83b4-44d1-b506-9ba73bf0786e", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import scipp as sc\n", - "import plopp as pp\n", - "import matplotlib.pyplot as plt\n", - "from ess.dream import diagnostics as diags\n", - "from ess import dream" - ] - }, - { - "cell_type": "markdown", - "id": "228a23dc-17f2-4273-a4c9-3e3276db8c54", - "metadata": {}, - "source": [ - "## Load and reshape the data\n", - "\n", - "We load a data set from a Geant4 simulation (stored as a `.csv` file).\n", - "After loading, we manipulate and reshape the data to obtain a data group that contains one entry per detector bank.\n", - "In each detector bank, the data is organised by `wire`, `strip`, `module`, `segment`, and `counter`.\n", - "\n", - "It is assumed that Nexus files from the DREAM beamline will be loaded as such by Scippnexus,\n", - "and that the manipulations required below would not be needed for production datasets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ebef41be-fe1c-46d8-80d6-da43b4ff9a73", - "metadata": {}, - "outputs": [], - "source": [ - "filename = dream.data.get_path('data_dream_HF_mil_closed_alldets_1e9.csv.zip')\n", - "df = pd.read_table(filename)\n", - "ds = sc.compat.from_pandas(df)\n", - "del ds.coords['row'] # we have no use for this row index\n", - "# Some logic to parse units in the file header\n", - "for key in list(ds):\n", - " name, *remainder = key.split(' ')\n", - " ds[name] = ds.pop(key)\n", - " ds[name].unit = remainder[0][1:-1] if remainder else None\n", - "ds['lambda'].unit = 'angstrom'\n", - "table = sc.DataArray(sc.ones(sizes=ds.sizes, unit='counts'))\n", - "for name in ds:\n", - " table.coords[name] = ds[name].data\n", - "# Group pixels according to their bank identifier\n", - "mapping = {'sumo0': 0, 'sumo1': 1, 'sumo2': 2, 'sumo3': 3, 'mantle': 4, 'high_res': 5}\n", - "da = table.group('det')\n", - "dg = sc.DataGroup({key: da['det', val] for key, val in mapping.items()})\n", - "dg = sc.DataGroup({key: diags.to_logical_dims(da) for key, da in dg.items()})\n", - "for da in dg.values():\n", - " da.coords['x'] = da.bins.coords.pop('x_pos').bins.mean()\n", - " da.coords['y'] = da.bins.coords.pop('y_pos').bins.mean()\n", - " da.coords['z'] = da.bins.coords.pop('z_pos').bins.mean()\n", - "dg" - ] - }, - { - "cell_type": "markdown", - "id": "5ac73609-f473-4c73-b445-7a92fe9de45a", - "metadata": {}, - "source": [ - "## Detector diagnostic plots\n", - "\n", - "We now make a single large figure showing some of the most commonly used plots,\n", - "with one column per detector bank." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01ec9dda-6537-405e-afa3-0240268f5b3e", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(4, len(dg), figsize=(len(dg) * 3, 4 * 3))\n", - "for i, (name, da) in enumerate(dg.items()):\n", - " da.bins.concat(list(set(da.dims) - {'module'})).hist().plot(ax=ax[0, i], title=name)\n", - " diags.module_vs_segment(da).hist().plot(ax=ax[1, i])\n", - " da.bins.concat(list(set(da.dims) - {'strip'})).hist().plot(ax=ax[2, i])\n", - " diags.wire_vs_strip(da).hist().plot(ax=ax[3, i])\n", - "fig.subplots_adjust(wspace=0.5, hspace=0.5)" - ] - }, - { - "cell_type": "markdown", - "id": "49595486-ab4e-4662-86f7-d301aedcf974", - "metadata": {}, - "source": [ - "## Instrument view\n", - "\n", - "We also show the 3D view of the instrument pixels.\n", - "The DREAM-specific instrument view is also capable of slicing the data along the `tof` dimension by using the argument `dim='tof'`\n", - "(use the `bins` argument to change how many bins will be used to histogram the `tof` dimension)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34d80492-7593-4969-ae97-f292bde5f66b", - "metadata": { - "nbsphinx": "hidden" - }, - "outputs": [], - "source": [ - "# In order to keep the size of the doc pages down, we only keep 1 in 10 pixels.\n", - "# Note that this cell is hidden in the docs.\n", - "dg = sc.DataGroup({key: da['pixel', ::10] for key, da in dg.flatten(to='pixel').items()})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dd768225-1776-41e3-9fe0-03dae565e5b9", - "metadata": {}, - "outputs": [], - "source": [ - "dream.instrument_view(dg, dim='tof', bins=10, pixel_size=20)" - ] - }, - { - "cell_type": "markdown", - "id": "76d53a44-979c-4968-902e-e625ef5a0cbd", - "metadata": {}, - "source": [ - "Finally, it is also possible to look at a single bank using" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "43f9ffbc-6bf5-4407-b3ad-5d1626efc43d", - "metadata": {}, - "outputs": [], - "source": [ - "dream.instrument_view(dg['mantle'], pixel_size=20)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/instruments/dream/dream.rst b/docs/instruments/dream/dream.rst deleted file mode 100644 index edbe8bd16..000000000 --- a/docs/instruments/dream/dream.rst +++ /dev/null @@ -1,7 +0,0 @@ -DREAM -===== - -.. toctree:: - :maxdepth: 4 - - dream-diagnostics diff --git a/src/ess/dream/__init__.py b/src/ess/dream/__init__.py index f54e74717..43639dfad 100644 --- a/src/ess/dream/__init__.py +++ b/src/ess/dream/__init__.py @@ -2,5 +2,4 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) from . import data -from . import diagnostics from .instrument_view import instrument_view diff --git a/src/ess/dream/diagnostics.py b/src/ess/dream/diagnostics.py deleted file mode 100644 index 1c70e0f0b..000000000 --- a/src/ess/dream/diagnostics.py +++ /dev/null @@ -1,127 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) - -from typing import Union - -import plopp as pp -import scipp as sc - - -def _group_and_concat(da: sc.DataArray, dims: tuple) -> sc.DataArray: - """ - Group the input data using groups specified by the ``dims``. - Then, remove any extra dimension that is not in ``dims`` by concatenating the - event data in the bins. - - Parameters - ---------- - da: - DataArray to group. - dims: - Coordinates to use for grouping. - """ - in_dims = set(da.dims) - out_dims = set(dims) - if (not dims) or (in_dims == out_dims): - return da - grouped = da.group(*list(out_dims - in_dims)) - return grouped.bins.concat(list(set(grouped.dims) - out_dims)) - - -def to_logical_dims(da: sc.DataArray) -> sc.DataArray: - """ - Reshape the input data to logical dimensions. - - Parameters - ---------- - da: - DataArray to reshape. - """ - return da.group('module', 'segment', 'counter', 'wire', 'strip') - - -def wire_vs_strip(da: sc.DataArray) -> sc.DataArray: - """ - Reshape the input data to wire vs strip dimensions. - - Parameters - ---------- - da: - DataArray to reshape. - """ - return _group_and_concat(da, dims=('wire', 'strip')) - - -def module_vs_segment(da: sc.DataArray) -> sc.DataArray: - """ - Reshape the input data to module vs segment dimensions. - - Parameters - ---------- - da: - DataArray to reshape. - """ - return _group_and_concat(da, dims=('module', 'segment')) - - -def module_vs_wire(da: sc.DataArray) -> sc.DataArray: - """ - Reshape the input data to module vs wire dimensions. - - Parameters - ---------- - da: - DataArray to reshape. - """ - return _group_and_concat(da, dims=('module', 'wire')) - - -def module_vs_strip(da: sc.DataArray) -> sc.DataArray: - """ - Reshape the input data to module vs strip dimensions. - - Parameters - ---------- - da: - DataArray to reshape. - """ - return _group_and_concat(da, dims=('module', 'strip')) - - -def segment_vs_strip(da: sc.DataArray) -> sc.DataArray: - """ - Reshape the input data to segment vs strip dimensions. - - Parameters - ---------- - da: - DataArray to reshape. - """ - return _group_and_concat(da, dims=('segment', 'strip')) - - -def segment_vs_wire(da: sc.DataArray) -> sc.DataArray: - """ - Reshape the input data to segment vs wire dimensions. - - Parameters - ---------- - da: - DataArray to reshape. - """ - return _group_and_concat(da, dims=('segment', 'wire')) - - -def tof_slicer(da: sc.DataArray, bins: Union[int, sc.Variable] = 300) -> sc.DataArray: - """ - Make a plot of the data with a slider to navigate the time-of-flight dimension. - - Parameters - ---------- - da: - DataArray to plot. - bins: - Binning to use for histogramming along the `tof` dimension before plotting. - """ - dims = list(set(da.dims) - {'tof'}) - return pp.slicer(da.hist(tof=bins), keep=dims) diff --git a/tests/dream/tools_test.py b/tests/dream/tools_test.py deleted file mode 100644 index c75541480..000000000 --- a/tests/dream/tools_test.py +++ /dev/null @@ -1,62 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -import numpy as np -import pytest -import scipp as sc - -from ess.dream import tools as tls - - -def make_fake_dream_data(nevents): - params = {'module': 14, 'segment': 6, 'counter': 2, 'wire': 32, 'strip': 256} - coords = { - key: sc.array( - dims=['row'], values=np.random.choice(np.arange(1, val + 1), size=nevents) - ) - for key, val in params.items() - } - return sc.DataArray(data=sc.ones(sizes={'row': nevents}), coords=coords) - - -def test_to_logical_dims(): - da = tls.to_logical_dims(make_fake_dream_data(1000)) - assert set(da.dims) == {'module', 'segment', 'counter', 'wire', 'strip'} - assert da.bins is not None - - -COMBINATIONS = [ - ('wire', 'strip'), - ('module', 'segment'), - ('module', 'wire'), - ('module', 'strip'), - ('segment', 'wire'), - ('segment', 'strip'), -] - - -@pytest.mark.parametrize("dims", COMBINATIONS) -def test_wire_vs_strip_from_table(dims): - func = getattr(tls, dims[0] + '_vs_' + dims[1]) - da = func(make_fake_dream_data(1000)) - assert set(da.dims) == set(dims) - - -@pytest.mark.parametrize("dims", COMBINATIONS) -def test_wire_vs_strip_from_logical(dims): - func = getattr(tls, dims[0] + '_vs_' + dims[1]) - da = func(tls.to_logical_dims(make_fake_dream_data(1000))) - assert set(da.dims) == set(dims) - - -@pytest.mark.parametrize("dims", COMBINATIONS) -def test_wire_vs_strip_first_dim_exists(dims): - func = getattr(tls, dims[0] + '_vs_' + dims[1]) - da = func(make_fake_dream_data(1000).group(dims[0])) - assert set(da.dims) == set(dims) - - -@pytest.mark.parametrize("dims", COMBINATIONS) -def test_wire_vs_strip_second_dim_exists(dims): - func = getattr(tls, dims[0] + '_vs_' + dims[1]) - da = func(make_fake_dream_data(1000).group(dims[1])) - assert set(da.dims) == set(dims) From 86d23aa76112aa6c8cbe40b4e2029a75a355f4aa Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 13 Sep 2023 17:08:19 +0200 Subject: [PATCH 15/34] flake8 --- src/ess/dream/__init__.py | 1 + src/ess/dream/data.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ess/dream/__init__.py b/src/ess/dream/__init__.py index 43639dfad..b9d17476f 100644 --- a/src/ess/dream/__init__.py +++ b/src/ess/dream/__init__.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) +# flake8: noqa: F401 from . import data from .instrument_view import instrument_view diff --git a/src/ess/dream/data.py b/src/ess/dream/data.py index b44159865..51f8cecf1 100644 --- a/src/ess/dream/data.py +++ b/src/ess/dream/data.py @@ -6,7 +6,7 @@ _registry = Registry( instrument='dream', files={ - 'data_dream_HF_mil_closed_alldets_1e9.csv.zip': 'md5:2997f33d3ddc792083bfab661cf1d93a' + 'data_dream_HF_mil_closed_alldets_1e9.csv.zip': 'md5:2997f33d3ddc792083bfab661cf1d93a' # noqa: E501 }, version='1', ) From 117d62b34c583527d3a48c9de9a830dcadcb1fd0 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Thu, 14 Sep 2023 09:08:04 +0200 Subject: [PATCH 16/34] formatting --- src/ess/__init__.py | 3 +-- src/ess/dream/instrument_view.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ess/__init__.py b/src/ess/__init__.py index fb9fac8c2..bb322b779 100644 --- a/src/ess/__init__.py +++ b/src/ess/__init__.py @@ -10,6 +10,5 @@ except importlib.metadata.PackageNotFoundError: __version__ = "0.0.0" -from . import data -from . import logging +from . import data, logging from .logging import get_logger diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 6de99caa9..eae1adb45 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -7,6 +7,7 @@ import scipp as sc DREAM_DETECTOR_DIMENSIONS = ('module', 'segment', 'counter', 'wire', 'strip') +DREAM_PIXEL_SIZE = sc.scalar(1.0, unit='cm') def _preprocess_data( @@ -48,7 +49,7 @@ def instrument_view( z: Optional[str] = None, pos: Optional[str] = None, dim: Optional[str] = None, - pixel_size: Union[sc.Variable, float] = sc.scalar(1.0, unit='cm'), + pixel_size: Union[sc.Variable, float] = DREAM_PIXEL_SIZE, **kwargs, ): """ @@ -81,8 +82,8 @@ def instrument_view( Additional arguments to pass to the plopp figure (see https://scipp.github.io/plopp/about/generated/plopp.scatter3d.html). """ - from plopp.graphics import figure3d import plopp.widgets as pw + from plopp.graphics import figure3d dims = [d for d in data.dims if (d in DREAM_DETECTOR_DIMENSIONS) and (d != dim)] to = 'pixel' From 2e4672fc1793a10d2bd5c07215ed4f699d36bdd1 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 14 Nov 2023 11:06:32 +0100 Subject: [PATCH 17/34] update to new mechanism --- src/ess/dream/instrument_view.py | 240 ++++++++++++++++--------------- 1 file changed, 122 insertions(+), 118 deletions(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index eae1adb45..9d2df3048 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -1,139 +1,143 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) +from html import escape from typing import List, Optional, Union import plopp as pp import scipp as sc -DREAM_DETECTOR_DIMENSIONS = ('module', 'segment', 'counter', 'wire', 'strip') -DREAM_PIXEL_SIZE = sc.scalar(1.0, unit='cm') +def _to_data_group(data: Union[sc.DataArray, sc.DataGroup, dict]) -> sc.DataGroup: + if isinstance(data, sc.DataArray): + data = sc.DataGroup({data.name or 'data': data}) + elif isinstance(data, dict): + data = sc.DataGroup(data) + return data -def _preprocess_data( - data: sc.DataArray, to_be_flattened: List[str], dim: str, to: str -) -> sc.DataArray: - """ - The 3D scatter visualization requires a flattened one-dimensional data array. - This function flattens the data array along the dimensions that are known to be - detector dimensions. - Because flattening can only happen for contiguous dimensions, the data array is - transposed to the correct dimension order before flattening. - Parameters - ---------- - data: - Data to be flattened. - to_be_flattened: - List of dimensions to be flattened. - dim: - Dimension to be used for the slider (this will not be flattened) - to: - Name of the new dimension to which the data will be flattened. - """ - if not to_be_flattened: - # Need to return a copy here because `flatten` makes a copy below. - return data.copy(deep=False) - transpose = list(data.dims) - if dim is not None: - # Move slider dim to the end of the list to allow flattening of the other dims - transpose.remove(dim) - transpose.append(dim) - return data.transpose(dims=transpose).flatten(dims=to_be_flattened, to=to) - - -def instrument_view( - data: Union[sc.DataArray, sc.DataGroup], - x: Optional[str] = None, - y: Optional[str] = None, - z: Optional[str] = None, - pos: Optional[str] = None, - dim: Optional[str] = None, - pixel_size: Union[sc.Variable, float] = DREAM_PIXEL_SIZE, - **kwargs, -): +@pp.node +def slice_range(da, trunc_range): + min_tr, max_tr = trunc_range + return da['tof', min_tr:max_tr].sum('tof') + + +@pp.node +def post_process(da, dim): + dims = list(da.dims) + dims.remove(dim) + out = da.flatten(dims=dims, to='pixel') + sel = sc.isfinite(out.coords['x']) + return out[sel] + + +class InstrumentView: + def __init__(self, data, dim=None, pixel_size=None, **kwargs): + import ipywidgets as ipw + + self.data = _to_data_group(data) + + self.post_process_nodes = { + key: post_process(da, dim) for key, da in self.data.items() + } + + self.children = [] + + if dim is not None: + # Once https://github.com/scipp/plopp/issues/277 is resolved, we can + # use Plopp's range slicer so that the value of the coordinates are + # displayed next to the slider, instead of the raw indices. + self.slider = ipw.IntRangeSlider( + value=[0, self.data.sizes[dim] - 1], + max=self.data.sizes[dim] - 1, + description=dim, + layout={'width': '700px'}, + continuous_update=False, + ) + self.slider_node = pp.widget_node(self.slider) + self.slice_nodes = { + key: slice_range(n, trunc_range=self.slider_node) + for key, n in self.post_process_nodes.items() + } + to_scatter = self.slice_nodes + self.children.append(self.slider) + else: + self.slice_nodes = self.post_process_nodes + to_scatter = self.post_process_nodes + + self.scatter = pp.scatter3d( + to_scatter, + pixel_size=1.0 * sc.Unit('cm') if pixel_size is None else pixel_size, + **kwargs, + ) + + self.children.insert(0, self.scatter) + + if len(self.data) > 1: + self._add_module_control() + + def _add_module_control(self): + import ipywidgets as ipw + + self.fig = self.scatter[0] + self.cutting_tool = self.scatter[1] + self.mapping = { + name: key for name, key in zip(self.data.keys(), self.fig.artists.keys()) + } + self.checkboxes = { + key: ipw.Checkbox( + value=True, + description=f"{escape(key)}", + indent=False, + layout={"width": "initial"}, + ) + for key in self.data + } + + self.modules_widget = ipw.HBox( + [ipw.HTML(value="Modules:     ")] + + list(self.checkboxes.values()) + ) + for key, ch in self.checkboxes.items(): + ch.key = key + ch.observe(self._check_visibility, names='value') + self.cutting_tool.cut_x.button.observe(self._check_visibility, names="value") + self.cutting_tool.cut_y.button.observe(self._check_visibility, names="value") + self.cutting_tool.cut_z.button.observe(self._check_visibility, names="value") + self.children.insert(0, self.modules_widget) + + def _check_visibility(self, _): + # Note that this brute force method of looping over all artists is not optimal + # but it is non-invasive in the sense that it does not require changes to the + # plopp code. If performance becomes an issue, we will consider a different + # approach. + for name, ch in self.checkboxes.items(): + key = self.mapping[name] + val = ch.value + self.fig.artists[key].points.visible = val + for c in "xyz": + cut_nodes = getattr(self.cutting_tool, f'cut_{c}').select_nodes + if key in cut_nodes: + self.fig.artists[cut_nodes[key].id].points.visible = val + + +def instrument_view(data, dim=None, pixel_size=None, **kwargs): """ - Three-dimensional visualization of the DREAM instrument pixels. - By default, the data counts will be integrated for all tofs/wavelengths. - It is possible to add a tof/wavelength slider by specifying the ``dim`` and ``bins`` - arguments (see parameters below). + Three-dimensional visualization of the DREAM instrument. Parameters ---------- data: Data to visualize. - x: - The name of the coordinate that is to be used for the X positions. - Default is 'x'. - y: - The name of the coordinate that is to be used for the Y positions. - Default is 'y'. - z: - The name of the coordinate that is to be used for the Z positions. - Default is 'z'. - pos: - The name of the vector coordinate that is to be used for the positions. dim: - Dimension to use for the slider. + Dimension to use for the slider. No slider will be shown if this is None. pixel_size: - Size of the pixels. If a float is provided, it will assume the same unit as the - pixel coordinates. + Size of the pixels. **kwargs: - Additional arguments to pass to the plopp figure - (see https://scipp.github.io/plopp/about/generated/plopp.scatter3d.html). + Additional arguments are forwarded to the scatter3d figure + (see https://scipp.github.io/plopp/reference/generated/plopp.scatter3d.html). """ - import plopp.widgets as pw - from plopp.graphics import figure3d - - dims = [d for d in data.dims if (d in DREAM_DETECTOR_DIMENSIONS) and (d != dim)] - to = 'pixel' - if not isinstance(data, sc.DataArray): - data = sc.concat( - [ - _preprocess_data(data=da, to_be_flattened=dims, dim=dim, to=to) - for da in data.values() - ], - dim=to, - ) - else: - data = _preprocess_data(data=data, to_be_flattened=dims, dim=dim, to=to) - - if pos is not None: - if any((x, y, z)): - raise ValueError( - f'If pos ({pos}) is defined, all of ' - f'x ({x}), y ({y}), and z ({z}) must be None.' - ) - coords = { - (x := f'{pos}.x'): data.coords[pos].fields.x, - (y := f'{pos}.y'): data.coords[pos].fields.y, - (z := f'{pos}.z'): data.coords[pos].fields.z, - } - else: - x = x if x is not None else 'x' - y = y if y is not None else 'y' - z = z if z is not None else 'z' - coords = {k: data.coords[k] for k in (x, y, z)} - - # No need to make a copy here because one was made higher up with `preprocess_data`. - data.coords.update(coords) - - if dim is not None: - slider_widget = pw.SliceWidget(data, dims=[dim]) - slider_widget.controls[dim]['slider'].layout = {"width": "400px"} - slider_node = pp.widget_node(slider_widget) - nodes = [pw.slice_dims(data_array=data, slices=slider_node)] - else: - nodes = [pp.Node(data)] - - fig = figure3d(*nodes, x=x, y=y, z=z, pixel_size=pixel_size, **kwargs) - tri_cutter = pw.TriCutTool(fig) - fig.toolbar['cut3d'] = pw.ToggleTool( - callback=tri_cutter.toggle_visibility, - icon='cube', - tooltip='Hide/show spatial cutting tool', - ) - out = [fig, tri_cutter] - if dim is not None: - out.append(slider_widget) - return pw.Box(out) + from plopp.widgets import Box + + view = InstrumentView(data, dim=dim, pixel_size=pixel_size, **kwargs) + return Box(view.children) From 72aea28b81a5a894b1d5e3af8e663f6a44604919 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 14 Nov 2023 14:40:30 +0100 Subject: [PATCH 18/34] add notebook to docs --- docs/index.rst | 1 + .../dream/dream-instrument-view.ipynb | 154 ++++++++++++++++++ docs/instruments/dream/dream.rst | 7 + src/ess/dream/instrument_view.py | 3 +- 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 docs/instruments/dream/dream-instrument-view.ipynb create mode 100644 docs/instruments/dream/dream.rst diff --git a/docs/index.rst b/docs/index.rst index d75ef2708..e8f65eee6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,7 @@ This involves a "filtering" process, since scope and contribution guidelines are :caption: Instruments instruments/amor/amor + instruments/dream/dream instruments/loki/loki instruments/external/external diff --git a/docs/instruments/dream/dream-instrument-view.ipynb b/docs/instruments/dream/dream-instrument-view.ipynb new file mode 100644 index 000000000..ddec3cfa2 --- /dev/null +++ b/docs/instruments/dream/dream-instrument-view.ipynb @@ -0,0 +1,154 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4852fe41-3f9a-4cdb-8aba-ff7c7f198e6c", + "metadata": {}, + "source": [ + "# DREAM instrument view\n", + "\n", + "This notebook is a simple example on how to use the instrument view for the DREAM instrument.\n", + "\n", + "- The DREAM-specific instrument view is capable of slicing the data with a slider widget along a dimension (e.g. `tof`) by using the `dim` argument.\n", + "- There are also checkboxes to hide/show the different modules that make up the DREAM detectors." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f416595-83b4-44d1-b506-9ba73bf0786e", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import scipp as sc\n", + "from ess import dream" + ] + }, + { + "cell_type": "markdown", + "id": "228a23dc-17f2-4273-a4c9-3e3276db8c54", + "metadata": {}, + "source": [ + "## Load and reshape the data\n", + "\n", + "We load a data set from a Geant4 simulation (stored as a `.csv` file).\n", + "After loading, we manipulate and reshape the data to obtain a data group that contains one entry per detector bank.\n", + "In each detector bank, the data is organised by `wire`, `strip`, `module`, `segment`, and `counter`.\n", + "\n", + "It is assumed that Nexus files from the DREAM beamline will be loaded as such by Scippnexus,\n", + "and that the manipulations required below would not be needed for production datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebef41be-fe1c-46d8-80d6-da43b4ff9a73", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_table(dream.data.get_path('data_dream_HF_mil_closed_alldets_1e9.csv.zip'))\n", + "ds = sc.compat.from_pandas(df)\n", + "# Some logic to parse units in the file header\n", + "for key in list(ds):\n", + " name, *remainder = key.split(' ')\n", + " ds[name] = ds.pop(key)\n", + " ds[name].unit = remainder[0][1:-1] if remainder else None\n", + "ds['lambda'].unit = 'angstrom'\n", + "table = sc.DataArray(sc.ones(sizes=ds.sizes, unit='counts'))\n", + "for name in ds:\n", + " table.coords[name] = ds[name].data\n", + "table.coords['tof'] = table.coords['tof'].to(dtype=float, unit='us')\n", + "# Group pixels according to their bank identifier\n", + "mapping = {'sumo0': 0, 'sumo1': 1, 'sumo2': 2, 'sumo3': 3, 'mantle': 4, 'high_res': 5}\n", + "da = table[::10].copy().group('det') # Limit number of points rendered for docs size\n", + "dg = sc.DataGroup({key: da['det', val] for key, val in mapping.items()})\n", + "dg = sc.DataGroup({key: da.group('module', 'segment', 'counter', 'wire', 'strip') for key, da in dg.items()})\n", + "for da in dg.values():\n", + " da.coords['x'] = da.bins.coords.pop('x_pos').bins.mean()\n", + " da.coords['y'] = da.bins.coords.pop('y_pos').bins.mean()\n", + " da.coords['z'] = da.bins.coords.pop('z_pos').bins.mean()\n", + "dg" + ] + }, + { + "cell_type": "markdown", + "id": "49595486-ab4e-4662-86f7-d301aedcf974", + "metadata": {}, + "source": [ + "## Instrument view\n", + "\n", + "We first histogram the data along the time-of-flight (`tof`) dimension,\n", + "making sure the same bins are used for all modules:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e85a8364-e0a1-4c10-8cae-c873f297e651", + "metadata": {}, + "outputs": [], + "source": [ + "tof_edges = sc.linspace('tof', 1.0e4, 1.0e5, 101, unit='us')\n", + "data = dg.hist(tof=tof_edges)" + ] + }, + { + "cell_type": "markdown", + "id": "d08ca911-b1a4-4f17-ba1e-355971531ffe", + "metadata": {}, + "source": [ + "We now use the `instrument_view` function to show the 3D view of the instrument pixels,\n", + "specifying that we wish to have a slider along the `tof` dimension:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43f9ffbc-6bf5-4407-b3ad-5d1626efc43d", + "metadata": {}, + "outputs": [], + "source": [ + "dream.instrument_view(data, dim='tof')" + ] + }, + { + "cell_type": "markdown", + "id": "c0b29ebf-21ff-4385-bf8b-0e4fa14dfaf9", + "metadata": {}, + "source": [ + "It is also possible to view a single detector module using" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b417011e-0d12-4287-91d5-c1fb6ecc7cac", + "metadata": {}, + "outputs": [], + "source": [ + "dream.instrument_view(data['mantle'], dim='tof')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/instruments/dream/dream.rst b/docs/instruments/dream/dream.rst new file mode 100644 index 000000000..4fc00b817 --- /dev/null +++ b/docs/instruments/dream/dream.rst @@ -0,0 +1,7 @@ +DREAM +===== + +.. toctree:: + :maxdepth: 4 + + dream-instrument-view diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 9d2df3048..80ee255a0 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -25,7 +25,8 @@ def slice_range(da, trunc_range): @pp.node def post_process(da, dim): dims = list(da.dims) - dims.remove(dim) + if dim is not None: + dims.remove(dim) out = da.flatten(dims=dims, to='pixel') sel = sc.isfinite(out.coords['x']) return out[sel] From 52523dc7c2e83996b30e2649aeb0798e03a01d2c Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 14 Nov 2023 14:42:13 +0100 Subject: [PATCH 19/34] unused imports --- src/ess/dream/instrument_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 80ee255a0..4cb4964e8 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -2,7 +2,7 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) from html import escape -from typing import List, Optional, Union +from typing import Union import plopp as pp import scipp as sc From e190692db16a807d43ec5f1b73953b460fd7cb5b Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 14 Nov 2023 16:33:50 +0100 Subject: [PATCH 20/34] cleanup --- .../dream/dream-instrument-view.ipynb | 7 +- src/ess/dream/instrument_view.py | 4 +- tests/dream/dream_instrument_view_test.py | 71 +++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 tests/dream/dream_instrument_view_test.py diff --git a/docs/instruments/dream/dream-instrument-view.ipynb b/docs/instruments/dream/dream-instrument-view.ipynb index ddec3cfa2..22b9f7429 100644 --- a/docs/instruments/dream/dream-instrument-view.ipynb +++ b/docs/instruments/dream/dream-instrument-view.ipynb @@ -63,7 +63,12 @@ "mapping = {'sumo0': 0, 'sumo1': 1, 'sumo2': 2, 'sumo3': 3, 'mantle': 4, 'high_res': 5}\n", "da = table[::10].copy().group('det') # Limit number of points rendered for docs size\n", "dg = sc.DataGroup({key: da['det', val] for key, val in mapping.items()})\n", - "dg = sc.DataGroup({key: da.group('module', 'segment', 'counter', 'wire', 'strip') for key, da in dg.items()})\n", + "dg = sc.DataGroup(\n", + " {\n", + " key: da.group('module', 'segment', 'counter', 'wire', 'strip')\n", + " for key, da in dg.items()\n", + " }\n", + ")\n", "for da in dg.values():\n", " da.coords['x'] = da.bins.coords.pop('x_pos').bins.mean()\n", " da.coords['y'] = da.bins.coords.pop('y_pos').bins.mean()\n", diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 4cb4964e8..c48a5ce4d 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -82,7 +82,7 @@ def _add_module_control(self): self.fig = self.scatter[0] self.cutting_tool = self.scatter[1] - self.mapping = { + self.artist_mapping = { name: key for name, key in zip(self.data.keys(), self.fig.artists.keys()) } self.checkboxes = { @@ -113,7 +113,7 @@ def _check_visibility(self, _): # plopp code. If performance becomes an issue, we will consider a different # approach. for name, ch in self.checkboxes.items(): - key = self.mapping[name] + key = self.artist_mapping[name] val = ch.value self.fig.artists[key].points.visible = val for c in "xyz": diff --git a/tests/dream/dream_instrument_view_test.py b/tests/dream/dream_instrument_view_test.py new file mode 100644 index 000000000..d67bde6c6 --- /dev/null +++ b/tests/dream/dream_instrument_view_test.py @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + +import numpy as np +import pytest +import scipp as sc + +from ess.dream.instrument_view import InstrumentView + + +@pytest.fixture +def fake_instrument_data(modules=('bank1', 'bank2', 'bank3', 'bank4', 'bank5')): + rng = np.random.default_rng() + + out = {} + npix = 300 + ntof = 100 + locations = range(len(modules)) + for name, loc in zip(modules, locations): + position = rng.normal(loc=[0, 0, loc], scale=[0.2, 0.2, 0.05], size=[npix, 3]) + tof = sc.linspace('tof', 0, 1.0e5, ntof + 1, unit='us') + values = rng.normal(loc=5.0e4, scale=2.0e4, size=[npix, ntof]) + vec = sc.vectors(dims=['pixel'], unit='m', values=position) + out[name] = sc.DataArray( + data=sc.array(dims=['pixel', 'tof'], values=values, unit='counts'), + coords={ + 'position': vec, + 'x': vec.fields.x, + 'y': vec.fields.y, + 'z': vec.fields.z, + 'tof': tof, + }, + ) + return sc.DataGroup(out) + + +def test_instrument_view_all_modules(fake_instrument_data): + view = InstrumentView(fake_instrument_data, dim='tof') + assert hasattr(view, 'checkboxes') + assert hasattr(view, 'scatter') + assert hasattr(view, 'slider') + + +def test_instrument_view_one_module(fake_instrument_data): + view = InstrumentView(fake_instrument_data['bank1'], dim='tof') + assert not hasattr(view, 'checkboxes') + assert hasattr(view, 'scatter') + assert hasattr(view, 'slider') + + +def test_instrument_view_no_tof_slider(fake_instrument_data): + view = InstrumentView(fake_instrument_data.sum('tof')) + assert hasattr(view, 'checkboxes') + assert hasattr(view, 'scatter') + assert not hasattr(view, 'slider') + + +def test_instrument_view_one_module_no_tof_slider(fake_instrument_data): + view = InstrumentView(fake_instrument_data['bank3'].sum('tof')) + assert not hasattr(view, 'checkboxes') + assert hasattr(view, 'scatter') + assert not hasattr(view, 'slider') + + +def test_instrument_view_toggle_module(fake_instrument_data): + view = InstrumentView(fake_instrument_data, dim='tof') + for name in fake_instrument_data: + key = view.artist_mapping[name] + assert view.fig.artists[key].points.visible + view.checkboxes[name].value = False + assert not view.fig.artists[key].points.visible From c0828bc72e02128902663bbaceacd7df12de20f0 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 14 Nov 2023 16:49:10 +0100 Subject: [PATCH 21/34] need plopp23.10 --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 5fc1cdf22..cfd8fc5e8 100644 --- a/environment.yml +++ b/environment.yml @@ -20,7 +20,7 @@ dependencies: - pandas=2.0.3 - pandoc=3.1.3 - pip=23.2.1 - - plopp=23.09.0 + - plopp=23.10.1 - pooch=1.7.0 - pytest=7.4.0 - python=3.8.* From a08857c7e8a57ff5b67dcb52c4a79acfb90328ff Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 14 Nov 2023 17:12:49 +0100 Subject: [PATCH 22/34] debug prints --- src/ess/external/powgen/data.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ess/external/powgen/data.py b/src/ess/external/powgen/data.py index 9216dbe4f..e2d26d43d 100644 --- a/src/ess/external/powgen/data.py +++ b/src/ess/external/powgen/data.py @@ -40,15 +40,18 @@ def mantid_calibration_file() -> str: def sample_file() -> str: + print(get_path('PG3_4844_event.zip', unzip=True)) (path,) = get_path('PG3_4844_event.zip', unzip=True) return path def vanadium_file() -> str: + print(get_path('PG3_4866_event.zip', unzip=True)) (path,) = get_path('PG3_4866_event.zip', unzip=True) return path def calibration_file() -> str: + print(get_path('PG3_FERNS_d4832_2011_08_24.zip', unzip=True)) (path,) = get_path('PG3_FERNS_d4832_2011_08_24.zip', unzip=True) return path From a7954bfa367e0a5fd5cbdfb251f5fda312fe2a57 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Thu, 16 Nov 2023 13:22:20 +0100 Subject: [PATCH 23/34] do not use the same instance of Unzip --- src/ess/data.py | 6 +++--- src/ess/external/powgen/data.py | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/ess/data.py b/src/ess/data.py index 451eebd52..bc801032f 100644 --- a/src/ess/data.py +++ b/src/ess/data.py @@ -19,8 +19,6 @@ def __init__(self, instrument: str, files: Dict[str, str], version: str): registry=files, ) - self._unzip = pooch.Unzip() - def get_path(self, name: str, unzip: bool = False) -> str: """ Get the path to a file in the registry. @@ -32,4 +30,6 @@ def get_path(self, name: str, unzip: bool = False) -> str: unzip: If `True`, unzip the file before returning the path. """ - return self._registry.fetch(name, processor=self._unzip if unzip else None) + import pooch + + return self._registry.fetch(name, processor=pooch.Unzip() if unzip else None) diff --git a/src/ess/external/powgen/data.py b/src/ess/external/powgen/data.py index e2d26d43d..9216dbe4f 100644 --- a/src/ess/external/powgen/data.py +++ b/src/ess/external/powgen/data.py @@ -40,18 +40,15 @@ def mantid_calibration_file() -> str: def sample_file() -> str: - print(get_path('PG3_4844_event.zip', unzip=True)) (path,) = get_path('PG3_4844_event.zip', unzip=True) return path def vanadium_file() -> str: - print(get_path('PG3_4866_event.zip', unzip=True)) (path,) = get_path('PG3_4866_event.zip', unzip=True) return path def calibration_file() -> str: - print(get_path('PG3_FERNS_d4832_2011_08_24.zip', unzip=True)) (path,) = get_path('PG3_FERNS_d4832_2011_08_24.zip', unzip=True) return path From 7b5fc253dbfff2df1ac0294c2e46d413504f75c8 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Fri, 17 Nov 2023 10:14:00 +0100 Subject: [PATCH 24/34] use index slicing instead of range slicing for much better performance --- src/ess/dream/instrument_view.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index c48a5ce4d..a082ec7a7 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -23,7 +23,7 @@ def slice_range(da, trunc_range): @pp.node -def post_process(da, dim): +def pre_process(da, dim): dims = list(da.dims) if dim is not None: dims.remove(dim) @@ -34,37 +34,28 @@ def post_process(da, dim): class InstrumentView: def __init__(self, data, dim=None, pixel_size=None, **kwargs): - import ipywidgets as ipw + from plopp.widgets import SliceWidget, slice_dims self.data = _to_data_group(data) - - self.post_process_nodes = { - key: post_process(da, dim) for key, da in self.data.items() + self.pre_process_nodes = { + key: pre_process(da, dim) for key, da in self.data.items() } self.children = [] if dim is not None: - # Once https://github.com/scipp/plopp/issues/277 is resolved, we can - # use Plopp's range slicer so that the value of the coordinates are - # displayed next to the slider, instead of the raw indices. - self.slider = ipw.IntRangeSlider( - value=[0, self.data.sizes[dim] - 1], - max=self.data.sizes[dim] - 1, - description=dim, - layout={'width': '700px'}, - continuous_update=False, - ) + self.slider = SliceWidget(next(iter(self.data.values())), dims=[dim]) + self.slider.controls[dim]['slider'].layout = {'width': '600px'} self.slider_node = pp.widget_node(self.slider) self.slice_nodes = { - key: slice_range(n, trunc_range=self.slider_node) - for key, n in self.post_process_nodes.items() + key: slice_dims(n, self.slider_node) + for key, n in self.pre_process_nodes.items() } to_scatter = self.slice_nodes self.children.append(self.slider) else: - self.slice_nodes = self.post_process_nodes - to_scatter = self.post_process_nodes + self.slice_nodes = self.pre_process_nodes + to_scatter = self.pre_process_nodes self.scatter = pp.scatter3d( to_scatter, From e63d39bc395c7bb2dade19f5c0e062c0070072e9 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Fri, 17 Nov 2023 10:15:17 +0100 Subject: [PATCH 25/34] update notebook --- .../dream/dream-instrument-view.ipynb | 100 ++++++++++++++++-- 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/docs/instruments/dream/dream-instrument-view.ipynb b/docs/instruments/dream/dream-instrument-view.ipynb index 22b9f7429..4542dba4a 100644 --- a/docs/instruments/dream/dream-instrument-view.ipynb +++ b/docs/instruments/dream/dream-instrument-view.ipynb @@ -44,7 +44,13 @@ "cell_type": "code", "execution_count": null, "id": "ebef41be-fe1c-46d8-80d6-da43b4ff9a73", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ "df = pd.read_table(dream.data.get_path('data_dream_HF_mil_closed_alldets_1e9.csv.zip'))\n", @@ -61,7 +67,7 @@ "table.coords['tof'] = table.coords['tof'].to(dtype=float, unit='us')\n", "# Group pixels according to their bank identifier\n", "mapping = {'sumo0': 0, 'sumo1': 1, 'sumo2': 2, 'sumo3': 3, 'mantle': 4, 'high_res': 5}\n", - "da = table[::10].copy().group('det') # Limit number of points rendered for docs size\n", + "da = table[::5].copy().group('det') # Limit number of points rendered for docs size\n", "dg = sc.DataGroup({key: da['det', val] for key, val in mapping.items()})\n", "dg = sc.DataGroup(\n", " {\n", @@ -79,7 +85,13 @@ { "cell_type": "markdown", "id": "49595486-ab4e-4662-86f7-d301aedcf974", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "## Instrument view\n", "\n", @@ -91,17 +103,29 @@ "cell_type": "code", "execution_count": null, "id": "e85a8364-e0a1-4c10-8cae-c873f297e651", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ - "tof_edges = sc.linspace('tof', 1.0e4, 1.0e5, 101, unit='us')\n", + "tof_edges = sc.linspace('tof', 1.0e4, 1.0e5, 51, unit='us')\n", "data = dg.hist(tof=tof_edges)" ] }, { "cell_type": "markdown", "id": "d08ca911-b1a4-4f17-ba1e-355971531ffe", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "We now use the `instrument_view` function to show the 3D view of the instrument pixels,\n", "specifying that we wish to have a slider along the `tof` dimension:" @@ -111,16 +135,46 @@ "cell_type": "code", "execution_count": null, "id": "43f9ffbc-6bf5-4407-b3ad-5d1626efc43d", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "full_view = dream.instrument_view(data, dim='tof')\n", + "full_view" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7030d56-a375-47b5-898c-28fd06c2f361", + "metadata": { + "editable": true, + "nbsphinx": "hidden", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ - "dream.instrument_view(data, dim='tof')" + "full_view[2].controls['tof']['slider'].value = 35" ] }, { "cell_type": "markdown", "id": "c0b29ebf-21ff-4385-bf8b-0e4fa14dfaf9", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "It is also possible to view a single detector module using" ] @@ -129,10 +183,34 @@ "cell_type": "code", "execution_count": null, "id": "b417011e-0d12-4287-91d5-c1fb6ecc7cac", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "mantle_view = dream.instrument_view(data['mantle'], dim='tof')\n", + "mantle_view" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "299ec404-fb18-4533-ad96-e23bf8ba24d6", + "metadata": { + "editable": true, + "nbsphinx": "hidden", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ - "dream.instrument_view(data['mantle'], dim='tof')" + "mantle_view[1].controls['tof']['slider'].value = 35" ] } ], From 695b712a39ad0544e13893ddaf9644a1002e7c39 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 20 Nov 2023 16:00:08 +0100 Subject: [PATCH 26/34] fix load_geant4 to work with other file --- src/ess/dream/io/geant4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ess/dream/io/geant4.py b/src/ess/dream/io/geant4.py index 8205f079c..bcb05a537 100644 --- a/src/ess/dream/io/geant4.py +++ b/src/ess/dream/io/geant4.py @@ -59,7 +59,7 @@ def _group(detectors: Dict[str, sc.DataArray]) -> Dict[str, sc.DataArray]: elements = ('module', 'segment', 'counter', 'wire', 'strip') def group(key: str, da: sc.DataArray) -> sc.DataArray: - if key == 'high_resolution': + if (key == 'high_resolution') and ('sector' in da.coords): # Only the HR detector has sectors. return da.group('sector', *elements) res = da.group(*elements) From 88e876adee9e774008d1879759ef774d55e59695 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 21 Nov 2023 13:59:33 +0100 Subject: [PATCH 27/34] fix bug in geant4 loader with missing sumo dimension in end caps --- src/ess/dream/instrument_view.py | 9 ++------- src/ess/dream/io/geant4.py | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index a082ec7a7..417913910 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -16,19 +16,13 @@ def _to_data_group(data: Union[sc.DataArray, sc.DataGroup, dict]) -> sc.DataGrou return data -@pp.node -def slice_range(da, trunc_range): - min_tr, max_tr = trunc_range - return da['tof', min_tr:max_tr].sum('tof') - - @pp.node def pre_process(da, dim): dims = list(da.dims) if dim is not None: dims.remove(dim) out = da.flatten(dims=dims, to='pixel') - sel = sc.isfinite(out.coords['x']) + sel = sc.isfinite(out.coords['position']) return out[sel] @@ -59,6 +53,7 @@ def __init__(self, data, dim=None, pixel_size=None, **kwargs): self.scatter = pp.scatter3d( to_scatter, + pos='position', pixel_size=1.0 * sc.Unit('cm') if pixel_size is None else pixel_size, **kwargs, ) diff --git a/src/ess/dream/io/geant4.py b/src/ess/dream/io/geant4.py index bcb05a537..0f6b7599c 100644 --- a/src/ess/dream/io/geant4.py +++ b/src/ess/dream/io/geant4.py @@ -61,9 +61,14 @@ def _group(detectors: Dict[str, sc.DataArray]) -> Dict[str, sc.DataArray]: def group(key: str, da: sc.DataArray) -> sc.DataArray: if (key == 'high_resolution') and ('sector' in da.coords): # Only the HR detector has sectors. - return da.group('sector', *elements) - res = da.group(*elements) - res.bins.coords.pop('sector', None) + elems = ('sector', *elements) + elif 'sumo' in da.coords: + elems = ('sumo', *elements) + else: + elems = elements + res = da.group(*elems) + if 'sector' in elems: + res.bins.coords.pop('sector', None) return res return {key: group(key, da) for key, da in detectors.items()} @@ -94,6 +99,10 @@ def _split_detectors( if (det := _extract_detector(groups, detector_id_name, i)) is not None ] if endcaps_list: + endcaps_list = [ + endcap.assign_coords(sumo=sc.full(sizes=endcap.sizes, value=i, unit=None)) + for i, endcap in enumerate(endcaps_list, 3) + ] endcaps = sc.concat(endcaps_list, data.dim) endcaps = endcaps.bin( z_pos=sc.array( From de11d393112ad70a7a5fa7f1dcdf05b54350cbee Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 21 Nov 2023 14:00:33 +0100 Subject: [PATCH 28/34] use geant4 loader in notebook --- .../dream/dream-instrument-view.ipynb | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/docs/instruments/dream/dream-instrument-view.ipynb b/docs/instruments/dream/dream-instrument-view.ipynb index 4542dba4a..6c39b8e93 100644 --- a/docs/instruments/dream/dream-instrument-view.ipynb +++ b/docs/instruments/dream/dream-instrument-view.ipynb @@ -30,55 +30,27 @@ "id": "228a23dc-17f2-4273-a4c9-3e3276db8c54", "metadata": {}, "source": [ - "## Load and reshape the data\n", + "## Load the data\n", "\n", "We load a data set from a Geant4 simulation (stored as a `.csv` file).\n", - "After loading, we manipulate and reshape the data to obtain a data group that contains one entry per detector bank.\n", "In each detector bank, the data is organised by `wire`, `strip`, `module`, `segment`, and `counter`.\n", - "\n", - "It is assumed that Nexus files from the DREAM beamline will be loaded as such by Scippnexus,\n", - "and that the manipulations required below would not be needed for production datasets." + "The end caps also have an additional `sumo` dimension." ] }, { "cell_type": "code", "execution_count": null, - "id": "ebef41be-fe1c-46d8-80d6-da43b4ff9a73", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, + "id": "1ef8f35b-815f-436a-80e5-cbe4b3172b58", + "metadata": {}, "outputs": [], "source": [ - "df = pd.read_table(dream.data.get_path('data_dream_HF_mil_closed_alldets_1e9.csv.zip'))\n", - "ds = sc.compat.from_pandas(df)\n", - "# Some logic to parse units in the file header\n", - "for key in list(ds):\n", - " name, *remainder = key.split(' ')\n", - " ds[name] = ds.pop(key)\n", - " ds[name].unit = remainder[0][1:-1] if remainder else None\n", - "ds['lambda'].unit = 'angstrom'\n", - "table = sc.DataArray(sc.ones(sizes=ds.sizes, unit='counts'))\n", - "for name in ds:\n", - " table.coords[name] = ds[name].data\n", - "table.coords['tof'] = table.coords['tof'].to(dtype=float, unit='us')\n", - "# Group pixels according to their bank identifier\n", - "mapping = {'sumo0': 0, 'sumo1': 1, 'sumo2': 2, 'sumo3': 3, 'mantle': 4, 'high_res': 5}\n", - "da = table[::5].copy().group('det') # Limit number of points rendered for docs size\n", - "dg = sc.DataGroup({key: da['det', val] for key, val in mapping.items()})\n", - "dg = sc.DataGroup(\n", - " {\n", - " key: da.group('module', 'segment', 'counter', 'wire', 'strip')\n", - " for key, da in dg.items()\n", - " }\n", - ")\n", + "dg = dream.io.load_geant4_csv(dream.data.get_path('data_dream_HF_mil_closed_alldets_1e9.csv.zip'))\n", + "dg = dg['instrument'] # Extract the instrument data\n", + "\n", + "# Construct the pixel positions from event positions\n", "for da in dg.values():\n", - " da.coords['x'] = da.bins.coords.pop('x_pos').bins.mean()\n", - " da.coords['y'] = da.bins.coords.pop('y_pos').bins.mean()\n", - " da.coords['z'] = da.bins.coords.pop('z_pos').bins.mean()\n", + " da.coords['position'] = da.bins.coords['position'].bins.mean()\n", + "\n", "dg" ] }, @@ -112,10 +84,28 @@ }, "outputs": [], "source": [ - "tof_edges = sc.linspace('tof', 1.0e4, 1.0e5, 51, unit='us')\n", + "tof_edges = sc.linspace('tof', 1.0e7, 1.0e8, 51, unit='ns', dtype=int)\n", "data = dg.hist(tof=tof_edges)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "d78c9f16-16e6-4eee-bcf5-2cd26b5fa404", + "metadata": { + "editable": true, + "nbsphinx": "hidden", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Only plot half of the pixels to reduce html docs size\n", + "data = data['counter', 0]" + ] + }, { "cell_type": "markdown", "id": "d08ca911-b1a4-4f17-ba1e-355971531ffe", From 9a8b2187fcacf50af20377906c2fff41fc13a433 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 21 Nov 2023 14:05:28 +0100 Subject: [PATCH 29/34] add sentence about using other dims for slider --- docs/instruments/dream/dream-instrument-view.ipynb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/instruments/dream/dream-instrument-view.ipynb b/docs/instruments/dream/dream-instrument-view.ipynb index 6c39b8e93..414dec81b 100644 --- a/docs/instruments/dream/dream-instrument-view.ipynb +++ b/docs/instruments/dream/dream-instrument-view.ipynb @@ -166,6 +166,8 @@ "tags": [] }, "source": [ + "Note that it is possible to use any dimension for the slider instead of `tof`, such as `wavelength` (if present in the data).\n", + "\n", "It is also possible to view a single detector module using" ] }, From 7797731b3651998b44fb57cbcf3a977a6ca08f4a Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 21 Nov 2023 14:21:58 +0100 Subject: [PATCH 30/34] typehints and docstrings --- src/ess/dream/instrument_view.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index 417913910..d6a0a550c 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -2,7 +2,7 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) from html import escape -from typing import Union +from typing import Union, Optional import plopp as pp import scipp as sc @@ -17,7 +17,7 @@ def _to_data_group(data: Union[sc.DataArray, sc.DataGroup, dict]) -> sc.DataGrou @pp.node -def pre_process(da, dim): +def pre_process(da: sc.DataArray, dim: str) -> sc.DataArray: dims = list(da.dims) if dim is not None: dims.remove(dim) @@ -27,7 +27,13 @@ def pre_process(da, dim): class InstrumentView: - def __init__(self, data, dim=None, pixel_size=None, **kwargs): + def __init__( + self, + data: Union[sc.DataArray, sc.DataGroup, dict], + dim: Optional[str] = None, + pixel_size: Optional[Union[float, sc.Variable]] = None, + **kwargs, + ): from plopp.widgets import SliceWidget, slice_dims self.data = _to_data_group(data) @@ -108,14 +114,25 @@ def _check_visibility(self, _): self.fig.artists[cut_nodes[key].id].points.visible = val -def instrument_view(data, dim=None, pixel_size=None, **kwargs): +def instrument_view( + data: Union[sc.DataArray, sc.DataGroup, dict], + dim: Optional[str] = None, + pixel_size: Optional[Union[float, sc.Variable]] = None, + **kwargs, +): """ Three-dimensional visualization of the DREAM instrument. + The instrument view is capable of slicing the input data with a slider widget along + a dimension (e.g. ``tof``) by using the ``dim`` argument. + It will also generate checkboxes to hide/show the different modules that make up + the DREAM detectors. Parameters ---------- data: - Data to visualize. + Data to visualize. The data can be a single detector module (``DataArray``), + or a group of detector modules (``dict`` or ``DataGroup``). + The data must contain a ``position`` coordinate. dim: Dimension to use for the slider. No slider will be shown if this is None. pixel_size: From cd8876ad043fe6be2d5e7d98784bd1d9dd641bcf Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 21 Nov 2023 14:43:29 +0100 Subject: [PATCH 31/34] fix unit tests --- src/ess/dream/io/geant4.py | 2 +- tests/dream/io/geant4_test.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ess/dream/io/geant4.py b/src/ess/dream/io/geant4.py index 0f6b7599c..548cb79e6 100644 --- a/src/ess/dream/io/geant4.py +++ b/src/ess/dream/io/geant4.py @@ -67,7 +67,7 @@ def group(key: str, da: sc.DataArray) -> sc.DataArray: else: elems = elements res = da.group(*elems) - if 'sector' in elems: + if 'sector' not in elems: res.bins.coords.pop('sector', None) return res diff --git a/tests/dream/io/geant4_test.py b/tests/dream/io/geant4_test.py index a9a944655..a640c184c 100644 --- a/tests/dream/io/geant4_test.py +++ b/tests/dream/io/geant4_test.py @@ -89,6 +89,7 @@ def test_load_geant4_csv_endcap_backward_has_expected_coords(file): assert_index_coord(endcap.coords['counter']) assert_index_coord(endcap.coords['wire'], values=set(range(1, 17))) assert_index_coord(endcap.coords['strip'], values=set(range(1, 17))) + assert_index_coord(endcap.coords['sumo'], values=set(range(3, 7))) assert 'sector' not in endcap.coords assert 'sector' not in endcap.bins.coords @@ -104,6 +105,7 @@ def test_load_geant4_csv_endcap_forward_has_expected_coords(file): assert_index_coord(endcap.coords['counter']) assert_index_coord(endcap.coords['wire'], values=set(range(1, 17))) assert_index_coord(endcap.coords['strip'], values=set(range(1, 17))) + assert_index_coord(endcap.coords['sumo'], values=set(range(3, 7))) assert 'sector' not in endcap.coords assert 'sector' not in endcap.bins.coords From 794b8e06f0abff5a9ac298ecada5b97d6c374a08 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 21 Nov 2023 14:45:22 +0100 Subject: [PATCH 32/34] update notebook --- .../dream/dream-instrument-view.ipynb | 63 +++++++++++++------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/docs/instruments/dream/dream-instrument-view.ipynb b/docs/instruments/dream/dream-instrument-view.ipynb index 414dec81b..1f275d6f5 100644 --- a/docs/instruments/dream/dream-instrument-view.ipynb +++ b/docs/instruments/dream/dream-instrument-view.ipynb @@ -10,7 +10,7 @@ "This notebook is a simple example on how to use the instrument view for the DREAM instrument.\n", "\n", "- The DREAM-specific instrument view is capable of slicing the data with a slider widget along a dimension (e.g. `tof`) by using the `dim` argument.\n", - "- There are also checkboxes to hide/show the different modules that make up the DREAM detectors." + "- There are also checkboxes to hide/show the different elements that make up the DREAM detectors." ] }, { @@ -55,26 +55,26 @@ ] }, { - "cell_type": "markdown", - "id": "49595486-ab4e-4662-86f7-d301aedcf974", + "cell_type": "code", + "execution_count": null, + "id": "e8325797-5651-43c1-b601-3db6d4348758", "metadata": { "editable": true, + "nbsphinx": "hidden", "slideshow": { "slide_type": "" }, "tags": [] }, + "outputs": [], "source": [ - "## Instrument view\n", - "\n", - "We first histogram the data along the time-of-flight (`tof`) dimension,\n", - "making sure the same bins are used for all modules:" + "# Only plot half of the pixels to reduce html docs size\n", + "dg = dg['counter', 0]" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "e85a8364-e0a1-4c10-8cae-c873f297e651", + "cell_type": "markdown", + "id": "49595486-ab4e-4662-86f7-d301aedcf974", "metadata": { "editable": true, "slideshow": { @@ -82,19 +82,19 @@ }, "tags": [] }, - "outputs": [], "source": [ - "tof_edges = sc.linspace('tof', 1.0e7, 1.0e8, 51, unit='ns', dtype=int)\n", - "data = dg.hist(tof=tof_edges)" + "## Full instrument view\n", + "\n", + "We first histogram the data along the time-of-flight (`tof`) dimension,\n", + "making sure the same bins are used for all elements:" ] }, { "cell_type": "code", "execution_count": null, - "id": "d78c9f16-16e6-4eee-bcf5-2cd26b5fa404", + "id": "e85a8364-e0a1-4c10-8cae-c873f297e651", "metadata": { "editable": true, - "nbsphinx": "hidden", "slideshow": { "slide_type": "" }, @@ -102,8 +102,8 @@ }, "outputs": [], "source": [ - "# Only plot half of the pixels to reduce html docs size\n", - "data = data['counter', 0]" + "tof_edges = sc.linspace('tof', 1.0e7, 1.0e8, 51, unit='ns', dtype=int)\n", + "data = dg.hist(tof=tof_edges)" ] }, { @@ -168,7 +168,9 @@ "source": [ "Note that it is possible to use any dimension for the slider instead of `tof`, such as `wavelength` (if present in the data).\n", "\n", - "It is also possible to view a single detector module using" + "## Displaying individual detector elements\n", + "\n", + "It is also possible to view a single detector element, selecting e.g. `mantle` from the original data:" ] }, { @@ -184,7 +186,7 @@ }, "outputs": [], "source": [ - "mantle_view = dream.instrument_view(data['mantle'], dim='tof')\n", + "mantle_view = dream.instrument_view(dg['mantle'].hist(wavelength=50), dim='wavelength')\n", "mantle_view" ] }, @@ -202,7 +204,28 @@ }, "outputs": [], "source": [ - "mantle_view[1].controls['tof']['slider'].value = 35" + "mantle_view[1].controls['wavelength']['slider'].value = 43" + ] + }, + { + "cell_type": "markdown", + "id": "1df1aa56-5251-4555-a4f4-283145747198", + "metadata": {}, + "source": [ + "The instrument view is designed to be flexible in terms of what it accepts as input.\n", + "This means that you can easily inspect, for example, a single module by using the usual slicing notation for data arrays.\n", + "\n", + "Below, we display the first module in the backward end-cap detector:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c86e491-3564-498d-9232-39485f5b95d7", + "metadata": {}, + "outputs": [], + "source": [ + "dream.instrument_view(dg['endcap_backward']['module', 0].hist(tof=1))" ] } ], From 57065fa6f78d4482a0ca9acd324b91d728774021 Mon Sep 17 00:00:00 2001 From: nvaytet Date: Tue, 21 Nov 2023 13:46:39 +0000 Subject: [PATCH 33/34] Apply automatic formatting --- src/ess/dream/instrument_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ess/dream/instrument_view.py b/src/ess/dream/instrument_view.py index d6a0a550c..e74b043ed 100644 --- a/src/ess/dream/instrument_view.py +++ b/src/ess/dream/instrument_view.py @@ -2,7 +2,7 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) from html import escape -from typing import Union, Optional +from typing import Optional, Union import plopp as pp import scipp as sc From e3c978467e1063d8399badc40fc32572bd0f25cb Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Tue, 21 Nov 2023 14:47:34 +0100 Subject: [PATCH 34/34] black notebook --- docs/instruments/dream/dream-instrument-view.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/instruments/dream/dream-instrument-view.ipynb b/docs/instruments/dream/dream-instrument-view.ipynb index 1f275d6f5..a4bbd5e8b 100644 --- a/docs/instruments/dream/dream-instrument-view.ipynb +++ b/docs/instruments/dream/dream-instrument-view.ipynb @@ -44,7 +44,9 @@ "metadata": {}, "outputs": [], "source": [ - "dg = dream.io.load_geant4_csv(dream.data.get_path('data_dream_HF_mil_closed_alldets_1e9.csv.zip'))\n", + "dg = dream.io.load_geant4_csv(\n", + " dream.data.get_path('data_dream_HF_mil_closed_alldets_1e9.csv.zip')\n", + ")\n", "dg = dg['instrument'] # Extract the instrument data\n", "\n", "# Construct the pixel positions from event positions\n",