Skip to content

Commit e5999e2

Browse files
authored
Merge pull request #2715 from cta-observatory/stats_tool_shape_bugfix
Processing gain-selected dl1 data with the pixel stats tool
2 parents ec47070 + 4e3393e commit e5999e2

5 files changed

Lines changed: 62 additions & 3 deletions

File tree

docs/changes/2715.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix processing gain-selected dl1 data with the ``PixelStatisticsCalculatorTool``.

src/ctapipe/monitoring/outlier.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ class RangeOutlierDetector(OutlierDetector):
6565
).tag(config=True)
6666

6767
def __call__(self, column):
68+
# Validate that the shape of the statistic values has three dimensions
69+
if column.ndim != 3:
70+
raise ValueError(
71+
f"Invalid shape of the column '{column.name}': '{column.shape}'. "
72+
"Expected the statistic values of shape (n_entries, n_channels, n_pixels)."
73+
)
6874
# Remove outliers is statistical values out a given range
6975
outliers = np.logical_or(
7076
column < self.validity_range[0],
@@ -93,6 +99,12 @@ class MedianOutlierDetector(OutlierDetector):
9399
).tag(config=True)
94100

95101
def __call__(self, column):
102+
# Validate that the shape of the statistic values has three dimensions
103+
if column.ndim != 3:
104+
raise ValueError(
105+
f"Invalid shape of the column '{column.name}': '{column.shape}'. "
106+
"Expected the statistic values of shape (n_entries, n_channels, n_pixels)."
107+
)
96108
# Camera median
97109
camera_median = np.ma.median(column, axis=2)
98110
# Detect outliers based on the deviation of the median distribution
@@ -124,6 +136,12 @@ class StdOutlierDetector(OutlierDetector):
124136
).tag(config=True)
125137

126138
def __call__(self, column):
139+
# Validate that the shape of the statistic values has three dimensions
140+
if column.ndim != 3:
141+
raise ValueError(
142+
f"Invalid shape of the column '{column.name}': '{column.shape}'. "
143+
"Expected the statistic values of shape (n_entries, n_channels, n_pixels)."
144+
)
127145
# Camera median
128146
camera_median = np.ma.median(column, axis=2)
129147
# Camera std

src/ctapipe/monitoring/tests/test_outlier.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
"""
44

55
import numpy as np
6+
import pytest
67
from astropy.table import Table
78

89
from ctapipe.monitoring.outlier import (
910
MedianOutlierDetector,
11+
OutlierDetector,
1012
RangeOutlierDetector,
1113
StdOutlierDetector,
1214
)
@@ -102,3 +104,28 @@ def test_std_detection(example_subarray):
102104
# Check if outliers where detected correctly
103105
np.testing.assert_array_equal(ff_outliers, ff_expected_outliers)
104106
np.testing.assert_array_equal(ped_outliers, ped_expected_outliers)
107+
108+
109+
def test_check_for_column_shape(example_subarray):
110+
"""test the check for the shape of the column"""
111+
112+
# Create dummy data for testing
113+
rng = np.random.default_rng(0)
114+
# Distribution mimics the median values of gain-selected charge images of flat-field events
115+
median = rng.normal(77.0, 0.6, size=(50, 1855))
116+
# Create astropy table
117+
table = Table([median], names=("median",))
118+
# Initialize the outlier detector based on the deviation from the camera median
119+
outlier_detectors = [
120+
"RangeOutlierDetector",
121+
"MedianOutlierDetector",
122+
"StdOutlierDetector",
123+
]
124+
for outlier_detector_name in outlier_detectors:
125+
outlier_detector = OutlierDetector.from_name(
126+
outlier_detector_name,
127+
subarray=example_subarray,
128+
)
129+
# Check if ValueError is raised when the provided column shape is invalid
130+
with pytest.raises(ValueError, match="Invalid shape of the column"):
131+
outlier_detector(table["median"])

src/ctapipe/tools/calculate_pixel_stats.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ class PixelStatisticsCalculatorTool(Tool):
8282
TableLoader,
8383
] + classes_with_traits(PixelStatisticsCalculator)
8484

85+
DL1_COLUMN_NAMES = ["image", "peak_time"]
86+
8587
def setup(self):
8688
# Read the input data with the 'TableLoader'
8789
self.input_data = self.enter_context(
@@ -137,6 +139,10 @@ def start(self):
137139
f"Column '{self.input_column_name}' not found "
138140
f"in the input data for telescope 'tel_id={tel_id}'."
139141
)
142+
# Check if the dl1 data is gain selected and add an extra dimension for n_channels
143+
for col_name in self.DL1_COLUMN_NAMES:
144+
if col_name in dl1_table.colnames and dl1_table[col_name].ndim == 2:
145+
dl1_table[col_name] = dl1_table[col_name][:, np.newaxis]
140146
# Perform the first pass of the statistics calculation
141147
aggregated_stats = self.stats_calculator.first_pass(
142148
table=dl1_table,

src/ctapipe/tools/tests/test_calculate_pixel_stats.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ def test_calculate_pixel_stats_tool(tmp_path, dl1_image_file):
2929
"stats_aggregator_type": [
3030
("id", tel_id, "PlainAggregator"),
3131
],
32+
"outlier_detector_list": [
33+
{
34+
"apply_to": "mean",
35+
"name": "MedianOutlierDetector",
36+
"config": {"median_range_factors": [-2.0, 2.0]},
37+
}
38+
],
3239
},
3340
"PlainAggregator": {
3441
"chunk_size": 1,
@@ -50,13 +57,13 @@ def test_calculate_pixel_stats_tool(tmp_path, dl1_image_file):
5057
)
5158
# Check that the output file has been created
5259
assert monitoring_file.exists()
53-
# Check that the output file is not empty
60+
# Check if the shape of the aggregated statistic values has three dimension
5461
assert (
5562
read_table(
5663
monitoring_file,
5764
path=f"/dl1/monitoring/telescope/statistics/tel_{tel_id:03d}",
58-
)["mean"]
59-
is not None
65+
)["mean"].ndim
66+
== 3
6067
)
6168
# Read subarray description from the created monitoring file
6269
subarray = SubarrayDescription.from_hdf(monitoring_file)

0 commit comments

Comments
 (0)