Skip to content
Draft
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
2 changes: 2 additions & 0 deletions src/ctapipe/calib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
"""

from .camera import CameraCalibrator, GainSelector
from .optics import PointingCalibrator

__all__ = [
"CameraCalibrator",
"GainSelector",
"PointingCalibrator",
]
3 changes: 3 additions & 0 deletions src/ctapipe/calib/optics/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .calibrator import PointingCalibrator

__all__ = ["PointingCalibrator"]
86 changes: 86 additions & 0 deletions src/ctapipe/calib/optics/calibrator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import astropy.units as u
import numpy as np

from ...containers import TelescopePointingContainer
from ...core import TelescopeComponent


class PointingCalibrator(TelescopeComponent):
"""
Calibrates telescope pointing by evaluating pre-interpolated structural
pointing and structural displacement containers from event monitoring data.
"""

def __call__(self, event) -> None:
"""
Calibrate pointing for all triggered telescopes in an event.

Parameters
----------
event : ctapipe.containers.DataContainer
The event to calibrate.
"""
for tel_id in event.trigger.tels_with_trigger:
if tel_id not in event.monitoring.tel:
continue

mon = event.monitoring.tel[tel_id]
pointing_container = self._apply_structure_displacement(mon)
if pointing_container is None:
self.log.warning(
"Pointing calibration failed for telescope %s. "
"Skipping pointing calibration for this telescope.",
tel_id,
)
continue
event.monitoring.tel[tel_id].pointing = pointing_container

def _apply_structure_displacement(self, mon_tel) -> TelescopePointingContainer:
"""
Apply structural displacement to raw structure pointing.
"""
raw_pointing = mon_tel.structure_pointing
displacement = mon_tel.structure_displacement
if raw_pointing is None:
self.log.warning("No structure pointing data available.")
return None

Check warning on line 46 in src/ctapipe/calib/optics/calibrator.py

View check run for this annotation

CTAO Sonarqube / SonarQube Code Analysis

Return a value of type "TelescopePointingContainer" instead of "NoneType" or update function "_apply_structure_displacement" type hint.

[S5886] Function return types should be consistent with their type hint See more on https://sonar-ctao.zeuthen.desy.de/project/issues?id=cta-observatory_ctapipe_6122e87b-83f3-4db1-8287-457e752adf01&pullRequest=3066&issues=33d6c521-9c2f-4dd6-9e0f-a5661968c883&open=33d6c521-9c2f-4dd6-9e0f-a5661968c883
if displacement is None:
self.log.warning("No structure displacement data available.")
return None

Check warning on line 49 in src/ctapipe/calib/optics/calibrator.py

View check run for this annotation

CTAO Sonarqube / SonarQube Code Analysis

Return a value of type "TelescopePointingContainer" instead of "NoneType" or update function "_apply_structure_displacement" type hint.

[S5886] Function return types should be consistent with their type hint See more on https://sonar-ctao.zeuthen.desy.de/project/issues?id=cta-observatory_ctapipe_6122e87b-83f3-4db1-8287-457e752adf01&pullRequest=3066&issues=e57a8ed6-3766-42be-954a-08c75188d486&open=e57a8ed6-3766-42be-954a-08c75188d486

# Combine raw encoder positions with structural displacement offsets
alt_corr = raw_pointing.altitude + displacement.delta_altitude
az_corr = (raw_pointing.azimuth + displacement.delta_azimuth) % (
2 * np.pi * u.rad
)

return TelescopePointingContainer(
azimuth=az_corr,
altitude=alt_corr,
)

def _apply_camera_displacement(self, event, tel_id):
"""
Apply the camera displacement to the pointing of the telescope.

Parameters
----------
event: ctapipe.containers.DataContainer
The event to calibrate.
tel_id: int
The telescope ID to calibrate.
"""
raise NotImplementedError("_apply_camera_displacement is not yet implemented.")

def _apply_pointing_correction(self, event, tel_id):
"""
Apply the pointing correction to the pointing of the telescope.

Parameters
----------
event: ctapipe.containers.DataContainer
The event to calibrate.
tel_id: int
The telescope ID to calibrate.
"""
raise NotImplementedError("_apply_pointing_correction is not yet implemented.")
Empty file.
137 changes: 137 additions & 0 deletions src/ctapipe/calib/optics/tests/test_calibrator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import astropy.units as u
import pytest

from ctapipe.calib.optics.calibrator import PointingCalibrator
from ctapipe.containers import (
ArrayEventContainer,
TelescopeStructureDisplacementContainer,
TelescopeStructurePointingContainer,
)


@pytest.fixture
def pointing_calibrator(example_subarray):
return PointingCalibrator(subarray=example_subarray)


def test_apply_structure_displacement(pointing_calibrator):
event = ArrayEventContainer()
tel_id = 1
event.trigger.tels_with_trigger = [tel_id]

raw_pointing = TelescopeStructurePointingContainer(
azimuth=0.3 * u.rad,
altitude=0.7 * u.rad,
)
displacement = TelescopeStructureDisplacementContainer(
delta_azimuth=0.2 * u.rad,
delta_altitude=-0.1 * u.rad,
)

event.monitoring.tel[tel_id].structure_pointing = raw_pointing
event.monitoring.tel[tel_id].structure_displacement = displacement

pointing_calibrator(event)

calibrated = event.monitoring.tel[tel_id].pointing
assert calibrated is not None
assert u.isclose(calibrated.azimuth, 0.5 * u.rad)
assert u.isclose(calibrated.altitude, 0.6 * u.rad)


def test_apply_structure_displacement_wraps_azimuth(pointing_calibrator):
event = ArrayEventContainer()
tel_id = 2
event.trigger.tels_with_trigger = [tel_id]

raw_pointing = TelescopeStructurePointingContainer(
azimuth=(2 * 3.141592653589793 - 0.2) * u.rad,
altitude=1.0 * u.rad,
)
displacement = TelescopeStructureDisplacementContainer(
delta_azimuth=0.3 * u.rad,
delta_altitude=0.0 * u.rad,
)

event.monitoring.tel[tel_id].structure_pointing = raw_pointing
event.monitoring.tel[tel_id].structure_displacement = displacement

pointing_calibrator(event)

calibrated = event.monitoring.tel[tel_id].pointing
assert calibrated is not None
assert u.isclose(calibrated.azimuth, 0.1 * u.rad)
assert u.isclose(calibrated.altitude, 1.0 * u.rad)


def test_missing_structure_pointing_logs_warning(pointing_calibrator, caplog):
event = ArrayEventContainer()
tel_id = 3
event.trigger.tels_with_trigger = [tel_id]

event.monitoring.tel[
tel_id
].structure_displacement = TelescopeStructureDisplacementContainer(
delta_azimuth=0.1 * u.rad,
delta_altitude=0.0 * u.rad,
)

pointing_calibrator(event)

assert event.monitoring.tel[tel_id].pointing is None
assert "No structure pointing data available." in caplog.text


def test_missing_structure_displacement_logs_warning(pointing_calibrator, caplog):
event = ArrayEventContainer()
tel_id = 4
event.trigger.tels_with_trigger = [tel_id]

event.monitoring.tel[
tel_id
].structure_pointing = TelescopeStructurePointingContainer(
azimuth=0.1 * u.rad,
altitude=0.2 * u.rad,
)

pointing_calibrator(event)

assert event.monitoring.tel[tel_id].pointing is None
assert "No structure displacement data available." in caplog.text


def test_missing_tel_in_monitoring_is_skipped(pointing_calibrator):
event = ArrayEventContainer()
tel_id = 5
event.trigger.tels_with_trigger = [tel_id]

pointing_calibrator(event)

assert tel_id not in event.monitoring.tel


def test_only_matching_telescopes_are_calibrated(pointing_calibrator):
event = ArrayEventContainer()
tels = [1, 2]
event.trigger.tels_with_trigger = tels

for tel_id in tels:
event.monitoring.tel[
tel_id
].structure_pointing = TelescopeStructurePointingContainer(
azimuth=0.1 * u.rad,
altitude=0.2 * u.rad,
)
event.monitoring.tel[
tel_id
].structure_displacement = TelescopeStructureDisplacementContainer(
delta_azimuth=0.0 * u.rad,
delta_altitude=0.0 * u.rad,
)

event.monitoring.tel[2].structure_pointing = None

pointing_calibrator(event)

assert event.monitoring.tel[1].pointing is not None
assert event.monitoring.tel[2].pointing is None
17 changes: 10 additions & 7 deletions src/ctapipe/tools/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

from tqdm.auto import tqdm

from ..calib import CameraCalibrator, GainSelector
from ..core import QualityQuery, Tool, ToolConfigurationError
from ..calib import CameraCalibrator, GainSelector, PointingCalibrator
from ..core import QualityQuery, Tool
from ..core.traits import Bool, ComponentName, List, classes_with_traits, flag
from ..exceptions import InputMissing
from ..image import ImageCleaner, ImageModifier, ImageProcessor, WaveformModifier
Expand Down Expand Up @@ -233,16 +233,16 @@
f"Please make sure the '{mon_source_name}' and its input "
f"are suitable for calibrating the data you are processing."
)
self.log.critical(msg)
raise ToolConfigurationError(msg)
# Append the monitoring source to the list if it has compatible monitoring types
self.log.warning(msg)
# Append the monitoring source to the list
self._monitoring_sources.append(mon_source)

if self.add_nsb_in_waveforms:
self.waveform_modifier = WaveformModifier(parent=self, subarray=subarray)

self.software_trigger = SoftwareTrigger(parent=self, subarray=subarray)
self.calibrate = CameraCalibrator(parent=self, subarray=subarray)
self.calibrate_camera = CameraCalibrator(parent=self, subarray=subarray)
self.calibrate_pointing = PointingCalibrator(parent=self, subarray=subarray)
self.process_images = ImageProcessor(subarray=subarray, parent=self)
self.process_shower = ShowerProcessor(
subarray=subarray,
Expand Down Expand Up @@ -333,7 +333,7 @@
append=True,
)

def start(self):

Check failure on line 336 in src/ctapipe/tools/process.py

View check run for this annotation

CTAO Sonarqube / SonarQube Code Analysis

Refactor this function to reduce its Cognitive Complexity from 20 to the 15 allowed.

[S3776] Cognitive Complexity of functions should not be too high See more on https://sonar-ctao.zeuthen.desy.de/project/issues?id=cta-observatory_ctapipe_6122e87b-83f3-4db1-8287-457e752adf01&pullRequest=3066&issues=e3294470-a5a1-425d-9a47-840eb0c61174&open=e3294470-a5a1-425d-9a47-840eb0c61174
"""
Process events
"""
Expand Down Expand Up @@ -369,7 +369,10 @@
mon_source.fill_monitoring_container(event)

if self.should_calibrate:
self.calibrate(event)
self.calibrate_camera(event)
# Pointing calibration is only applied to real data, not simulations
if not self.event_source.metadata["is_simulation"]:
self.calibrate_pointing(event)

if self.should_compute_dl1:
self.process_images(event)
Expand Down
Loading