Skip to content

Commit cf0ae6d

Browse files
committed
refactor: move figures to separate module
1 parent 37ce6c3 commit cf0ae6d

File tree

6 files changed

+325
-309
lines changed

6 files changed

+325
-309
lines changed

docs/api-reference/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,6 @@
5252
orso
5353
resolution
5454
types
55+
utils
56+
figures
5557
```

docs/user-guide/amor/amor-reduction.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@
272272
"metadata": {},
273273
"outputs": [],
274274
"source": [
275-
"from ess.amor.utils import wavelength_theta_figure\n",
275+
"from ess.amor.figures import wavelength_theta_figure\n",
276276
"\n",
277277
"wavelength_theta_figure(\n",
278278
" [res[ReflectivityData] for res in diagnostics.values()],\n",
@@ -302,7 +302,7 @@
302302
"metadata": {},
303303
"outputs": [],
304304
"source": [
305-
"from ess.amor.utils import q_theta_figure\n",
305+
"from ess.amor.figures import q_theta_figure\n",
306306
"\n",
307307
"q_theta_figure(\n",
308308
" [res[ReflectivityData] for res in diagnostics.values()],\n",
@@ -328,7 +328,7 @@
328328
"metadata": {},
329329
"outputs": [],
330330
"source": [
331-
"from ess.amor.utils import wavelength_z_figure\n",
331+
"from ess.amor.figures import wavelength_z_figure\n",
332332
"\n",
333333
"workflow[Filename[SampleRun]] = amor.data.amor_sample_run('608')\n",
334334
"workflow[SampleRotation[SampleRun]] = runs['608']\n",

src/ess/amor/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
SamplePosition,
1717
BeamDivergenceLimits,
1818
)
19-
from . import conversions, load, orso, resolution, utils
19+
from . import conversions, load, orso, resolution, utils, figures
2020
from .instrument_view import instrument_view
2121
from .types import (
2222
AngularResolution,
@@ -44,6 +44,7 @@
4444
*conversions.providers,
4545
*resolution.providers,
4646
*utils.providers,
47+
*figures.providers,
4748
*orso.providers,
4849
)
4950
"""

src/ess/amor/figures.py

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
from collections.abc import Sequence
2+
3+
import numpy as np
4+
import plopp as pp
5+
import scipp as sc
6+
7+
from ess.reflectometry.types import (
8+
QBins,
9+
ReflectivityData,
10+
ReflectivityOverQ,
11+
SampleRun,
12+
)
13+
14+
from .types import (
15+
QThetaFigure,
16+
ReflectivityDiagnosticsView,
17+
ThetaBins,
18+
WavelengthThetaFigure,
19+
WavelengthZIndexFigure,
20+
)
21+
from .utils import theta_grid
22+
23+
24+
def _reshape_array_to_expected_shape(da, dims, **bins):
25+
if da.bins:
26+
da = da.bins.concat(set(da.dims) - set(dims))
27+
elif set(da.dims) > set(dims):
28+
raise ValueError(
29+
f'Histogram must have exactly the dimensions'
30+
f' {set(dims)} but got {set(da.dims)}'
31+
)
32+
33+
if not set(da.dims).union(set(bins)) >= set(dims):
34+
raise ValueError(
35+
f'Could not find bins for dimensions:'
36+
f' {set(dims) - set(da.dims).union(set(bins))}'
37+
)
38+
39+
if da.bins or not set(da.dims) == set(dims):
40+
da = da.hist(**bins)
41+
42+
return da.transpose(dims)
43+
44+
45+
def _repeat_variable_argument(n, arg):
46+
return (
47+
(None,) * n
48+
if arg is None
49+
else (arg,) * n
50+
if isinstance(arg, sc.Variable)
51+
else arg
52+
)
53+
54+
55+
def _try_to_create_theta_grid_if_missing(bins, da):
56+
if (
57+
'theta' not in bins
58+
and 'theta' not in da.dims
59+
and 'sample_rotation' in da.coords
60+
and 'detector_rotation' in da.coords
61+
):
62+
bins['theta'] = theta_grid(
63+
nu=da.coords['detector_rotation'], mu=da.coords['sample_rotation']
64+
)
65+
66+
67+
def wavelength_theta_figure(
68+
da: sc.DataArray | Sequence[sc.DataArray],
69+
*,
70+
wavelength_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
71+
theta_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
72+
q_edges_to_display: Sequence[sc.Variable] = (),
73+
linewidth: float = 1.0,
74+
**kwargs,
75+
):
76+
'''
77+
Creates a figure displaying a histogram over :math:`\\theta` and :math:`\\lambda`.
78+
79+
The input can either be a single data array containing the data to display, or
80+
a sequence of data arrays.
81+
82+
The inputs must either have coordinates called "theta" and "wavelength",
83+
or they must be histograms with dimensions "theta" and "wavelength".
84+
85+
If :code:`wavelength_bins` or :code:`theta_bins` are provided, they are used
86+
to construct the histogram. If not provided, the function uses the
87+
bin edges that already exist on the data arrays.
88+
89+
If :code:`q_edges_to_display` is provided, lines will be drawn in the figure
90+
corresponding to :math:`Q` equal to the values in :code:`q_edges_to_display`.
91+
92+
Parameters
93+
----------
94+
da : array or sequence of arrays
95+
Data arrays to display.
96+
wavelength_bins : array-like, optional
97+
Bins used to histogram the data in wavelength.
98+
theta_bins : array-like, optional
99+
Bins used to histogram the data in theta.
100+
q_edges_to_display : sequence of float, optional
101+
Values of :math:`Q` to be displayed as straight lines in the figure.
102+
linewidth : float, optional
103+
Thickness of the displayed :math:`Q` lines.
104+
**kwargs : keyword arguments, optional
105+
Additional parameters passed to the histogram plot function,
106+
used to customize colors, etc.
107+
108+
Returns
109+
-------
110+
A Plopp figure displaying the histogram.
111+
'''
112+
113+
if isinstance(da, sc.DataArray):
114+
return wavelength_theta_figure(
115+
(da,),
116+
wavelength_bins=(wavelength_bins,),
117+
theta_bins=(theta_bins,),
118+
q_edges_to_display=q_edges_to_display,
119+
**kwargs,
120+
)
121+
122+
wavelength_bins, theta_bins = (
123+
_repeat_variable_argument(len(da), arg) for arg in (wavelength_bins, theta_bins)
124+
)
125+
126+
hs = []
127+
for d, wavelength_bin, theta_bin in zip(
128+
da, wavelength_bins, theta_bins, strict=True
129+
):
130+
bins = {}
131+
132+
if wavelength_bin is not None:
133+
bins['wavelength'] = wavelength_bin
134+
135+
if theta_bin is not None:
136+
bins['theta'] = theta_bin
137+
138+
_try_to_create_theta_grid_if_missing(bins, d)
139+
140+
hs.append(_reshape_array_to_expected_shape(d, ('theta', 'wavelength'), **bins))
141+
142+
kwargs.setdefault('cbar', True)
143+
kwargs.setdefault('norm', 'log')
144+
p = pp.imagefigure(*(pp.Node(h) for h in hs), **kwargs)
145+
for q in q_edges_to_display:
146+
thmax = max(h.coords["theta"].max() for h in hs)
147+
p.ax.plot(
148+
[0.0, 4 * np.pi * (sc.sin(thmax) / q).value],
149+
[0.0, thmax.value],
150+
linestyle="solid",
151+
linewidth=linewidth,
152+
color="black",
153+
marker=None,
154+
)
155+
return p
156+
157+
158+
def q_theta_figure(
159+
da: sc.DataArray | Sequence[sc.DataArray],
160+
*,
161+
q_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
162+
theta_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
163+
**kwargs,
164+
):
165+
'''
166+
Creates a figure displaying a histogram over :math:`\\theta` and :math:`Q`.
167+
168+
The input can either be a single data array containing the data to display, or
169+
a sequence of data arrays.
170+
171+
The inputs must either have coordinates called "theta" and "Q",
172+
or they must be histograms with dimensions "theta" and "Q".
173+
174+
If :code:`theta_bins` or :code:`q_bins` are provided, they are used
175+
to construct the histogram. If not provided, the function uses the
176+
bin edges that already exist on the data arrays.
177+
178+
Parameters
179+
----------
180+
da : array or sequence of arrays
181+
Data arrays to display.
182+
q_bins : array-like, optional
183+
Bins used to histogram the data in Q.
184+
theta_bins : array-like, optional
185+
Bins used to histogram the data in theta.
186+
187+
Returns
188+
-------
189+
A Plopp figure displaying the histogram.
190+
'''
191+
192+
if isinstance(da, sc.DataArray):
193+
return q_theta_figure(
194+
(da,), q_bins=(q_bins,), theta_bins=(theta_bins,), **kwargs
195+
)
196+
197+
q_bins, theta_bins = (
198+
_repeat_variable_argument(len(da), arg) for arg in (q_bins, theta_bins)
199+
)
200+
201+
hs = []
202+
for d, q_bin, theta_bin in zip(da, q_bins, theta_bins, strict=True):
203+
bins = {}
204+
205+
if q_bin is not None:
206+
bins['Q'] = q_bin
207+
208+
if theta_bin is not None:
209+
bins['theta'] = theta_bin
210+
211+
_try_to_create_theta_grid_if_missing(bins, d)
212+
213+
hs.append(_reshape_array_to_expected_shape(d, ('theta', 'Q'), **bins))
214+
215+
kwargs.setdefault('cbar', True)
216+
kwargs.setdefault('norm', 'log')
217+
kwargs.setdefault('grid', True)
218+
return pp.imagefigure(*(pp.Node(h) for h in hs), **kwargs)
219+
220+
221+
def wavelength_z_figure(
222+
da: sc.DataArray | Sequence[sc.DataArray],
223+
*,
224+
wavelength_bins: (sc.Variable | None) | Sequence[sc.Variable | None] = None,
225+
**kwargs,
226+
):
227+
'''
228+
Creates a figure displaying a histogram over the detector "Z"-direction,
229+
corresponding to the combination of the logical detector coordinates
230+
:code:`blade` and :code:`wire`.
231+
232+
The input can either be a single data array containing the data to display, or
233+
a sequence of data arrays.
234+
235+
The inputs must either have coordinates called "blade" and "wire" and "wavelength",
236+
or they must be histograms with dimensions "blade", "wire" and "wavelength".
237+
238+
If :code:`wavelength_bins` is provided, it is used
239+
to construct the histogram. If not provided, the function uses the
240+
bin edges that already exist on the data arrays.
241+
242+
Parameters
243+
----------
244+
da : array or sequence of arrays
245+
Data arrays to display.
246+
wavelength_bins : array-like, optional
247+
Bins used to histogram the data in wavelength.
248+
249+
Returns
250+
-------
251+
A Plopp figure displaying the histogram.
252+
'''
253+
254+
if isinstance(da, sc.DataArray):
255+
return wavelength_z_figure((da,), wavelength_bins=(wavelength_bins,), **kwargs)
256+
257+
wavelength_bins = _repeat_variable_argument(len(da), wavelength_bins)
258+
259+
hs = []
260+
for d, wavelength_bin in zip(da, wavelength_bins, strict=True):
261+
bins = {}
262+
if wavelength_bin is not None:
263+
bins['wavelength'] = wavelength_bin
264+
265+
d = _reshape_array_to_expected_shape(d, ("blade", "wire", "wavelength"), **bins)
266+
d = d.flatten(("blade", "wire"), to="z_index")
267+
hs.append(d)
268+
269+
kwargs.setdefault('cbar', True)
270+
kwargs.setdefault('norm', 'log')
271+
kwargs.setdefault('grid', True)
272+
return pp.imagefigure(*(pp.Node(h) for h in hs), **kwargs)
273+
274+
275+
def wavelength_theta_diagnostic_figure(
276+
da: ReflectivityData,
277+
thbins: ThetaBins[SampleRun],
278+
) -> WavelengthThetaFigure:
279+
return wavelength_theta_figure(da, theta_bins=thbins)
280+
281+
282+
def q_theta_diagnostic_figure(
283+
da: ReflectivityData,
284+
thbins: ThetaBins[SampleRun],
285+
qbins: QBins,
286+
) -> QThetaFigure:
287+
return q_theta_figure(da, q_bins=qbins, theta_bins=thbins)
288+
289+
290+
def wavelength_z_diagnostic_figure(
291+
da: ReflectivityData,
292+
) -> WavelengthZIndexFigure:
293+
return wavelength_z_figure(da)
294+
295+
296+
def diagnostic_view(
297+
lath: WavelengthThetaFigure,
298+
laz: WavelengthZIndexFigure,
299+
qth: QThetaFigure,
300+
ioq: ReflectivityOverQ,
301+
) -> ReflectivityDiagnosticsView:
302+
ioq = ioq.hist().plot(norm="log")
303+
return (ioq + laz) / (lath + qth)
304+
305+
306+
providers = (
307+
wavelength_z_diagnostic_figure,
308+
wavelength_theta_diagnostic_figure,
309+
q_theta_diagnostic_figure,
310+
diagnostic_view,
311+
)

0 commit comments

Comments
 (0)