Skip to content

Commit a7bce2e

Browse files
Add Div to display run times (#278)
* feat(src/nectarchain/dqm/bokeh_app): - app_hooks.py: implement get_run_times function to extract run start timestamp and first and last recorded event timestamps * feat(src/nectarchain/dqm/bokeh_app): - main.py: implement Div to display run times * feat(src/nectarchain/dqm/bokeh_app) - main.py: update now updates also the run times - app_hooks.py: small refactor to get datetime from timestamp * test(src/nectarchain/dqm/bokeh_app/tests): - test_app_hooks.py: implement test for the get_run_times function * fix(src/nectarchain/dqm/bokeh_app): - app_hooks.py: specify time zone in datetime.fromtimestamp in get_run_times to avoid issues related to local time zone in tests
1 parent 03ba41b commit a7bce2e

3 files changed

Lines changed: 121 additions & 2 deletions

File tree

src/nectarchain/dqm/bokeh_app/app_hooks.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
import os
44
import re
5+
from datetime import datetime, timezone
56

67
import numpy as np
78
from astropy.coordinates import SkyCoord
@@ -66,6 +67,47 @@ def get_rundata(src, runid):
6667
return run_data
6768

6869

70+
def get_run_times(source):
71+
"""Extract important time stamps for the provided run data
72+
73+
Parameters
74+
----------
75+
source : dict
76+
Dictionary returned by `get_rundata`
77+
78+
Returns
79+
-------
80+
run_start_time_dt : datetime.datetime
81+
Time of the start of the run in %Y-%m-%d %H:%M:%S format
82+
first_event_time_dt : datetime.datetime
83+
Time when the first event was recorded in %Y-%m-%d %H:%M:%S format
84+
last_event_time_dt : datetime.datetime
85+
Time when the last event was recorded in %Y-%m-%d %H:%M:%S format
86+
"""
87+
88+
run_start_time = int(source["START-TIMES"]["Run start time"].flatten()[0])
89+
run_start_time_dt = datetime.fromtimestamp(
90+
run_start_time, tz=timezone.utc
91+
).strftime("%Y-%m-%d %H:%M:%S")
92+
first_event_time = int(source["START-TIMES"]["First event"].flatten()[0])
93+
first_event_time_dt = datetime.fromtimestamp(
94+
first_event_time, tz=timezone.utc
95+
).strftime("%Y-%m-%d %H:%M:%S")
96+
last_event_time = int(source["START-TIMES"]["Last event"].flatten()[0])
97+
last_event_time_dt = datetime.fromtimestamp(
98+
last_event_time, tz=timezone.utc
99+
).strftime("%Y-%m-%d %H:%M:%S")
100+
101+
logger.info(
102+
f"Successfully extracted run times: "
103+
f"run start time {run_start_time_dt}, "
104+
f"first event time {first_event_time_dt}, "
105+
f"last event time {last_event_time_dt}"
106+
)
107+
108+
return run_start_time_dt, first_event_time_dt, last_event_time_dt
109+
110+
69111
def make_timelines(source, runid=None):
70112
"""Make timeline plots for pixel quantities evolving with time
71113

src/nectarchain/dqm/bokeh_app/main.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from app_hooks import (
2+
get_run_times,
23
get_rundata,
34
make_camera_displays,
45
make_timelines,
@@ -8,7 +9,7 @@
89

910
# bokeh imports
1011
from bokeh.layouts import column, gridplot, row
11-
from bokeh.models import Select, TabPanel, Tabs
12+
from bokeh.models import Div, Select, TabPanel, Tabs
1213
from bokeh.plotting import curdoc
1314

1415
# ctapipe imports
@@ -64,11 +65,30 @@ def update(attr, old, new):
6465

6566
tab_camera_displays = update_camera_displays(source, displays, runid)
6667
tab_timelines = update_timelines(source, timelines, runid)
68+
run_start_time_dt, first_event_time_dt, last_event_time_dt = get_run_times(source)
69+
70+
run_times_string = Div(
71+
text=f"""
72+
<div style="
73+
background-color: #f0f8ff;
74+
border-radius: 10px;
75+
padding: 10px;
76+
width: fit-content;
77+
font-size: 14px;
78+
">
79+
<p>Run start time: {run_start_time_dt}</p>
80+
<p>First event recorded at: {first_event_time_dt}</p>
81+
<p>Last event recorded at: {last_event_time_dt}</p>
82+
</div>
83+
"""
84+
)
6785

6886
# Combine panels into tabs
6987
tabs = Tabs(tabs=[tab_camera_displays, tab_timelines], sizing_mode="scale_width")
7088

7189
page_layout.children[1] = tabs
90+
page_layout.children[0].children[1] = run_times_string
91+
7292
logger.info("Updated layouts and TabPanel objects for tabs.")
7393

7494

@@ -97,7 +117,24 @@ def update(attr, old, new):
97117
displays = make_camera_displays(source, runid)
98118
timelines = make_timelines(source, runid)
99119

100-
controls = row(run_select)
120+
run_start_time_dt, first_event_time_dt, last_event_time_dt = get_run_times(source)
121+
run_times_string = Div(
122+
text=f"""
123+
<div style="
124+
background-color: #f0f8ff;
125+
border-radius: 10px;
126+
padding: 10px;
127+
width: fit-content;
128+
font-size: 14px;
129+
">
130+
<p>Run start time: {run_start_time_dt}</p>
131+
<p>First event recorded at: {first_event_time_dt}</p>
132+
<p>Last event recorded at: {last_event_time_dt}</p>
133+
</div>
134+
"""
135+
)
136+
137+
controls = row(run_select, run_times_string)
101138

102139
# # TEST:
103140
# attr = 'value'

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,46 @@ def test_make_camera_displays():
7676
make_camera_displays(source=test_dict[runid], runid=runid)
7777

7878

79+
def test_get_run_times():
80+
from datetime import datetime
81+
82+
from nectarchain.dqm.bokeh_app.app_hooks import get_run_times
83+
84+
# Create a test source dict with START-TIMES data
85+
# Using np.array to simulate the structure expected by the function
86+
run_start_time_ts = 1609459200 # 2021-01-01 00:00:00 UTC
87+
first_event_time_ts = 1609459260 # 2021-01-01 00:01:00 UTC
88+
last_event_time_ts = 1609462800 # 2021-01-01 01:00:00 UTC
89+
90+
source_with_times = {
91+
"START-TIMES": {
92+
"Run start time": np.array([run_start_time_ts]),
93+
"First event": np.array([first_event_time_ts]),
94+
"Last event": np.array([last_event_time_ts]),
95+
}
96+
}
97+
98+
run_start_time_dt, first_event_time_dt, last_event_time_dt = get_run_times(
99+
source_with_times
100+
)
101+
102+
# Verify the returned strings are in the correct format
103+
assert isinstance(run_start_time_dt, str)
104+
assert isinstance(first_event_time_dt, str)
105+
assert isinstance(last_event_time_dt, str)
106+
107+
# Verify the format is YYYY-MM-DD HH:MM:SS
108+
expected_format = "%Y-%m-%d %H:%M:%S"
109+
datetime.strptime(run_start_time_dt, expected_format)
110+
datetime.strptime(first_event_time_dt, expected_format)
111+
datetime.strptime(last_event_time_dt, expected_format)
112+
113+
# Verify the values match the input timestamps
114+
assert run_start_time_dt == "2021-01-01 00:00:00"
115+
assert first_event_time_dt == "2021-01-01 00:01:00"
116+
assert last_event_time_dt == "2021-01-01 01:00:00"
117+
118+
79119
def test_make_timelines():
80120
from nectarchain.dqm.bokeh_app.app_hooks import make_timelines
81121

0 commit comments

Comments
 (0)