Source code for ess.dream.instrument_view
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
from __future__ import annotations
from html import escape
from typing import TYPE_CHECKING, Any
import plopp as pp
import scipp as sc
if TYPE_CHECKING:
try:
from plopp.widgets import Box
except ModuleNotFoundError:
Box = object
[docs]
def instrument_view(
data: sc.DataArray | sc.DataGroup | dict,
dim: str | None = None,
pixel_size: float | sc.Variable | None = None,
**kwargs: Any,
) -> Box:
"""
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. 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:
Size of the pixels.
**kwargs:
Additional arguments are forwarded to the scatter3d figure
(see https://scipp.github.io/plopp/generated/plopp.scatter3d.html).
"""
from plopp.widgets import Box
view = InstrumentView(data, dim=dim, pixel_size=pixel_size, **kwargs)
return Box(view.children)
def _to_data_group(data: 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
@pp.node
def _pre_process(da: sc.DataArray, dim: str) -> sc.DataArray:
dims = list(da.dims)
if dim is not None:
dims.remove(dim)
out = da.flatten(dims=dims, to="pixel")
sel = sc.isfinite(out.coords["position"])
return out[sel]
class InstrumentView:
"""Instrument view for DREAM."""
def __init__(
self,
data: sc.DataArray | sc.DataGroup | dict,
dim: str | None = None,
pixel_size: float | sc.Variable | None = None,
**kwargs,
):
from plopp.widgets import SliceWidget, slice_dims
self.data = _to_data_group(data)
self.pre_process_nodes = {
key: _pre_process(da, dim) for key, da in self.data.items()
}
self.children = []
if dim is not None:
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_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.pre_process_nodes
to_scatter = self.pre_process_nodes
kwargs.setdefault('cbar', True)
self.fig = pp.scatter3d(
to_scatter,
pos="position",
pixel_size=1.0 * sc.Unit("cm") if pixel_size is None else pixel_size,
**kwargs,
)
self.children.insert(0, self.fig)
if len(self.data) > 1:
self._add_module_control()
def _add_module_control(self):
import ipywidgets as ipw
self.cutting_tool = self.fig.bottom_bar[0]
self._node_backup = list(self.cutting_tool._original_nodes)
self.artist_mapping = dict(
zip(self.data.keys(), self.fig.artists.keys(), strict=True)
)
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: "),
*self.checkboxes.values(),
]
)
for key, ch in self.checkboxes.items():
ch.key = key
ch.observe(self._check_visibility, names="value")
self.children.insert(0, self.modules_widget)
def _check_visibility(self, _):
active_nodes = [
node_id
for key, node_id in self.artist_mapping.items()
if self.checkboxes[key].value
]
for n in self._node_backup:
self.fig.artists[n.id].points.visible = n.id in active_nodes
self.cutting_tool._original_nodes = [
n for n in self._node_backup if n.id in active_nodes
]
self.cutting_tool.update_state()