Source code for ess.amor.figures
from collections.abc import Sequence
import numpy as np
import plopp as pp
import scipp as sc
from ess.reflectometry.types import (
QBins,
ReflectivityData,
ReflectivityOverQ,
SampleRun,
)
from .types import (
QThetaFigure,
ReflectivityDiagnosticsView,
ThetaBins,
WavelengthThetaFigure,
WavelengthZIndexFigure,
)
from .utils import theta_grid
def _reshape_array_to_expected_shape(da, dims, **bins):
if da.bins:
da = da.bins.concat(set(da.dims) - set(dims))
elif set(da.dims) > set(dims):
raise ValueError(
f'Histogram must have exactly the dimensions'
f' {set(dims)} but got {set(da.dims)}'
)
if not set(da.dims).union(set(bins)) >= set(dims):
raise ValueError(
f'Could not find bins for dimensions:'
f' {set(dims) - set(da.dims).union(set(bins))}'
)
if da.bins or not set(da.dims) == set(dims):
da = da.hist(**bins)
return da.transpose(dims)
def _repeat_variable_argument(n, arg):
return (
(None,) * n
if arg is None
else (arg,) * n
if isinstance(arg, sc.Variable)
else arg
)
def _try_to_create_theta_grid_if_missing(bins, da):
if (
'theta' not in bins
and 'theta' not in da.dims
and 'sample_rotation' in da.coords
and 'detector_rotation' in da.coords
):
bins['theta'] = theta_grid(
nu=da.coords['detector_rotation'], mu=da.coords['sample_rotation']
)
[docs]
def wavelength_theta_figure(
da: sc.DataArray | Sequence[sc.DataArray],
*,
wavelength_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
theta_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
q_edges_to_display: Sequence[sc.Variable] = (),
linewidth: float = 1.0,
**kwargs,
):
'''
Creates a figure displaying a histogram over :math:`\\theta` and :math:`\\lambda`.
The input can either be a single data array containing the data to display, or
a sequence of data arrays.
The inputs must either have coordinates called "theta" and "wavelength",
or they must be histograms with dimensions "theta" and "wavelength".
If :code:`wavelength_bins` or :code:`theta_bins` are provided, they are used
to construct the histogram. If not provided, the function uses the
bin edges that already exist on the data arrays.
If :code:`q_edges_to_display` is provided, lines will be drawn in the figure
corresponding to :math:`Q` equal to the values in :code:`q_edges_to_display`.
Parameters
----------
da : array or sequence of arrays
Data arrays to display.
wavelength_bins : array-like, optional
Bins used to histogram the data in wavelength.
theta_bins : array-like, optional
Bins used to histogram the data in theta.
q_edges_to_display : sequence of float, optional
Values of :math:`Q` to be displayed as straight lines in the figure.
linewidth : float, optional
Thickness of the displayed :math:`Q` lines.
**kwargs : keyword arguments, optional
Additional parameters passed to the histogram plot function,
used to customize colors, etc.
Returns
-------
A Plopp figure displaying the histogram.
'''
if isinstance(da, sc.DataArray):
return wavelength_theta_figure(
(da,),
wavelength_bins=(wavelength_bins,),
theta_bins=(theta_bins,),
q_edges_to_display=q_edges_to_display,
**kwargs,
)
wavelength_bins, theta_bins = (
_repeat_variable_argument(len(da), arg) for arg in (wavelength_bins, theta_bins)
)
hs = []
for d, wavelength_bin, theta_bin in zip(
da, wavelength_bins, theta_bins, strict=True
):
bins = {}
if wavelength_bin is not None:
bins['wavelength'] = wavelength_bin
if theta_bin is not None:
bins['theta'] = theta_bin
_try_to_create_theta_grid_if_missing(bins, d)
hs.append(_reshape_array_to_expected_shape(d, ('theta', 'wavelength'), **bins))
kwargs.setdefault('cbar', True)
kwargs.setdefault('norm', 'log')
p = pp.imagefigure(*(pp.Node(h) for h in hs), **kwargs)
for q in q_edges_to_display:
thmax = max(h.coords["theta"].max() for h in hs)
p.ax.plot(
[0.0, 4 * np.pi * (sc.sin(thmax) / q).value],
[0.0, thmax.value],
linestyle="solid",
linewidth=linewidth,
color="black",
marker=None,
)
return p
[docs]
def q_theta_figure(
da: sc.DataArray | Sequence[sc.DataArray],
*,
q_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
theta_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
**kwargs,
):
'''
Creates a figure displaying a histogram over :math:`\\theta` and :math:`Q`.
The input can either be a single data array containing the data to display, or
a sequence of data arrays.
The inputs must either have coordinates called "theta" and "Q",
or they must be histograms with dimensions "theta" and "Q".
If :code:`theta_bins` or :code:`q_bins` are provided, they are used
to construct the histogram. If not provided, the function uses the
bin edges that already exist on the data arrays.
Parameters
----------
da : array or sequence of arrays
Data arrays to display.
q_bins : array-like, optional
Bins used to histogram the data in Q.
theta_bins : array-like, optional
Bins used to histogram the data in theta.
Returns
-------
A Plopp figure displaying the histogram.
'''
if isinstance(da, sc.DataArray):
return q_theta_figure(
(da,), q_bins=(q_bins,), theta_bins=(theta_bins,), **kwargs
)
q_bins, theta_bins = (
_repeat_variable_argument(len(da), arg) for arg in (q_bins, theta_bins)
)
hs = []
for d, q_bin, theta_bin in zip(da, q_bins, theta_bins, strict=True):
bins = {}
if q_bin is not None:
bins['Q'] = q_bin
if theta_bin is not None:
bins['theta'] = theta_bin
_try_to_create_theta_grid_if_missing(bins, d)
hs.append(_reshape_array_to_expected_shape(d, ('theta', 'Q'), **bins))
kwargs.setdefault('cbar', True)
kwargs.setdefault('norm', 'log')
kwargs.setdefault('grid', True)
return pp.imagefigure(*(pp.Node(h) for h in hs), **kwargs)
[docs]
def wavelength_z_figure(
da: sc.DataArray | Sequence[sc.DataArray],
*,
wavelength_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
**kwargs,
):
'''
Creates a figure displaying a histogram over the detector "Z"-direction,
corresponding to the combination of the logical detector coordinates
:code:`blade` and :code:`wire`.
The input can either be a single data array containing the data to display, or
a sequence of data arrays.
The inputs must either have coordinates called "blade" and "wire" and "wavelength",
or they must be histograms with dimensions "blade", "wire" and "wavelength".
If :code:`wavelength_bins` is provided, it is used
to construct the histogram. If not provided, the function uses the
bin edges that already exist on the data arrays.
Parameters
----------
da : array or sequence of arrays
Data arrays to display.
wavelength_bins : array-like, optional
Bins used to histogram the data in wavelength.
Returns
-------
A Plopp figure displaying the histogram.
'''
if isinstance(da, sc.DataArray):
return wavelength_z_figure((da,), wavelength_bins=(wavelength_bins,), **kwargs)
wavelength_bins = _repeat_variable_argument(len(da), wavelength_bins)
hs = []
for d, wavelength_bin in zip(da, wavelength_bins, strict=True):
bins = {}
if wavelength_bin is not None:
bins['wavelength'] = wavelength_bin
d = _reshape_array_to_expected_shape(d, ("blade", "wire", "wavelength"), **bins)
d = d.flatten(("blade", "wire"), to="z_index")
hs.append(d)
kwargs.setdefault('cbar', True)
kwargs.setdefault('norm', 'log')
kwargs.setdefault('grid', True)
return pp.imagefigure(*(pp.Node(h) for h in hs), **kwargs)
[docs]
def wavelength_theta_diagnostic_figure(
da: ReflectivityData,
thbins: ThetaBins[SampleRun],
) -> WavelengthThetaFigure:
return wavelength_theta_figure(da, theta_bins=thbins)
[docs]
def q_theta_diagnostic_figure(
da: ReflectivityData,
thbins: ThetaBins[SampleRun],
qbins: QBins,
) -> QThetaFigure:
return q_theta_figure(da, q_bins=qbins, theta_bins=thbins)
[docs]
def wavelength_z_diagnostic_figure(
da: ReflectivityData,
) -> WavelengthZIndexFigure:
return wavelength_z_figure(da)
[docs]
def diagnostic_view(
lath: WavelengthThetaFigure,
laz: WavelengthZIndexFigure,
qth: QThetaFigure,
ioq: ReflectivityOverQ,
) -> ReflectivityDiagnosticsView:
ioq = ioq.hist().plot(norm="log")
return (ioq + laz) / (lath + qth)
providers = (
wavelength_z_diagnostic_figure,
wavelength_theta_diagnostic_figure,
q_theta_diagnostic_figure,
diagnostic_view,
)