ctao-calibpipe 0.3.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- calibpipe/__init__.py +5 -0
- calibpipe/_dev_version/__init__.py +9 -0
- calibpipe/_version.py +34 -0
- calibpipe/atmosphere/__init__.py +1 -0
- calibpipe/atmosphere/atmosphere_containers.py +109 -0
- calibpipe/atmosphere/meteo_data_handlers.py +485 -0
- calibpipe/atmosphere/models/README.md +14 -0
- calibpipe/atmosphere/models/__init__.py +1 -0
- calibpipe/atmosphere/models/macobac.ecsv +23 -0
- calibpipe/atmosphere/models/reference_MDPs/__init__.py +1 -0
- calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_intermediate.ecsv +8 -0
- calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_summer.ecsv +8 -0
- calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_winter.ecsv +8 -0
- calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-south_summer.ecsv +8 -0
- calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-south_winter.ecsv +8 -0
- calibpipe/atmosphere/models/reference_atmospheres/__init__.py +1 -0
- calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_intermediate.ecsv +73 -0
- calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_summer.ecsv +73 -0
- calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_winter.ecsv +73 -0
- calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-south_summer.ecsv +73 -0
- calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-south_winter.ecsv +73 -0
- calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/__init__.py +1 -0
- calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_intermediate.ecsv +857 -0
- calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_summer.ecsv +857 -0
- calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_winter.ecsv +857 -0
- calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-south_summer.ecsv +857 -0
- calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-south_winter.ecsv +857 -0
- calibpipe/atmosphere/templates/request_templates/__init__.py +1 -0
- calibpipe/atmosphere/templates/request_templates/copernicus.json +11 -0
- calibpipe/atmosphere/templates/request_templates/gdas.json +12 -0
- calibpipe/core/__init__.py +39 -0
- calibpipe/core/common_metadata_containers.py +198 -0
- calibpipe/core/exceptions.py +87 -0
- calibpipe/database/__init__.py +24 -0
- calibpipe/database/adapter/__init__.py +23 -0
- calibpipe/database/adapter/adapter.py +80 -0
- calibpipe/database/adapter/database_containers/__init__.py +63 -0
- calibpipe/database/adapter/database_containers/atmosphere.py +199 -0
- calibpipe/database/adapter/database_containers/common_metadata.py +150 -0
- calibpipe/database/adapter/database_containers/container_map.py +59 -0
- calibpipe/database/adapter/database_containers/observatory.py +61 -0
- calibpipe/database/adapter/database_containers/table_version_manager.py +39 -0
- calibpipe/database/adapter/database_containers/throughput.py +30 -0
- calibpipe/database/adapter/database_containers/version_control.py +17 -0
- calibpipe/database/connections/__init__.py +28 -0
- calibpipe/database/connections/calibpipe_database.py +60 -0
- calibpipe/database/connections/postgres_utils.py +97 -0
- calibpipe/database/connections/sql_connection.py +103 -0
- calibpipe/database/connections/user_confirmation.py +19 -0
- calibpipe/database/interfaces/__init__.py +71 -0
- calibpipe/database/interfaces/hashable_row_data.py +54 -0
- calibpipe/database/interfaces/queries.py +180 -0
- calibpipe/database/interfaces/sql_column_info.py +67 -0
- calibpipe/database/interfaces/sql_metadata.py +6 -0
- calibpipe/database/interfaces/sql_table_info.py +131 -0
- calibpipe/database/interfaces/table_handler.py +333 -0
- calibpipe/database/interfaces/types.py +96 -0
- calibpipe/telescope/throughput/containers.py +66 -0
- calibpipe/tests/conftest.py +274 -0
- calibpipe/tests/data/atmosphere/molecular_atmosphere/__init__.py +0 -0
- calibpipe/tests/data/atmosphere/molecular_atmosphere/contemporary_MDP.ecsv +34 -0
- calibpipe/tests/data/atmosphere/molecular_atmosphere/macobac.csv +852 -0
- calibpipe/tests/data/atmosphere/molecular_atmosphere/macobac.ecsv +23 -0
- calibpipe/tests/data/atmosphere/molecular_atmosphere/merged_file.ecsv +1082 -0
- calibpipe/tests/data/atmosphere/molecular_atmosphere/meteo_data_copernicus.ecsv +1082 -0
- calibpipe/tests/data/atmosphere/molecular_atmosphere/meteo_data_gdas.ecsv +66 -0
- calibpipe/tests/data/atmosphere/molecular_atmosphere/observatory_configurations.json +71 -0
- calibpipe/tests/data/utils/__init__.py +0 -0
- calibpipe/tests/data/utils/meteo_data_winter_and_summer.ecsv +12992 -0
- calibpipe/tests/test_conftest_data.py +200 -0
- calibpipe/tests/unittests/array/test_cross_calibration.py +412 -0
- calibpipe/tests/unittests/atmosphere/astral_testing.py +107 -0
- calibpipe/tests/unittests/atmosphere/test_meteo_data_handler.py +775 -0
- calibpipe/tests/unittests/atmosphere/test_molecular_atmosphere.py +327 -0
- calibpipe/tests/unittests/database/test_table_handler.py +163 -0
- calibpipe/tests/unittests/database/test_types.py +38 -0
- calibpipe/tests/unittests/telescope/camera/test_calculate_camcalib_coefficients.py +456 -0
- calibpipe/tests/unittests/telescope/camera/test_produce_camcalib_test_data.py +37 -0
- calibpipe/tests/unittests/telescope/throughput/test_muon_throughput_calibrator.py +693 -0
- calibpipe/tests/unittests/test_bootstrap_db.py +79 -0
- calibpipe/tests/unittests/utils/test_observatory.py +309 -0
- calibpipe/tools/atmospheric_base_tool.py +78 -0
- calibpipe/tools/atmospheric_model_db_loader.py +181 -0
- calibpipe/tools/basic_tool_with_db.py +38 -0
- calibpipe/tools/camcalib_test_data.py +374 -0
- calibpipe/tools/camera_calibrator.py +462 -0
- calibpipe/tools/contemporary_mdp_producer.py +87 -0
- calibpipe/tools/init_db.py +37 -0
- calibpipe/tools/macobac_calculator.py +82 -0
- calibpipe/tools/molecular_atmospheric_model_producer.py +197 -0
- calibpipe/tools/muon_throughput_calculator.py +219 -0
- calibpipe/tools/observatory_data_db_loader.py +71 -0
- calibpipe/tools/reference_atmospheric_model_selector.py +201 -0
- calibpipe/tools/telescope_cross_calibration_calculator.py +721 -0
- calibpipe/utils/__init__.py +10 -0
- calibpipe/utils/observatory.py +486 -0
- calibpipe/utils/observatory_containers.py +26 -0
- calibpipe/version.py +24 -0
- ctao_calibpipe-0.3.0rc2.dist-info/METADATA +92 -0
- ctao_calibpipe-0.3.0rc2.dist-info/RECORD +105 -0
- ctao_calibpipe-0.3.0rc2.dist-info/WHEEL +5 -0
- ctao_calibpipe-0.3.0rc2.dist-info/entry_points.txt +12 -0
- ctao_calibpipe-0.3.0rc2.dist-info/licenses/AUTHORS.md +13 -0
- ctao_calibpipe-0.3.0rc2.dist-info/licenses/LICENSE +21 -0
- ctao_calibpipe-0.3.0rc2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test calibpipe-calculate-camcalib-coefficients tool
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
import tables
|
|
12
|
+
import yaml
|
|
13
|
+
from astropy import units as u
|
|
14
|
+
from astropy.time import Time
|
|
15
|
+
from calibpipe.tools.camera_calibrator import CameraCalibratorTool
|
|
16
|
+
from ctapipe.core import ToolConfigurationError, run_tool
|
|
17
|
+
from ctapipe.instrument import SubarrayDescription
|
|
18
|
+
from ctapipe.io import read_table, write_table
|
|
19
|
+
from ctapipe.io.hdf5dataformat import (
|
|
20
|
+
DL1_CAMERA_COEFFICIENTS_GROUP,
|
|
21
|
+
DL1_PIXEL_STATISTICS_GROUP,
|
|
22
|
+
DL1_TEL_IMAGES_GROUP,
|
|
23
|
+
SIMULATION_GROUP,
|
|
24
|
+
)
|
|
25
|
+
from ctapipe.tools.calculate_pixel_stats import PixelStatisticsCalculatorTool
|
|
26
|
+
from ctapipe.tools.merge import MergeTool
|
|
27
|
+
from ctapipe.tools.process import ProcessorTool
|
|
28
|
+
from traitlets.config.loader import Config
|
|
29
|
+
|
|
30
|
+
# Get the path to the configuration files
|
|
31
|
+
CONFIG_PATH = Path(__file__).parent.joinpath(
|
|
32
|
+
"../../../../../../docs/source/user_guide/telescope/camera/configuration/"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture(scope="session")
|
|
37
|
+
def camera_test_data_dir(
|
|
38
|
+
flatfield_file, pedestal_file, calibpipe_test_data_dir, tmp_path_factory
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
Fixture providing camera test data files with proper naming.
|
|
42
|
+
|
|
43
|
+
This fixture creates a temporary directory with the test files
|
|
44
|
+
named as expected by the camera calibration tests.
|
|
45
|
+
"""
|
|
46
|
+
temp_dir = tmp_path_factory.mktemp("camera_test_data")
|
|
47
|
+
|
|
48
|
+
# Copy files with the expected names
|
|
49
|
+
shutil.copy2(flatfield_file, temp_dir / "flatfield_LST_dark.simtel.gz")
|
|
50
|
+
shutil.copy2(pedestal_file, temp_dir / "pedestal_LST_dark.simtel.gz")
|
|
51
|
+
|
|
52
|
+
# Copy the pre-computed stats files for high_stats tests
|
|
53
|
+
for mode in ["single_chunk", "same_chunks", "different_chunks"]:
|
|
54
|
+
stats_file = (
|
|
55
|
+
calibpipe_test_data_dir
|
|
56
|
+
/ "telescope"
|
|
57
|
+
/ "camera"
|
|
58
|
+
/ f"high_statsagg_v1_{mode}.dl1.h5"
|
|
59
|
+
)
|
|
60
|
+
if stats_file.exists():
|
|
61
|
+
shutil.copy2(stats_file, temp_dir / f"high_statsagg_v1_{mode}.dl1.h5")
|
|
62
|
+
|
|
63
|
+
return temp_dir
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# The telescope ID to be used in the tests
|
|
67
|
+
TEL_ID = 1
|
|
68
|
+
# The monitoring groups to be used in the tests
|
|
69
|
+
CAMERA_MONITORING_GROUPS = [
|
|
70
|
+
"sky_pedestal_image",
|
|
71
|
+
"flatfield_image",
|
|
72
|
+
"flatfield_peak_time",
|
|
73
|
+
]
|
|
74
|
+
# Define reference time and trigger rate for the tests. These values
|
|
75
|
+
# are used to create realistic timestamps for the aggregated chunks.
|
|
76
|
+
REFERENCE_TIME = Time.now()
|
|
77
|
+
REFERENCE_TRIGGER_RATE = 1000.0 * u.Hz
|
|
78
|
+
# Simulated values for the tests for the different gain channels
|
|
79
|
+
# HG: High Gain, LG: Low Gain
|
|
80
|
+
EXPECTED_ADC_OFFSET = {"HG": 400, "LG": 400}
|
|
81
|
+
EXPECTED_DC2PE = {"HG": 0.015, "LG": 0.25}
|
|
82
|
+
EXPECTED_TIME_SHIFT = {"HG": 0.0, "LG": 0.0}
|
|
83
|
+
# Set different file prefixes and tolerances for the two statistic modes
|
|
84
|
+
# - 'low_stats': 100 events per calibration type processed from simtel files to the final camcalib
|
|
85
|
+
# coefficients to ensure that the camcalib coefficients calculation works from DL0.
|
|
86
|
+
# Due to the low number of events, the tolerance for the coefficients is relatively high,
|
|
87
|
+
# since the statistics are not sufficient to calculate the coefficients with high precision.
|
|
88
|
+
# - 'high_stats': 25000 events per calibration type already aggregated from simtel files via
|
|
89
|
+
# the 'CamCalibTestDataTool' and retrieved from MinIO. The final camcalib coefficients
|
|
90
|
+
# are calculated from the aggregated statistics files to test the correctness
|
|
91
|
+
# of the camcalib coefficients calculation within a restrictive tolerance.
|
|
92
|
+
FILE_PREFIX = {"low_stats": "low_statsagg_", "high_stats": "high_statsagg_v1_"}
|
|
93
|
+
DC2PE_TOLERANCE = {
|
|
94
|
+
"low_stats": {"rtol": 0.25, "atol": 0.0},
|
|
95
|
+
"high_stats": {"rtol": 0.02, "atol": 0.0},
|
|
96
|
+
}
|
|
97
|
+
ADC_OFFSET_TOLERANCE = {
|
|
98
|
+
"low_stats": {"rtol": 0.0, "atol": 10.0},
|
|
99
|
+
"high_stats": {"rtol": 0.0, "atol": 2.0},
|
|
100
|
+
}
|
|
101
|
+
TIME_SHIFT_TOLERANCE = {
|
|
102
|
+
"low_stats": {"rtol": 0.0, "atol": 0.25},
|
|
103
|
+
"high_stats": {"rtol": 0.0, "atol": 0.25},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@pytest.mark.order(1)
|
|
108
|
+
def test_produce_dl1_image_file(camera_test_data_dir):
|
|
109
|
+
"""
|
|
110
|
+
Produce DL1A file containing the images of the calibration events.
|
|
111
|
+
"""
|
|
112
|
+
# Set the path to the simtel calibration events
|
|
113
|
+
for calibration_type in ["pedestal", "flatfield"]:
|
|
114
|
+
simtel_file = camera_test_data_dir.joinpath(
|
|
115
|
+
f"{calibration_type}_LST_dark.simtel.gz"
|
|
116
|
+
)
|
|
117
|
+
# Set the output file path for pedestal images
|
|
118
|
+
image_file = camera_test_data_dir.joinpath(f"{calibration_type}_events.dl1.h5")
|
|
119
|
+
with open(
|
|
120
|
+
CONFIG_PATH.joinpath(f"ctapipe_process_{calibration_type}.yaml")
|
|
121
|
+
) as yaml_file:
|
|
122
|
+
config = yaml.safe_load(yaml_file)
|
|
123
|
+
# Run the ProcessorTool to create pedestal images
|
|
124
|
+
assert (
|
|
125
|
+
run_tool(
|
|
126
|
+
ProcessorTool(config=Config(config)),
|
|
127
|
+
argv=[
|
|
128
|
+
f"--input={simtel_file}",
|
|
129
|
+
f"--output={image_file}",
|
|
130
|
+
"--overwrite",
|
|
131
|
+
],
|
|
132
|
+
cwd=camera_test_data_dir,
|
|
133
|
+
)
|
|
134
|
+
== 0
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.order(2)
|
|
139
|
+
@pytest.mark.verifies_usecase("UC-120-2.21")
|
|
140
|
+
@pytest.mark.parametrize(
|
|
141
|
+
"aggregation_mode",
|
|
142
|
+
["single_chunk", "same_chunks", "different_chunks"],
|
|
143
|
+
)
|
|
144
|
+
def test_stats_aggregation(aggregation_mode, camera_test_data_dir):
|
|
145
|
+
"""
|
|
146
|
+
DL1 camera monitoring file containing the statistics aggregation for a given chunk mode.
|
|
147
|
+
"""
|
|
148
|
+
# Loop over the monitoring groups and calculate pixel statistics
|
|
149
|
+
output_files = []
|
|
150
|
+
for mon_group in CAMERA_MONITORING_GROUPS:
|
|
151
|
+
# Set the output file path for the statistics aggregation
|
|
152
|
+
output_file = camera_test_data_dir.joinpath(
|
|
153
|
+
f"low_statsagg_{mon_group}_{aggregation_mode}.dl1.h5"
|
|
154
|
+
)
|
|
155
|
+
# Save the output file path to the list to be used later in the merge tool
|
|
156
|
+
output_files.append(str(output_file))
|
|
157
|
+
# Set the input file path for the PixelStatisticsCalculator
|
|
158
|
+
dl1_image_file = (
|
|
159
|
+
camera_test_data_dir.joinpath("pedestal_events.dl1.h5")
|
|
160
|
+
if mon_group == "sky_pedestal_image"
|
|
161
|
+
else camera_test_data_dir.joinpath("flatfield_events.dl1.h5")
|
|
162
|
+
)
|
|
163
|
+
# Get the standard configuration for the PixelStatisticsCalculator
|
|
164
|
+
with open(
|
|
165
|
+
CONFIG_PATH.joinpath(f"ctapipe_calculate_pixel_stats_{mon_group}.yaml")
|
|
166
|
+
) as yaml_file:
|
|
167
|
+
pix_stats_config = yaml.safe_load(yaml_file)
|
|
168
|
+
# Set some additional parameters using cli arguments
|
|
169
|
+
cli_argv = [
|
|
170
|
+
f"--input_url={dl1_image_file}",
|
|
171
|
+
f"--output_path={output_file}",
|
|
172
|
+
]
|
|
173
|
+
n_events = len(
|
|
174
|
+
read_table(
|
|
175
|
+
dl1_image_file,
|
|
176
|
+
path=f"{DL1_TEL_IMAGES_GROUP}/tel_{TEL_ID:03d}",
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
# Modify the configuration for the specific chunk mode
|
|
180
|
+
if aggregation_mode == "single_chunk":
|
|
181
|
+
chunk_duration = 1000.0 * u.s
|
|
182
|
+
# Use a single chunk size for all monitoring groups
|
|
183
|
+
# Overwrite the chunk size for the specific aggregator
|
|
184
|
+
cli_argv.append(f"--SizeChunking.chunk_size={n_events}")
|
|
185
|
+
elif aggregation_mode == "same_chunks":
|
|
186
|
+
chunk_duration = 100.0 * u.s
|
|
187
|
+
# Overwrite the chunk size for the specific aggregators to have ten chunks
|
|
188
|
+
cli_argv.append(f"--SizeChunking.chunk_size={n_events//10}")
|
|
189
|
+
elif aggregation_mode == "different_chunks":
|
|
190
|
+
# Use different chunk sizes for each monitoring group
|
|
191
|
+
if mon_group == "sky_pedestal_image":
|
|
192
|
+
chunk_duration = 200.0 * u.s
|
|
193
|
+
cli_argv.append(f"--SizeChunking.chunk_size={2 * (n_events//10)}")
|
|
194
|
+
elif mon_group == "flatfield_image":
|
|
195
|
+
chunk_duration = 100.0 * u.s
|
|
196
|
+
cli_argv.append(f"--SizeChunking.chunk_size={n_events//10}")
|
|
197
|
+
elif mon_group == "flatfield_peak_time":
|
|
198
|
+
chunk_duration = 500.0 * u.s
|
|
199
|
+
cli_argv.append(f"--SizeChunking.chunk_size={5 * (n_events//10)}")
|
|
200
|
+
|
|
201
|
+
# Run the PixelStatisticsCalculatorTool to calculate pixel statistics
|
|
202
|
+
assert (
|
|
203
|
+
run_tool(
|
|
204
|
+
PixelStatisticsCalculatorTool(config=Config(pix_stats_config)),
|
|
205
|
+
argv=cli_argv,
|
|
206
|
+
cwd=camera_test_data_dir,
|
|
207
|
+
raises=True,
|
|
208
|
+
)
|
|
209
|
+
== 0
|
|
210
|
+
)
|
|
211
|
+
# Overwrite timestamps in the output file to make them realistic
|
|
212
|
+
# Read the created statsagg table for the specific monitoring group
|
|
213
|
+
stats_aggregation_tab = read_table(
|
|
214
|
+
output_file,
|
|
215
|
+
path=f"{DL1_PIXEL_STATISTICS_GROUP}/{mon_group}/tel_{TEL_ID:03d}",
|
|
216
|
+
)
|
|
217
|
+
# Loop over the chunks and set the new timestamps
|
|
218
|
+
for chunk_nr in range(len(stats_aggregation_tab)):
|
|
219
|
+
stats_aggregation_tab["time_start"][chunk_nr] = (
|
|
220
|
+
REFERENCE_TIME
|
|
221
|
+
+ (1 / REFERENCE_TRIGGER_RATE).to(u.s)
|
|
222
|
+
+ chunk_nr * chunk_duration
|
|
223
|
+
)
|
|
224
|
+
stats_aggregation_tab["time_end"][chunk_nr] = (
|
|
225
|
+
REFERENCE_TIME + (chunk_nr + 1) * chunk_duration
|
|
226
|
+
)
|
|
227
|
+
# Set a different starting time (outside the default 1 second tolerance)
|
|
228
|
+
# for the pedestal group if the mode is 'diffent_chunks'. This is to ensure
|
|
229
|
+
# that the we can later test when the chunk interpolator is returning NaN values
|
|
230
|
+
# for the first and last unique timestamps.
|
|
231
|
+
if aggregation_mode == "different_chunks":
|
|
232
|
+
if mon_group == "sky_pedestal_image":
|
|
233
|
+
stats_aggregation_tab["time_start"][0] -= 2 * u.s
|
|
234
|
+
stats_aggregation_tab["time_end"][-1] += 2 * u.s
|
|
235
|
+
# Overwrite the table in the output file
|
|
236
|
+
write_table(
|
|
237
|
+
stats_aggregation_tab,
|
|
238
|
+
output_file,
|
|
239
|
+
f"{DL1_PIXEL_STATISTICS_GROUP}/{mon_group}/tel_{TEL_ID:03d}",
|
|
240
|
+
overwrite=True,
|
|
241
|
+
)
|
|
242
|
+
# Run the merge tool to combine the statistics
|
|
243
|
+
# from the three files into a single monitoring file
|
|
244
|
+
monitoring_sims_file = camera_test_data_dir.joinpath(
|
|
245
|
+
f"low_statsagg_{aggregation_mode}.dl1.h5"
|
|
246
|
+
)
|
|
247
|
+
run_tool(
|
|
248
|
+
MergeTool(),
|
|
249
|
+
argv=output_files
|
|
250
|
+
+ [
|
|
251
|
+
f"--output={monitoring_sims_file}",
|
|
252
|
+
"--monitoring",
|
|
253
|
+
"--single-ob",
|
|
254
|
+
],
|
|
255
|
+
cwd=camera_test_data_dir,
|
|
256
|
+
raises=True,
|
|
257
|
+
)
|
|
258
|
+
# Also create a monitoring file mimicking a real observation file
|
|
259
|
+
monitoring_obs_file = camera_test_data_dir.joinpath(
|
|
260
|
+
f"low_statsagg_{aggregation_mode}_obs.dl1.h5"
|
|
261
|
+
)
|
|
262
|
+
shutil.copy(monitoring_sims_file, monitoring_obs_file)
|
|
263
|
+
# Remove the simulation to mimic a real observation file
|
|
264
|
+
with tables.open_file(monitoring_obs_file, "r+") as f:
|
|
265
|
+
f.remove_node(SIMULATION_GROUP, recursive=True)
|
|
266
|
+
for monitoring_file in [monitoring_sims_file, monitoring_obs_file]:
|
|
267
|
+
# Check that the output file has been created
|
|
268
|
+
assert monitoring_file.exists()
|
|
269
|
+
for mon_group in CAMERA_MONITORING_GROUPS:
|
|
270
|
+
# Read the monitoring table for the specific monitoring group
|
|
271
|
+
stats_aggregation_tab = read_table(
|
|
272
|
+
monitoring_file,
|
|
273
|
+
path=f"{DL1_PIXEL_STATISTICS_GROUP}/{mon_group}/tel_{TEL_ID:03d}",
|
|
274
|
+
)
|
|
275
|
+
# Check that the timestamps are set correctly
|
|
276
|
+
assert len(stats_aggregation_tab) > 0
|
|
277
|
+
assert np.all(
|
|
278
|
+
stats_aggregation_tab["time_start"] >= REFERENCE_TIME - 2 * u.s
|
|
279
|
+
)
|
|
280
|
+
assert np.all(stats_aggregation_tab["time_end"] >= REFERENCE_TIME)
|
|
281
|
+
# Check that the timestamps are in ascending order
|
|
282
|
+
assert np.all(
|
|
283
|
+
stats_aggregation_tab["time_start"][1:]
|
|
284
|
+
>= stats_aggregation_tab["time_start"][:-1]
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# We are ignoring the warning about NaN slices, since we expect all values to be
|
|
289
|
+
# NaN for the first and last timestamps in the 'different_chunks' mode.
|
|
290
|
+
@pytest.mark.order(3)
|
|
291
|
+
@pytest.mark.verifies_usecase("UC-120-2.20")
|
|
292
|
+
@pytest.mark.parametrize(
|
|
293
|
+
"statistic_mode",
|
|
294
|
+
["low_stats", "high_stats"],
|
|
295
|
+
)
|
|
296
|
+
@pytest.mark.filterwarnings("ignore:All-NaN slice encountered")
|
|
297
|
+
def test_calculate_camcalib_coeffs_tool(statistic_mode, camera_test_data_dir):
|
|
298
|
+
"""check camcalib coefficients calculation from dl1 camera monitoring data files"""
|
|
299
|
+
# There are three different aggregation modes:
|
|
300
|
+
# - single_chunk: all monitoring groups are aggregated in a single chunk
|
|
301
|
+
# - same_chunks: all monitoring groups are aggregated in the same chunks
|
|
302
|
+
# - different_chunks: each monitoring group is aggregated in different chunks
|
|
303
|
+
for aggregation_mode in ["single_chunk", "same_chunks", "different_chunks"]:
|
|
304
|
+
# Set the path to the simtel pedestal events
|
|
305
|
+
stats_aggregation_sims_file = camera_test_data_dir.joinpath(
|
|
306
|
+
f"{FILE_PREFIX[statistic_mode]}{aggregation_mode}.dl1.h5"
|
|
307
|
+
)
|
|
308
|
+
# Create a monitoring file mimicking a real observation file
|
|
309
|
+
stats_aggregation_obs_file = camera_test_data_dir.joinpath(
|
|
310
|
+
f"{FILE_PREFIX[statistic_mode]}{aggregation_mode}_obs.dl1.h5"
|
|
311
|
+
)
|
|
312
|
+
shutil.copy(stats_aggregation_sims_file, stats_aggregation_obs_file)
|
|
313
|
+
# Remove the simulation to mimic a real observation file
|
|
314
|
+
with tables.open_file(stats_aggregation_obs_file, "r+") as f:
|
|
315
|
+
f.remove_node(SIMULATION_GROUP, recursive=True)
|
|
316
|
+
for stats_aggregation_file in [
|
|
317
|
+
stats_aggregation_sims_file,
|
|
318
|
+
stats_aggregation_obs_file,
|
|
319
|
+
]:
|
|
320
|
+
# Run the tool with the configuration and the input file
|
|
321
|
+
assert (
|
|
322
|
+
run_tool(
|
|
323
|
+
CameraCalibratorTool(),
|
|
324
|
+
argv=[
|
|
325
|
+
f"--input_url={stats_aggregation_file}",
|
|
326
|
+
"--overwrite",
|
|
327
|
+
],
|
|
328
|
+
cwd=camera_test_data_dir,
|
|
329
|
+
raises=True,
|
|
330
|
+
)
|
|
331
|
+
== 0
|
|
332
|
+
)
|
|
333
|
+
# Read subarray description from the created monitoring file
|
|
334
|
+
subarray = SubarrayDescription.from_hdf(stats_aggregation_file)
|
|
335
|
+
# Check for the selected telescope
|
|
336
|
+
assert subarray.tel_ids[0] == TEL_ID
|
|
337
|
+
# Read the camera calibration coefficients from the created monitoring file
|
|
338
|
+
# and check that the calculated values are as expected.
|
|
339
|
+
camcalib_coeffs = read_table(
|
|
340
|
+
stats_aggregation_file,
|
|
341
|
+
path=f"{DL1_CAMERA_COEFFICIENTS_GROUP}/tel_{TEL_ID:03d}",
|
|
342
|
+
)
|
|
343
|
+
for i in range(len(camcalib_coeffs)):
|
|
344
|
+
if (
|
|
345
|
+
aggregation_mode == "different_chunks"
|
|
346
|
+
and stats_aggregation_file == stats_aggregation_obs_file
|
|
347
|
+
):
|
|
348
|
+
# For the 'different_chunks' mode, we expect the first factor and pedestal
|
|
349
|
+
# to be NaN, since the first timestamp is not valid for the pedestal group.
|
|
350
|
+
if i == 0 or i == len(camcalib_coeffs) - 1:
|
|
351
|
+
# Check that the factor and time shift are NaN for the first and last timestamps
|
|
352
|
+
assert np.isnan(camcalib_coeffs["factor"][i]).all()
|
|
353
|
+
assert np.isnan(camcalib_coeffs["time_shift"][i]).all()
|
|
354
|
+
# Check that the outlier mask is all True for the first and last timestamps
|
|
355
|
+
assert camcalib_coeffs["outlier_mask"][i].all()
|
|
356
|
+
# Check that the is_valid flag is False for the first and last timestamps
|
|
357
|
+
assert not camcalib_coeffs["is_valid"][i]
|
|
358
|
+
# Check that the pedestal offsets are not NaN since the first and last timestamps
|
|
359
|
+
# are valid for the pedestal group.
|
|
360
|
+
for g, gain_channel in enumerate(["HG", "LG"]):
|
|
361
|
+
np.testing.assert_allclose(
|
|
362
|
+
np.nanmedian(camcalib_coeffs["pedestal_offset"][i][g]),
|
|
363
|
+
EXPECTED_ADC_OFFSET[gain_channel],
|
|
364
|
+
rtol=ADC_OFFSET_TOLERANCE[statistic_mode]["rtol"],
|
|
365
|
+
atol=ADC_OFFSET_TOLERANCE[statistic_mode]["atol"],
|
|
366
|
+
err_msg=(
|
|
367
|
+
f"Pedestal per sample values do not match expected values within "
|
|
368
|
+
f"a tolerance of {int(ADC_OFFSET_TOLERANCE[statistic_mode]['atol'])} ADC counts"
|
|
369
|
+
),
|
|
370
|
+
)
|
|
371
|
+
continue
|
|
372
|
+
# Check that the median of the calculated factor is close to the
|
|
373
|
+
# simtel_dc2pe values for the corresponding gain channel.
|
|
374
|
+
for g, gain_channel in enumerate(["HG", "LG"]):
|
|
375
|
+
np.testing.assert_allclose(
|
|
376
|
+
np.nanmedian(camcalib_coeffs["factor"][i][g]),
|
|
377
|
+
EXPECTED_DC2PE[gain_channel],
|
|
378
|
+
rtol=DC2PE_TOLERANCE[statistic_mode]["rtol"],
|
|
379
|
+
atol=DC2PE_TOLERANCE[statistic_mode]["atol"],
|
|
380
|
+
err_msg=(
|
|
381
|
+
f"Factor coefficients do not match expected values within "
|
|
382
|
+
f"a tolerance of {int(DC2PE_TOLERANCE[statistic_mode]['rtol']*100)}%"
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
# Check that the median of the calculated pedestal offset is close to the
|
|
386
|
+
# simtel_pedestal_per_sample values for the corresponding gain channel.
|
|
387
|
+
np.testing.assert_allclose(
|
|
388
|
+
np.nanmedian(camcalib_coeffs["pedestal_offset"][i][g]),
|
|
389
|
+
EXPECTED_ADC_OFFSET[gain_channel],
|
|
390
|
+
rtol=ADC_OFFSET_TOLERANCE[statistic_mode]["rtol"],
|
|
391
|
+
atol=ADC_OFFSET_TOLERANCE[statistic_mode]["atol"],
|
|
392
|
+
err_msg=(
|
|
393
|
+
f"Pedestal per sample values do not match expected values within "
|
|
394
|
+
f"a tolerance of {int(ADC_OFFSET_TOLERANCE[statistic_mode]['atol'])} ADC counts"
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
# Check that the median of the calculated time shift is close to the
|
|
398
|
+
# simtel_time_shift values for the corresponding gain channel.
|
|
399
|
+
np.testing.assert_allclose(
|
|
400
|
+
np.nanmedian(camcalib_coeffs["time_shift"][i][g]),
|
|
401
|
+
EXPECTED_TIME_SHIFT[gain_channel],
|
|
402
|
+
rtol=TIME_SHIFT_TOLERANCE[statistic_mode]["rtol"],
|
|
403
|
+
atol=TIME_SHIFT_TOLERANCE[statistic_mode]["atol"],
|
|
404
|
+
err_msg=(
|
|
405
|
+
"Time shift values do not match expected values "
|
|
406
|
+
"within a tolerance of a quarter of a waveform sample"
|
|
407
|
+
),
|
|
408
|
+
)
|
|
409
|
+
# Check that the is_valid flag is True for all timestamps
|
|
410
|
+
assert camcalib_coeffs["is_valid"][i]
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def test_npe_std_outlier_detector(camera_test_data_dir):
|
|
414
|
+
"""check camcalib coefficients calculation with the NpeStdOutlierDetector"""
|
|
415
|
+
# Only consider the single_chunk aggregation mode for this test
|
|
416
|
+
stats_aggregation_file = camera_test_data_dir.joinpath(
|
|
417
|
+
"high_statsagg_v1_single_chunk.dl1.h5"
|
|
418
|
+
)
|
|
419
|
+
# Read the NpeStdOutlierDetector configuration from the YAML file
|
|
420
|
+
with open(CONFIG_PATH.joinpath("npe_std_outlier_detector.yaml")) as yaml_file:
|
|
421
|
+
npe_std_outlier_detector_config = yaml.safe_load(yaml_file)
|
|
422
|
+
# Run the CameraCalibratorTool with the NpeStdOutlierDetector configuration
|
|
423
|
+
assert (
|
|
424
|
+
run_tool(
|
|
425
|
+
CameraCalibratorTool(config=Config(npe_std_outlier_detector_config)),
|
|
426
|
+
argv=[
|
|
427
|
+
f"--input_url={stats_aggregation_file}",
|
|
428
|
+
"--overwrite",
|
|
429
|
+
],
|
|
430
|
+
cwd=camera_test_data_dir,
|
|
431
|
+
raises=True,
|
|
432
|
+
)
|
|
433
|
+
== 0
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def test_exceptions_camcalibtool(camera_test_data_dir):
|
|
438
|
+
"""check exceptions of the CameraCalibratorTool"""
|
|
439
|
+
# Only consider the single_chunk aggregation mode for this test
|
|
440
|
+
stats_aggregation_file = camera_test_data_dir.joinpath(
|
|
441
|
+
"high_statsagg_v1_same_chunks.dl1.h5"
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
with pytest.raises(
|
|
445
|
+
ToolConfigurationError,
|
|
446
|
+
match="CameraCalibratorTool requires exactly one input file.",
|
|
447
|
+
):
|
|
448
|
+
run_tool(
|
|
449
|
+
CameraCalibratorTool(),
|
|
450
|
+
argv=[
|
|
451
|
+
f"--input_url={stats_aggregation_file}",
|
|
452
|
+
f"--input_url={stats_aggregation_file}",
|
|
453
|
+
],
|
|
454
|
+
cwd=camera_test_data_dir,
|
|
455
|
+
raises=True,
|
|
456
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test calibpipe-produce-camcalib-test-data tool
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from calibpipe.tools.camcalib_test_data import CamCalibTestDataTool
|
|
9
|
+
from ctapipe.core import run_tool
|
|
10
|
+
|
|
11
|
+
# Get the path to the configuration files
|
|
12
|
+
CONFIG_PATH = Path(__file__).parent.joinpath(
|
|
13
|
+
"../../../../../../docs/source/user_guide/telescope/camera/configuration/"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_produce_camcalib_test_data(pedestal_file, flatfield_file, tmp_path):
|
|
18
|
+
"""Test the calibpipe-produce-camcalib-test-data tool"""
|
|
19
|
+
# Run the tool with the configuration and the input files
|
|
20
|
+
assert (
|
|
21
|
+
run_tool(
|
|
22
|
+
CamCalibTestDataTool(),
|
|
23
|
+
argv=[
|
|
24
|
+
f"--CamCalibTestDataTool.pedestal_input_url={pedestal_file}",
|
|
25
|
+
f"--CamCalibTestDataTool.flatfield_input_url={flatfield_file}",
|
|
26
|
+
f"--CamCalibTestDataTool.output_dir={tmp_path}",
|
|
27
|
+
f"--CamCalibTestDataTool.process_pedestal_config={CONFIG_PATH.joinpath('ctapipe_process_pedestal.yaml')}",
|
|
28
|
+
f"--CamCalibTestDataTool.process_flatfield_config={CONFIG_PATH.joinpath('ctapipe_process_flatfield.yaml')}",
|
|
29
|
+
f"--CamCalibTestDataTool.agg_stats_sky_pedestal_image_config={CONFIG_PATH.joinpath('ctapipe_calculate_pixel_stats_sky_pedestal_image.yaml')}",
|
|
30
|
+
f"--CamCalibTestDataTool.agg_stats_flatfield_image_config={CONFIG_PATH.joinpath('ctapipe_calculate_pixel_stats_flatfield_image.yaml')}",
|
|
31
|
+
f"--CamCalibTestDataTool.agg_stats_flatfield_peak_time_config={CONFIG_PATH.joinpath('ctapipe_calculate_pixel_stats_flatfield_peak_time.yaml')}",
|
|
32
|
+
],
|
|
33
|
+
cwd=tmp_path,
|
|
34
|
+
raises=True,
|
|
35
|
+
)
|
|
36
|
+
== 0
|
|
37
|
+
)
|