Skip to content

Commit 8132ca2

Browse files
authored
DQM Bokeh time series (#227)
* New attempt to add timelines to Bokeh app * Follow Bokeh API recent changes for tab panels * Play with layouts. Timelines still not updated on run change, added runid in timelines titles to help to debug it * Play with update timelines (not yet working) * Include timelines in Bokeh DQM app, now working as expected !
1 parent f004eef commit 8132ca2

File tree

3 files changed

+185
-64
lines changed

3 files changed

+185
-64
lines changed

src/nectarchain/dqm/bokeh_app/app_hooks.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
import re
33

44
import numpy as np
5+
6+
# bokeh imports
7+
from bokeh.layouts import gridplot
8+
from bokeh.models import TabPanel
9+
from bokeh.plotting import figure
10+
11+
# ctapipe imports
512
from ctapipe.coordinates import EngineeringCameraFrame
613
from ctapipe.instrument import CameraGeometry
714

@@ -27,18 +34,87 @@ def get_rundata(src, runid):
2734
return run_data
2835

2936

30-
def make_camera_displays(db, source, runid):
37+
def make_timelines(source, runid=None):
38+
timelines = collections.defaultdict(dict)
39+
for parentkey in source.keys():
40+
if re.match("(?:.*PIXTIMELINE-.*)", parentkey):
41+
for childkey in source[parentkey].keys():
42+
print(f"Run id {runid} Preparing plot for {parentkey}, {childkey}")
43+
timelines[parentkey][childkey] = figure(title=childkey)
44+
evts = np.arange(len(source[parentkey][childkey]))
45+
timelines[parentkey][childkey].line(
46+
x=evts,
47+
y=source[parentkey][childkey],
48+
line_width=3,
49+
)
50+
return dict(timelines)
51+
52+
53+
def update_timelines(data, timelines, runid=None):
54+
# Reset each timeline
55+
for k in timelines.keys():
56+
for kk in timelines[k].keys():
57+
timelines[k][kk].line(x=0, y=0)
58+
59+
timelines = make_timelines(data, runid)
60+
61+
list_timelines = [
62+
timelines[parentkey][childkey]
63+
for parentkey in timelines.keys()
64+
for childkey in timelines[parentkey].keys()
65+
]
66+
67+
layout_timelines = gridplot(
68+
list_timelines,
69+
ncols=2,
70+
)
71+
72+
tab_timelines = TabPanel(child=layout_timelines, title="Timelines")
73+
74+
return tab_timelines
75+
76+
77+
def make_camera_displays(source, runid):
3178
displays = collections.defaultdict(dict)
32-
for parentkey in db[runid].keys():
79+
for parentkey in source.keys():
3380
if not re.match(TEST_PATTERN, parentkey):
34-
for childkey in db[runid][parentkey].keys():
81+
for childkey in source[parentkey].keys():
3582
print(f"Run id {runid} Preparing plot for {parentkey}, {childkey}")
3683
displays[parentkey][childkey] = make_camera_display(
3784
source, parent_key=parentkey, child_key=childkey
3885
)
3986
return dict(displays)
4087

4188

89+
def update_camera_displays(data, displays, runid=None):
90+
ncols = 3
91+
92+
# Reset each display
93+
for k in displays.keys():
94+
for kk in displays[k].keys():
95+
displays[k][kk].image = np.zeros(shape=constants.N_PIXELS)
96+
97+
displays = make_camera_displays(data, runid)
98+
99+
camera_displays = [
100+
displays[parentkey][childkey].figure
101+
for parentkey in displays.keys()
102+
for childkey in displays[parentkey].keys()
103+
]
104+
105+
layout_camera_displays = gridplot(
106+
camera_displays,
107+
sizing_mode="scale_width",
108+
ncols=ncols,
109+
)
110+
111+
tab_camera_displays = TabPanel(
112+
child=layout_camera_displays, title="Camera displays"
113+
)
114+
115+
return tab_camera_displays
116+
117+
42118
def make_camera_display(source, parent_key, child_key):
43119
# Example camera display
44120
image = source[parent_key][child_key]
Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,40 @@
1-
import re
2-
3-
import numpy as np
4-
from app_hooks import TEST_PATTERN, get_rundata, make_camera_displays
1+
from app_hooks import (
2+
get_rundata,
3+
make_camera_displays,
4+
make_timelines,
5+
update_camera_displays,
6+
update_timelines,
7+
)
58

69
# bokeh imports
7-
from bokeh.layouts import layout, row
8-
from bokeh.models import Select # , NumericInput
10+
from bokeh.layouts import column, gridplot, row
11+
from bokeh.models import Select, TabPanel, Tabs
912
from bokeh.plotting import curdoc
1013

1114
# ctapipe imports
1215
from ctapipe.coordinates import EngineeringCameraFrame
1316
from ctapipe.instrument import CameraGeometry
14-
from ctapipe_io_nectarcam import constants
1517

1618
from nectarchain.dqm.db_utils import DQMDB
1719

1820
geom = CameraGeometry.from_name("NectarCam-003")
1921
geom = geom.transform_to(EngineeringCameraFrame())
2022

2123

22-
def update_camera_displays(attr, old, new):
24+
def update(attr, old, new):
2325
runid = run_select.value
24-
new_rundata = get_rundata(db, runid)
25-
26-
# Reset each display
27-
for k in displays.keys():
28-
for kk in displays[k].keys():
29-
displays[k][kk].image = np.zeros(shape=constants.N_PIXELS)
30-
31-
for parentkey in db[runid].keys():
32-
if not re.match(TEST_PATTERN, parentkey):
33-
for childkey in db[runid][parentkey].keys():
34-
print(f"Run id {runid} Updating plot for {parentkey}, {childkey}")
35-
36-
image = new_rundata[parentkey][childkey]
37-
image = np.nan_to_num(image, nan=0.0)
38-
try:
39-
displays[parentkey][childkey].image = image
40-
except ValueError as e:
41-
print(
42-
f"Caught {type(e).__name__} for {childkey}, filling display"
43-
f"with zeros. Details: {e}"
44-
)
45-
image = np.zeros(shape=displays[parentkey][childkey].image.shape)
46-
displays[parentkey][childkey].image = image
47-
except KeyError as e:
48-
print(
49-
f"Caught {type(e).__name__} for {childkey}, filling display"
50-
f"with zeros. Details: {e}"
51-
)
52-
image = np.zeros(shape=constants.N_PIXELS)
53-
displays[parentkey][childkey].image = image
54-
# TODO: TRY TO USE `stream` INSTEAD, ON UPDATES:
55-
# display.datasource.stream(new_data)
56-
# displays[parentkey][childkey].datasource.stream(image)
26+
source = get_rundata(db, runid)
27+
28+
tab_camera_displays = update_camera_displays(source, displays, runid)
29+
tab_timelines = update_timelines(source, timelines, runid)
30+
31+
# Combine panels into tabs
32+
tabs = Tabs(
33+
tabs=[tab_camera_displays, tab_timelines],
34+
sizing_mode="scale_width",
35+
)
36+
37+
page_layout.children[1] = tabs
5738

5839

5940
print("Opening connection to ZODB")
@@ -71,13 +52,13 @@ def update_camera_displays(attr, old, new):
7152

7253
print("Defining Select")
7354
# runid_input = NumericInput(value=db.root.keys()[-1], title="NectarCAM run number")
55+
# run_select = Select(value=runid, title="NectarCAM run number", options=runids)
7456
run_select = Select(value=runid, title="NectarCAM run number", options=runids)
7557

7658
print(f"Getting data for run {run_select.value}")
7759
source = get_rundata(db, run_select.value)
78-
displays = make_camera_displays(db, source, runid)
79-
80-
run_select.on_change("value", update_camera_displays)
60+
displays = make_camera_displays(source, runid)
61+
timelines = make_timelines(source, runid)
8162

8263
controls = row(run_select)
8364

@@ -88,15 +69,40 @@ def update_camera_displays(attr, old, new):
8869
# update_camera_displays(attr, old, new)
8970

9071
ncols = 3
91-
plots = [
72+
camera_displays = [
9273
displays[parentkey][childkey].figure
9374
for parentkey in displays.keys()
9475
for childkey in displays[parentkey].keys()
9576
]
96-
curdoc().add_root(
97-
layout(
98-
[[controls], [[plots[x : x + ncols] for x in range(0, len(plots), ncols)]]],
99-
sizing_mode="scale_width",
100-
)
77+
list_timelines = [
78+
timelines[parentkey][childkey]
79+
for parentkey in timelines.keys()
80+
for childkey in timelines[parentkey].keys()
81+
]
82+
83+
layout_camera_displays = gridplot(
84+
camera_displays,
85+
ncols=ncols,
86+
)
87+
88+
layout_timelines = gridplot(
89+
list_timelines,
90+
ncols=2,
91+
)
92+
93+
# Create different tabs
94+
tab_camera_displays = TabPanel(child=layout_camera_displays, title="Camera displays")
95+
tab_timelines = TabPanel(child=layout_timelines, title="Timelines")
96+
97+
# Combine panels into tabs
98+
tabs = Tabs(
99+
tabs=[tab_camera_displays, tab_timelines],
101100
)
101+
102+
page_layout = column([controls, tabs], sizing_mode="scale_width")
103+
104+
run_select.on_change("value", update)
105+
106+
# Add to the Bokeh document
107+
curdoc().add_root(page_layout)
102108
curdoc().title = "NectarCAM Data Quality Monitoring web app"

src/nectarchain/dqm/bokeh_app/tests/test_app_hooks.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
# bokeh imports
44
from bokeh.io import output_file, save
5-
from bokeh.layouts import layout
6-
from bokeh.models import Select
5+
from bokeh.layouts import column, gridplot, row
6+
from bokeh.models import Select, TabPanel, Tabs
77
from bokeh.plotting import curdoc
88

99
# ctapipe imports
@@ -19,10 +19,12 @@
1919
"mykey1": {
2020
"mysubkey1": np.random.normal(size=geom.n_pixels),
2121
"mysubkey2": np.random.normal(size=geom.n_pixels),
22+
"FOOPIXTIMELINE-HIGH": np.random.normal(size=1000),
2223
},
2324
"mykey2": {
2425
"mysubkey1": np.random.normal(size=geom.n_pixels),
2526
"mysubkey2": np.random.normal(size=geom.n_pixels),
27+
"FOOPIXTIMELINE-HIGH": np.random.normal(size=1000),
2628
},
2729
}
2830
}
@@ -34,11 +36,22 @@ def test_make_camera_displays():
3436
from nectarchain.dqm.bokeh_app.app_hooks import make_camera_displays
3537

3638
for runid in list(test_dict.keys()):
37-
make_camera_displays(test_dict, test_dict[runid], runid)
39+
make_camera_displays(source=test_dict[runid], runid=runid)
40+
41+
42+
def test_make_timelines():
43+
from nectarchain.dqm.bokeh_app.app_hooks import make_timelines
44+
45+
for runid in list(test_dict.keys()):
46+
make_timelines(source=test_dict[runid], runid=runid)
3847

3948

4049
def test_bokeh(tmp_path):
41-
from nectarchain.dqm.bokeh_app.app_hooks import get_rundata, make_camera_displays
50+
from nectarchain.dqm.bokeh_app.app_hooks import (
51+
get_rundata,
52+
make_camera_displays,
53+
make_timelines,
54+
)
4255

4356
db = DB(None)
4457
conn = db.open()
@@ -53,20 +66,46 @@ def test_bokeh(tmp_path):
5366
run_select = Select(value=runid, title="NectarCAM run number", options=runids)
5467

5568
source = get_rundata(root, run_select.value)
56-
displays = make_camera_displays(root, source, runid)
69+
displays = make_camera_displays(source=source, runid=runid)
70+
timelines = make_timelines(source, runid)
5771

5872
ncols = 3
59-
plots = [
73+
camera_displays = [
6074
displays[parentkey][childkey].figure
6175
for parentkey in displays.keys()
6276
for childkey in displays[parentkey].keys()
6377
]
64-
curdoc().add_root(
65-
layout(
66-
[[[plots[x : x + ncols] for x in range(0, len(plots), ncols)]]],
67-
sizing_mode="scale_width",
68-
)
78+
list_timelines = [
79+
timelines[parentkey][childkey]
80+
for parentkey in timelines.keys()
81+
for childkey in timelines[parentkey].keys()
82+
]
83+
84+
layout_camera_displays = gridplot(
85+
camera_displays,
86+
ncols=ncols,
6987
)
88+
89+
layout_timelines = gridplot(
90+
list_timelines,
91+
ncols=2,
92+
)
93+
# Create different tabs
94+
tab_camera_displays = TabPanel(
95+
child=layout_camera_displays, title="Camera displays"
96+
)
97+
tab_timelines = TabPanel(child=layout_timelines, title="Timelines")
98+
99+
# Combine panels into tabs
100+
tabs = Tabs(
101+
tabs=[tab_camera_displays, tab_timelines],
102+
)
103+
104+
controls = row(run_select)
105+
106+
page_layout = column([controls, tabs], sizing_mode="scale_width")
107+
108+
curdoc().add_root(page_layout)
70109
curdoc().title = "NectarCAM Data Quality Monitoring web app"
71110

72111
output_path = tmp_path / "test.html"

0 commit comments

Comments
 (0)