Skip to content

Commit 7d130c8

Browse files
authored
Merge pull request #148 from scipp/select-reference
Gui improvements
2 parents 4d8b1c0 + 69a9eef commit 7d130c8

File tree

1 file changed

+200
-60
lines changed

1 file changed

+200
-60
lines changed

src/ess/reflectometry/gui.py

Lines changed: 200 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@
1010
import pandas as pd
1111
import plopp as pp
1212
import scipp as sc
13-
from ipydatagrid import DataGrid, VegaExpr
13+
from ipydatagrid import DataGrid, TextRenderer, VegaExpr
1414
from IPython.display import display
1515
from ipytree import Node, Tree
16+
from traitlets import Bool
1617

1718
from ess import amor
1819
from ess.amor.types import ChopperPhase
20+
from ess.reflectometry.figures import wavelength_z_figure
1921
from ess.reflectometry.types import (
22+
Filename,
2023
QBins,
2124
ReducedReference,
25+
ReducibleData,
2226
ReferenceRun,
2327
ReflectivityOverQ,
2428
SampleRun,
@@ -30,8 +34,95 @@
3034
from ess.reflectometry.workflow import with_filenames
3135

3236

33-
class NexusExplorer:
34-
def __init__(self, runs_table: DataGrid, run_to_filepath: Callable[[str], str]):
37+
class DetectorView(widgets.HBox):
38+
is_active_tab = Bool(False).tag(sync=True)
39+
40+
def __init__(
41+
self, runs_table: DataGrid, run_to_filepath: Callable[[str], str], **kwargs
42+
):
43+
super().__init__([], **kwargs)
44+
self.runs_table = runs_table
45+
self.run_to_filepath = run_to_filepath
46+
self.plot_log = widgets.VBox([])
47+
self.working_label = widgets.Label(
48+
"...working", layout=widgets.Layout(display='none')
49+
)
50+
self.children = (
51+
widgets.VBox(
52+
[
53+
widgets.Label("Runs Table"),
54+
self.runs_table,
55+
],
56+
layout={"width": "35%"},
57+
),
58+
widgets.VBox(
59+
[
60+
widgets.HBox(
61+
[
62+
widgets.Label("Wavelength z-index counts distribution"),
63+
self.working_label,
64+
]
65+
),
66+
self.plot_log,
67+
],
68+
layout={"width": "60%"},
69+
),
70+
)
71+
72+
def run_when_selected_row_changes(change):
73+
if not change['old'] or change['old'][0]['r1'] != change['new'][0]['r1']:
74+
self.run_workflow()
75+
76+
self.runs_table.observe(run_when_selected_row_changes, names='selections')
77+
78+
def run_when_active_tab(change):
79+
if change['new']:
80+
self.run_workflow()
81+
82+
self.observe(run_when_active_tab, 'is_active_tab')
83+
84+
def run_workflow(self):
85+
selections = self.runs_table.selections
86+
if not self.is_active_tab or not selections:
87+
return
88+
89+
self.working_label.layout.display = ''
90+
row_idx = selections[0]['r1']
91+
run = self.runs_table.data.iloc[row_idx]['Run']
92+
93+
workflow = amor.AmorWorkflow()
94+
workflow[SampleSize[SampleRun]] = sc.scalar(10, unit='mm')
95+
workflow[SampleSize[ReferenceRun]] = sc.scalar(10, unit='mm')
96+
97+
workflow[ChopperPhase[ReferenceRun]] = sc.scalar(7.5, unit='deg')
98+
workflow[ChopperPhase[SampleRun]] = sc.scalar(7.5, unit='deg')
99+
100+
workflow[YIndexLimits] = (0, 64)
101+
workflow[ZIndexLimits] = (0, 16 * 32)
102+
workflow[WavelengthBins] = sc.geomspace(
103+
'wavelength',
104+
2,
105+
13.5,
106+
2001,
107+
unit='angstrom',
108+
)
109+
workflow[Filename[SampleRun]] = self.run_to_filepath(run)
110+
da = workflow.compute(ReducibleData[SampleRun])
111+
da.bins.data[...] = sc.scalar(1.0, variance=1.0, unit=da.bins.unit)
112+
da.bins.unit = 'counts'
113+
da.masks.clear()
114+
da.bins.masks.clear()
115+
p = wavelength_z_figure(da, wavelength_bins=workflow.compute(WavelengthBins))
116+
self.plot_log.children = (p,)
117+
self.working_label.layout.display = 'none'
118+
119+
120+
class NexusExplorer(widgets.VBox):
121+
def __init__(
122+
self, runs_table: DataGrid, run_to_filepath: Callable[[str], str], **kwargs
123+
):
124+
kwargs.setdefault('layout', {"width": "100%"})
125+
super().__init__(**kwargs)
35126
self.runs_table = runs_table
36127
self.run_to_filepath = run_to_filepath
37128

@@ -58,47 +149,44 @@ def __init__(self, runs_table: DataGrid, run_to_filepath: Callable[[str], str]):
58149
self.nexus_tree.observe(self.on_tree_select, names='selected_nodes')
59150

60151
# Create the Nexus Explorer tab content
61-
self.widget = widgets.VBox(
62-
[
63-
widgets.Label("Nexus Explorer"),
64-
widgets.HBox(
65-
[
66-
widgets.VBox(
67-
[
68-
widgets.Label("Runs Table"),
69-
self.runs_table,
70-
],
71-
layout={"width": "30%"},
72-
),
73-
widgets.VBox(
74-
[
75-
widgets.Label("File Structure"),
76-
widgets.VBox(
77-
[self.nexus_tree],
78-
layout=widgets.Layout(
79-
width='100%',
80-
height='600px',
81-
min_height='100px', # Min resize height
82-
max_height='1000px', # Max resize height
83-
overflow_y='scroll',
84-
border='1px solid lightgray',
85-
resize='vertical', # Add resize handle
86-
),
152+
self.children = (
153+
widgets.Label("Nexus Explorer"),
154+
widgets.HBox(
155+
[
156+
widgets.VBox(
157+
[
158+
widgets.Label("Runs Table"),
159+
self.runs_table,
160+
],
161+
layout={"width": "30%"},
162+
),
163+
widgets.VBox(
164+
[
165+
widgets.Label("File Structure"),
166+
widgets.VBox(
167+
[self.nexus_tree],
168+
layout=widgets.Layout(
169+
width='100%',
170+
height='600px',
171+
min_height='100px', # Min resize height
172+
max_height='1000px', # Max resize height
173+
overflow_y='scroll',
174+
border='1px solid lightgray',
175+
resize='vertical', # Add resize handle
87176
),
88-
],
89-
layout={"width": "35%"},
90-
),
91-
widgets.VBox(
92-
[
93-
widgets.Label("Content"),
94-
self.nexus_content,
95-
],
96-
layout={"width": "35%"},
97-
),
98-
]
99-
),
100-
],
101-
layout={"width": "100%"},
177+
),
178+
],
179+
layout={"width": "35%"},
180+
),
181+
widgets.VBox(
182+
[
183+
widgets.Label("Content"),
184+
self.nexus_content,
185+
],
186+
layout={"width": "35%"},
187+
),
188+
]
189+
),
102190
)
103191

104192
def create_hdf5_tree(self, filepath):
@@ -241,6 +329,8 @@ def set_table_colors(self, table):
241329
if self.get_row_key(row) == row_key:
242330
expr += template.format(i=i, reduced_color="'lightgreen'")
243331
expr += "default_value"
332+
for renderer in table.renderers.values():
333+
renderer.background_color = VegaExpr(expr)
244334
table.default_renderer.background_color = VegaExpr(expr)
245335

246336
@staticmethod
@@ -254,6 +344,18 @@ def set_result(self, metadata, result):
254344
self.set_table_colors(self.custom_reduction_table)
255345
self.set_table_colors(self.reference_table)
256346

347+
def get_renderers_for_reduction_table(self):
348+
return {}
349+
350+
def get_renderers_for_reference_table(self):
351+
return {}
352+
353+
def get_renderers_for_custom_reduction_table(self):
354+
return {}
355+
356+
def get_renderers_for_runs_table(self):
357+
return {}
358+
257359
def log(self, message):
258360
out = widgets.Output()
259361
with out:
@@ -306,27 +408,31 @@ def __init__(self):
306408
auto_fit_columns=True,
307409
column_visibility={"key": False},
308410
selection_mode="cell",
411+
renderers=self.get_renderers_for_runs_table(),
309412
)
310413
self.reduction_table = DataGrid(
311414
pd.DataFrame([]),
312415
editable=True,
313416
auto_fit_columns=True,
314417
column_visibility={"key": False},
315418
selection_mode="cell",
419+
renderers=self.get_renderers_for_reduction_table(),
316420
)
317421
self.reference_table = DataGrid(
318422
pd.DataFrame([]),
319423
editable=True,
320424
auto_fit_columns=True,
321425
column_visibility={"key": False},
322426
selection_mode="cell",
427+
renderers=self.get_renderers_for_reference_table(),
323428
)
324429
self.custom_reduction_table = DataGrid(
325430
pd.DataFrame([]),
326431
editable=True,
327432
auto_fit_columns=True,
328433
column_visibility={"key": False},
329434
selection_mode="cell",
435+
renderers=self.get_renderers_for_custom_reduction_table(),
330436
)
331437

332438
self.runs_table.on_cell_change(self.sync)
@@ -515,9 +621,17 @@ def delete_row(_):
515621
tab_settings,
516622
tab_log,
517623
]
518-
self.tabs.set_title(0, "Reduce")
519-
self.tabs.set_title(1, "Settings")
520-
self.tabs.set_title(2, "Log")
624+
self.tabs.titles = ["Reduce", "Settings", "Log"]
625+
626+
def on_tab_change(change):
627+
old = self.tabs.children[change['old']]
628+
new = self.tabs.children[change['new']]
629+
if hasattr(old, 'is_active_tab'):
630+
old.is_active_tab = False
631+
if hasattr(new, 'is_active_tab'):
632+
new.is_active_tab = True
633+
634+
self.tabs.observe(on_tab_change, names='selected_index')
521635

522636
self.main = widgets.VBox(
523637
[
@@ -555,8 +669,16 @@ class AmorBatchReductionGUI(ReflectometryBatchReductionGUI):
555669
def __init__(self):
556670
super().__init__()
557671
self.nexus_explorer = NexusExplorer(self.runs_table, self.get_filepath_from_run)
558-
self.tabs.children = (*self.tabs.children, self.nexus_explorer.widget)
559-
self.tabs.set_title(len(self.tabs.children) - 1, "Nexus Explorer")
672+
self.detector_display = DetectorView(
673+
self.runs_table, self.get_filepath_from_run
674+
)
675+
self.tabs.children = (
676+
*self.tabs.children,
677+
self.nexus_explorer,
678+
self.detector_display,
679+
)
680+
# Empty titles are automatically added for the new children
681+
self.tabs.titles = [*self.tabs.titles[:-2], "Nexus Explorer", "Detector View"]
560682

561683
def read_meta_data(self, path):
562684
with h5py.File(path) as f:
@@ -566,6 +688,21 @@ def read_meta_data(self, path):
566688
"Angle": f['entry1']['Amor']['master_parameters']['mu']['value'][0, 0],
567689
}
568690

691+
def get_renderers_for_reduction_table(self):
692+
return {
693+
'Angle': TextRenderer(text_value=VegaExpr("format(cell.value, ',.3f')"))
694+
}
695+
696+
def get_renderers_for_custom_reduction_table(self):
697+
return {
698+
'Angle': TextRenderer(text_value=VegaExpr("format(cell.value, ',.3f')"))
699+
}
700+
701+
def get_renderers_for_runs_table(self):
702+
return {
703+
'Angle': TextRenderer(text_value=VegaExpr("format(cell.value, ',.3f')"))
704+
}
705+
569706
@staticmethod
570707
def _merge_old_and_new_state(new, old, on, how='left'):
571708
old = old if on in old else old.assign(**{on: None})
@@ -623,22 +760,24 @@ def sync_reference_table(self, db):
623760
df = db["user_runs"]
624761
df = (
625762
df[df["Sample"] == "sm5"][~df["Exclude"]]
626-
.groupby(["Sample"], as_index=False)
763+
.groupby(["Sample", "Angle"], as_index=False)
627764
.agg(Runs=("Run", tuple))
628-
.sort_values(by="Sample")
765+
.sort_values(["Sample", "Angle"])
629766
)
630767
# We don't want changes to Sample
631768
# in the user_reference table to persist
632-
user_reference = db['user_reference'].drop(columns=["Sample"], errors='ignore')
769+
user_reference = db['user_reference'].drop(
770+
columns=["Sample", "Angle"], errors='ignore'
771+
)
633772
df = self._merge_old_and_new_state(df, user_reference, on='Runs')
634773
self._setdefault(df, "Ymin", 17)
635774
self._setdefault(df, "Ymax", 47)
636775
self._setdefault(df, "Zmin", 60)
637776
self._setdefault(df, "Zmax", 380)
638777
self._setdefault(df, "Lmin", 3.0)
639778
self._setdefault(df, "Lmax", 12.5)
640-
df = self._ordercolumns(df, 'Sample', 'Runs')
641-
return df.sort_values(by="Sample")
779+
df = self._ordercolumns(df, 'Sample', 'Angle', 'Runs')
780+
return df.sort_values(["Sample", "Angle"])
642781

643782
def sync_custom_reduction_table(self):
644783
df = self.custom_reduction_table.data.copy()
@@ -656,17 +795,17 @@ def display_results(self):
656795
if len(df) == 0:
657796
self.log('There was nothing to display')
658797
return
659-
try:
798+
for _ in range(2):
660799
results = [
661-
next(v for (m, _), v in self.results.items() if m == key)
662-
for key in (tuple(row) for _, row in df.iterrows())
800+
self.results[key]
801+
for _, row in df.iterrows()
802+
if (key := self.get_row_key(row)) in self.results
663803
]
664-
except StopIteration:
665-
# No results were found for the selected row
804+
if len(results) == len(df):
805+
break
806+
# No results were found for some of the selected rows.
666807
# It hasn't been computed yet, so compute it and try again.
667808
self.run_workflow()
668-
self.display_results()
669-
return
670809

671810
def get_unique_names(df):
672811
# Create labels with Sample name and runs
@@ -694,6 +833,7 @@ def get_unique_names(df):
694833
results,
695834
norm='log',
696835
figsize=(12, 6),
836+
vmin=1e-6,
697837
)
698838
]
699839
)

0 commit comments

Comments
 (0)