Skip to content

Commit 9ad9de5

Browse files
committed
make HistogramAggregator a subclass of the stats one and calculate also stats value from the histo
Use as a normal stats aggregator and add testing for the pix stat comp and tool
1 parent 7e5db9f commit 9ad9de5

7 files changed

Lines changed: 455 additions & 187 deletions

File tree

docs/changes/2996.feature.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Added a new class HistogramsAggregator to compute histograms along a specified axis, and updated the documentation to reflect this new functionality. The documentation includes examples of how to use the HistogramsAggregator in practice.
1+
Added a new class HistogramAggregator to compute histograms along a specified axis, and updated the documentation to reflect this new functionality. The documentation includes examples of how to use the HistogramsAggregator in practice.

examples/tutorials/histograms_aggregation.py renamed to examples/tutorials/histogram_aggregation.py

Lines changed: 84 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
"""
2-
Histogram aggregation with HistogramsAggregator
2+
Histogram aggregation with HistogramAggregator
33
===============================================
44
55
This tutorial shows how to:
66
77
1. Build an event table with camera-like data (images and peak times) and some invalid values.
8-
2. Configure and run HistogramsAggregator in chunks.
9-
3. Access counts, bin edges, and valid-event counts (n_events).
8+
2. Configure and run HistogramAggregator in chunks.
9+
3. Access histogram counts, bin edges, summary statistics, and valid-event counts (n_events).
1010
4. Plot one pixel histogram from the selected chunks and both gain channels for both image and peak_time columns.
11+
5. Overlay mean, median, and std on top of the histogram curves.
1112
"""
1213

1314
import matplotlib.pyplot as plt
1415
import numpy as np
15-
import hist
16+
from matplotlib.lines import Line2D
17+
from matplotlib.patches import Patch
1618
from astropy.table import Table
1719
from astropy.time import Time
1820
from traitlets.config import Config
1921

20-
from ctapipe.monitoring.aggregator import HistogramsAggregator
22+
from ctapipe.monitoring.aggregator import HistogramAggregator
2123

2224

2325
# -------------------------------------------------------------------
@@ -36,7 +38,7 @@
3638
)
3739
event_ids = np.arange(n_events)
3840
images = rng.normal(loc=77.0, scale=10.0, size=(n_events, n_channels, n_pixels))
39-
peak_time = rng.normal(loc=20.0, scale=5.0, size=(n_events, n_channels, n_pixels))
41+
peak_time = rng.normal(loc=20.0, scale=2.0, size=(n_events, n_channels, n_pixels))
4042

4143
# Add a few invalid values to demonstrate n_events behavior.
4244
images[3, 0, 10] = np.nan
@@ -60,17 +62,23 @@
6062
# -------------------------------------------------------------------
6163
config_image = Config(
6264
{
63-
"HistogramsAggregator": {
65+
"HistogramAggregator": {
6466
"chunking_type": "SizeChunking",
67+
"hist_axis_dict": {
68+
"axis_class_name": "Regular",
69+
"kwargs": {
70+
"bins": 50,
71+
"start": 40.0,
72+
"stop": 110.0,
73+
"name": "value",
74+
},
75+
},
6576
},
6677
"SizeChunking": {"chunk_size": 1000},
6778
}
6879
)
6980

70-
aggregator_image = HistogramsAggregator(
71-
hist.axis.Regular(50, 40.0, 110.0, name="value"),
72-
config=config_image,
73-
)
81+
aggregator_image = HistogramAggregator(config=config_image)
7482
result = aggregator_image(
7583
table=table,
7684
col_name="image",
@@ -79,26 +87,32 @@
7987

8088
config_peak_time = Config(
8189
{
82-
"HistogramsAggregator": {
90+
"HistogramAggregator": {
8391
"chunking_type": "SizeChunking",
92+
"hist_axis_dict": {
93+
"axis_class_name": "Regular",
94+
"kwargs": {
95+
"bins": 50,
96+
"start": 2.0,
97+
"stop": 38.0,
98+
"name": "value",
99+
},
100+
},
84101
},
85102
"SizeChunking": {"chunk_size": 1000},
86103
}
87104
)
88105

89-
aggregator_peak_time = HistogramsAggregator(
90-
hist.axis.Regular(50, 2.0, 38.0, name="value"),
91-
config=config_peak_time,
92-
)
106+
aggregator_peak_time = HistogramAggregator(config=config_peak_time)
93107
result_peak_time = aggregator_peak_time(
94108
table=table,
95109
col_name="peak_time",
96110
masked_elements_of_sample=masked_elements_of_sample,
97111
)
98112

99113
print(f"Number of chunks: {len(result)}")
100-
print(f"counts shape per chunk: {result[0]['counts'].shape}")
101-
print(f"edges shape per chunk: {result[0]['edges'].shape}")
114+
print(f"histogram shape per chunk: {result[0]['histogram'].shape}")
115+
print(f"edges shape per chunk: {result[0]['meta']['bin_edges'].shape}")
102116
print(f"n_events shape per chunk: {result[0]['n_events'].shape}")
103117

104118

@@ -110,22 +124,43 @@
110124

111125
fig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)
112126
for chunk_index, ax in enumerate(axes):
113-
edges = result[chunk_index]["edges"]
127+
edges = result[chunk_index]["meta"]["bin_edges"]
128+
channel_handles = []
114129

115130
for channel_index in range(n_channels):
116-
counts = result[chunk_index]["counts"][:, channel_index, pixel_index]
131+
counts = result[chunk_index]["histogram"][:, channel_index, pixel_index]
117132
valid_events = result[chunk_index]["n_events"][channel_index, pixel_index]
118-
ax.step(
133+
mean_val = result[chunk_index]["mean"][channel_index, pixel_index]
134+
median_val = result[chunk_index]["median"][channel_index, pixel_index]
135+
std_val = result[chunk_index]["std"][channel_index, pixel_index]
136+
137+
line = ax.step(
119138
edges[:-1],
120139
counts,
121140
where="post",
122141
label=f"{gain_label[channel_index]} (n_events={valid_events})",
142+
)[0]
143+
channel_handles.append(line)
144+
color = line.get_color()
145+
146+
ax.axvline(mean_val, color=color, linestyle="--", linewidth=1.2)
147+
ax.axvline(median_val, color=color, linestyle=":", linewidth=1.2)
148+
ax.axvspan(
149+
mean_val - std_val,
150+
mean_val + std_val,
151+
color=color,
152+
alpha=0.12,
123153
)
124154

125155
ax.set_title(f"Chunk {chunk_index}, pixel {pixel_index}")
126156
ax.set_xlabel("image value")
127157
ax.set_ylabel("Counts")
128-
ax.legend(loc="upper right", fontsize=8)
158+
stat_handles = [
159+
Line2D([0], [0], color="black", linestyle="--", linewidth=1.2, label="Mean"),
160+
Line2D([0], [0], color="black", linestyle=":", linewidth=1.2, label="Median"),
161+
Patch(facecolor="gray", alpha=0.12, label="Mean ± Std"),
162+
]
163+
ax.legend(handles=channel_handles + stat_handles, loc="upper left", fontsize=8)
129164

130165
plt.show()
131166

@@ -135,23 +170,46 @@
135170
# -------------------------------------------------------------------
136171
fig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)
137172
for chunk_index, ax in enumerate(axes):
138-
edges = result_peak_time[chunk_index]["edges"]
173+
edges = result_peak_time[chunk_index]["meta"]["bin_edges"]
174+
channel_handles = []
139175

140176
for channel_index in range(n_channels):
141-
counts = result_peak_time[chunk_index]["counts"][:, channel_index, pixel_index]
177+
counts = result_peak_time[chunk_index]["histogram"][
178+
:, channel_index, pixel_index
179+
]
142180
valid_events = result_peak_time[chunk_index]["n_events"][
143181
channel_index, pixel_index
144182
]
145-
ax.step(
183+
mean_val = result_peak_time[chunk_index]["mean"][channel_index, pixel_index]
184+
median_val = result_peak_time[chunk_index]["median"][channel_index, pixel_index]
185+
std_val = result_peak_time[chunk_index]["std"][channel_index, pixel_index]
186+
187+
line = ax.step(
146188
edges[:-1],
147189
counts,
148190
where="post",
149191
label=f"{gain_label[channel_index]} (n_events={valid_events})",
192+
)[0]
193+
channel_handles.append(line)
194+
color = line.get_color()
195+
196+
ax.axvline(mean_val, color=color, linestyle="--", linewidth=1.2)
197+
ax.axvline(median_val, color=color, linestyle=":", linewidth=1.2)
198+
ax.axvspan(
199+
mean_val - std_val,
200+
mean_val + std_val,
201+
color=color,
202+
alpha=0.12,
150203
)
151204

152205
ax.set_title(f"Peak Time - Chunk {chunk_index}, pixel {pixel_index}")
153206
ax.set_xlabel("peak_time value")
154207
ax.set_ylabel("Counts")
155-
ax.legend(loc="upper right", fontsize=8)
208+
stat_handles = [
209+
Line2D([0], [0], color="black", linestyle="--", linewidth=1.2, label="Mean"),
210+
Line2D([0], [0], color="black", linestyle=":", linewidth=1.2, label="Median"),
211+
Patch(facecolor="gray", alpha=0.12, label="Mean ± Std"),
212+
]
213+
ax.legend(handles=channel_handles + stat_handles, loc="upper left", fontsize=8)
156214

157215
plt.show()

src/ctapipe/containers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,11 @@ class StatisticsContainer(Container):
12541254
"standard deviation of a pixel-wise quantity for each channel"
12551255
"Type: float; Shape: (n_channels, n_pixel)",
12561256
)
1257+
histogram = Field(
1258+
None,
1259+
"histogram of a pixel-wise quantity for each channel"
1260+
"Type: float; Shape: (n_bins, n_channels, n_pixel)",
1261+
)
12571262
n_events = Field(-1, "number of events used for the extraction of the statistics")
12581263
outlier_mask = Field(
12591264
None,

0 commit comments

Comments
 (0)