1010import pandas as pd
1111import plopp as pp
1212import scipp as sc
13- from ipydatagrid import DataGrid , VegaExpr
13+ from ipydatagrid import DataGrid , TextRenderer , VegaExpr
1414from IPython .display import display
1515from ipytree import Node , Tree
16+ from traitlets import Bool
1617
1718from ess import amor
1819from ess .amor .types import ChopperPhase
20+ from ess .reflectometry .figures import wavelength_z_figure
1921from ess .reflectometry .types import (
22+ Filename ,
2023 QBins ,
2124 ReducedReference ,
25+ ReducibleData ,
2226 ReferenceRun ,
2327 ReflectivityOverQ ,
2428 SampleRun ,
3034from 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