Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 210 additions & 21 deletions src/nectarchain/dqm/bokeh_app/app_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from astropy.coordinates import SkyCoord

# bokeh imports
from bokeh.layouts import gridplot
from bokeh.models import ColorBar, TabPanel
from bokeh.layouts import column, row
from bokeh.models import ColorBar, Label, Node, TabPanel
from bokeh.plotting import figure

# ctapipe imports
Expand Down Expand Up @@ -256,11 +256,6 @@ def update_timelines(data, timelines, runid=None):
Updated TabPanel containing the bokeh layout for the timeline plots
"""

# Reset timeline line plots
for k in timelines.keys():
for kk in timelines[k].keys():
timelines[k][kk].line(x=0, y=0)

# Make new timeline plots
timelines = make_timelines(data, runid)

Expand All @@ -270,19 +265,19 @@ def update_timelines(data, timelines, runid=None):
for childkey in timelines[parentkey].keys()
]

layout_timelines = gridplot(
layout_timelines = column(
list_timelines,
ncols=2,
sizing_mode="scale_width",
)

# Recreate TabPanel layout
tab_timelines = TabPanel(child=layout_timelines, title="Timelines")

return tab_timelines


def make_camera_displays(source, runid):
"""Make camera display plots using `make_camera_display`
"""Make camera display plots using `make_camera_display`,
`make_pixel_val_vs_id` and `make_pixel_vals_histo`

Parameters
----------
Expand All @@ -305,9 +300,22 @@ def make_camera_displays(source, runid):
logger.info(
f"Run id {runid}, preparing plot for {parentkey}, {childkey}"
)
displays[parentkey][childkey] = make_camera_display(
camera_display = make_camera_display(
source, parent_key=parentkey, child_key=childkey
)
displays_to_show = [camera_display]

if "BADPIX" not in parentkey:
camera_pixel_val_vs_id = make_pixel_val_vs_id(
source, parent_key=parentkey, child_key=childkey
)
displays_to_show.append(camera_pixel_val_vs_id)
camera_pixel_vals_histo = make_pixel_vals_histo(
source, parent_key=parentkey, child_key=childkey
)
displays_to_show.append(camera_pixel_vals_histo)

displays[parentkey][childkey] = displays_to_show

logger.info(f"Successfully created camera display plots for run {runid}")

Expand Down Expand Up @@ -335,24 +343,24 @@ def update_camera_displays(data, displays, runid=None):
Updated TabPanel containing the bokeh layout for the display plots
"""

ncols = 3

for k in displays.keys():
for kk in displays[k].keys():
displays[k][kk].image = np.zeros(shape=constants.N_PIXELS)

# Make new camera display plots
displays = make_camera_displays(data, runid)

camera_displays = [
displays[parentkey][childkey].figure
row(
displays[parentkey][childkey][0].figure,
displays[parentkey][childkey][1],
displays[parentkey][childkey][2],
)
if len(displays[parentkey][childkey]) == 3
else displays[parentkey][childkey][0].figure
for parentkey in displays.keys()
for childkey in displays[parentkey].keys()
]

layout_camera_displays = gridplot(
layout_camera_displays = column(
camera_displays,
sizing_mode="scale_width",
ncols=ncols,
)

tab_camera_displays = TabPanel(
Expand All @@ -362,11 +370,188 @@ def update_camera_displays(data, displays, runid=None):
return tab_camera_displays


def make_pixel_vals_histo(source, parent_key, child_key):
"""Make histograms of pixel values
to fill the nested dict
created by `make_camera_displays`
along with the camera displays
and the 1D plot of camera pixel values vs pixel id

Parameters
----------
source : dict
Dictionary returned by `get_rundata`
parent_key : str
Parent key to extract quantity from the dict
child_key : str
Child key to extract quantity from the dict

Returns
-------
bokeh.plotting.figure
figure containing the histogram of pixel values
"""

image = np.nan_to_num(source[parent_key][child_key], nan=0.0)

if "BADPIX" in parent_key:
image = set_bad_pixels_cap_value(image)
data_for_hist = image
else:
mask_high_gain, mask_low_gain = get_bad_pixels_position(
source=source, image_shape=image.shape
)
data_for_hist = image[
~mask_low_gain if "LOW-GAIN" in parent_key else ~mask_high_gain
]

# Use adaptive binning on full data to include outliers
hist, bins = np.histogram(data_for_hist, bins="fd")
min_val, max_val = 0.0, np.max(hist) + 10

with open(labels_path, "r", encoding="utf-8") as file:
colorbar_labels = json.load(file)["colorbar_labels_camera_display"]

try:
x_ax_label = colorbar_labels[parent_key]
except ValueError:
x_ax_label = ""
except KeyError:
x_ax_label = ""

histo_values = figure(
x_range=(np.min(bins) * 0.99, np.max(bins) * 1.01), y_range=(min_val, max_val)
)

# Calculate bar width based on actual bin widths to avoid overlapping or gaps
bin_widths = bins[1:] - bins[:-1]
bar_width = np.mean(bin_widths) * 0.95 # Use 95% of average bin width

histo_values.vbar(
x=((bins[1:] - bins[:-1]) / 2.0 + bins[:-1]),
top=hist,
width=bar_width,
color="green",
alpha=0.6,
)

frame_left = Node(target="frame", symbol="left", offset=5)
frame_top = Node(target="frame", symbol="top", offset=5)

stats = Label(
x=frame_left,
y=frame_top,
anchor="top_left",
text=f"Mean = {np.mean(data_for_hist):.2f} \n"
+ f"Median = {np.median(data_for_hist):.2f} \n"
+ f"Std = {np.std(data_for_hist):.2f}",
padding=10,
border_radius=5,
border_line_color="green",
border_line_width=2,
background_fill_color="white",
)

histo_values.add_layout(stats)

histo_values.xaxis.axis_label = x_ax_label
histo_values.yaxis.axis_label = "Pixel count"

histo_values.xaxis.axis_label_text_font_size = "12pt"
histo_values.yaxis.axis_label_text_font_size = "12pt"
histo_values.xaxis.major_label_text_font_size = "10pt"
histo_values.yaxis.major_label_text_font_size = "10pt"
histo_values.xaxis.axis_label_text_font_style = "normal"
histo_values.yaxis.axis_label_text_font_style = "normal"

return histo_values


def make_pixel_val_vs_id(source, parent_key, child_key):
"""Make 1D plot of camera pixel values vs pixel id
to fill the nested dict
created by `make_camera_displays`
along with the camera displays and the histograms of pixel values

Parameters
----------
source : dict
Dictionary returned by `get_rundata`
parent_key : str
Parent key to extract quantity from the dict
child_key : str
Child key to extract quantity from the dict

Returns
-------
bokeh.plotting.figure
figure containing the 1D plot of camera pixel values vs pixel id
"""

image = np.nan_to_num(source[parent_key][child_key], nan=0.0)
if "BADPIX" in parent_key:
image = set_bad_pixels_cap_value(image)
min_val, max_val = 0.0, 1.0
else:
mask_high_gain, mask_low_gain = get_bad_pixels_position(
source=source, image_shape=image.shape
)
min_val = (
np.min(
image[~mask_low_gain if "LOW-GAIN" in parent_key else ~mask_high_gain]
)
* 0.99
)
max_val = (
np.max(
image[~mask_low_gain if "LOW-GAIN" in parent_key else ~mask_high_gain]
)
* 1.01
)

with open(labels_path, "r", encoding="utf-8") as file:
colorbar_labels = json.load(file)["colorbar_labels_camera_display"]

try:
y_ax_label = colorbar_labels[parent_key]
except ValueError:
y_ax_label = ""
except KeyError:
y_ax_label = ""

scatter_value_vs_id = figure(
background_fill_color="#ffffff",
y_range=(min_val, max_val),
)

scatter_value_vs_id.scatter(
x=np.arange(len(image)),
y=image,
color="blue",
size=5,
alpha=0.6,
)

scatter_value_vs_id.xaxis.axis_label = "Pixel id"
scatter_value_vs_id.yaxis.axis_label = y_ax_label

scatter_value_vs_id.xaxis.axis_label_text_font_size = "12pt"
scatter_value_vs_id.yaxis.axis_label_text_font_size = "12pt"
scatter_value_vs_id.xaxis.major_label_text_font_size = "10pt"
scatter_value_vs_id.yaxis.major_label_text_font_size = "10pt"
scatter_value_vs_id.xaxis.axis_label_text_font_style = "normal"
scatter_value_vs_id.yaxis.axis_label_text_font_style = "normal"

return scatter_value_vs_id


# TODO: some more explanation about the parent and child keys
# may help the user, if needed
def make_camera_display(source, parent_key, child_key):
"""Make camera display plot to fill the nested dict
created by `make_camera_displays`
along with the 1D plot of camera pixel values vs pixel id
and the histograms of pixel values

Parameters
----------
Expand All @@ -384,6 +569,10 @@ def make_camera_display(source, parent_key, child_key):
and displayed with the geometry from ctapipe.instrument.CameraGeometry
"""

# TODO: may want to check here to implement
# the "on_pixel_clicked" function for pixels
# ctapipe.readthedocs.io/en/stable/api/ctapipe.visualization.CameraDisplay.html

image = np.nan_to_num(source[parent_key][child_key], nan=0.0)

if "BADPIX" in parent_key:
Expand Down
20 changes: 12 additions & 8 deletions src/nectarchain/dqm/bokeh_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
)

# bokeh imports
from bokeh.layouts import column, gridplot, row
from bokeh.layouts import column, row
from bokeh.models import Div, Select, TabPanel, Tabs
from bokeh.plotting import curdoc

Expand Down Expand Up @@ -146,10 +146,14 @@ def update(attr, old, new):
# old = runid
# new = runids[1]
# update_camera_displays(attr, old, new)

ncols = 3
camera_displays = [
displays[parentkey][childkey].figure
row(
displays[parentkey][childkey][0].figure,
displays[parentkey][childkey][1],
displays[parentkey][childkey][2],
)
if len(displays[parentkey][childkey]) == 3
else displays[parentkey][childkey][0].figure
for parentkey in displays.keys()
for childkey in displays[parentkey].keys()
]
Expand All @@ -160,14 +164,14 @@ def update(attr, old, new):
for childkey in timelines[parentkey].keys()
]

layout_camera_displays = gridplot(
layout_camera_displays = column(
camera_displays,
ncols=ncols,
sizing_mode="scale_width",
)

layout_timelines = gridplot(
layout_timelines = column(
list_timelines,
ncols=2,
sizing_mode="scale_width",
)

# Create different tabs
Expand Down
20 changes: 13 additions & 7 deletions src/nectarchain/dqm/bokeh_app/tests/test_app_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# bokeh imports
from bokeh.io import output_file, save
from bokeh.layouts import column, gridplot, row
from bokeh.layouts import column, row
from bokeh.models import Select, TabPanel, Tabs
from bokeh.plotting import curdoc

Expand Down Expand Up @@ -221,26 +221,32 @@ def test_bokeh(tmp_path):
displays = make_camera_displays(source=source, runid=runid)
timelines = make_timelines(source, runid)

ncols = 3
camera_displays = [
displays[parentkey][childkey].figure
row(
displays[parentkey][childkey][0].figure,
displays[parentkey][childkey][1],
displays[parentkey][childkey][2],
)
if len(displays[parentkey][childkey]) == 3
else displays[parentkey][childkey][0].figure
for parentkey in displays.keys()
for childkey in displays[parentkey].keys()
]

list_timelines = [
timelines[parentkey][childkey]
for parentkey in timelines.keys()
for childkey in timelines[parentkey].keys()
]

layout_camera_displays = gridplot(
layout_camera_displays = column(
camera_displays,
ncols=ncols,
sizing_mode="scale_width",
)

layout_timelines = gridplot(
layout_timelines = column(
list_timelines,
ncols=2,
sizing_mode="scale_width",
)
# Create different tabs
tab_camera_displays = TabPanel(
Expand Down
Loading