Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hillas parameters in telescope frame #1591

Merged
merged 45 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
4028688
Cache files by full url/path
maxnoe Feb 2, 2021
7d83987
Fix part file not deleted on ctrl c
maxnoe Feb 2, 2021
b52bf0c
Add container for nominal reconstruction of hillas parameters
LukasNickel Feb 3, 2021
4000a08
Add method to calculate hillas parameters in the nominal frame
LukasNickel Feb 3, 2021
6aa2eb2
Remove second hillas parameters function
LukasNickel Feb 4, 2021
f554d8f
Add containers for nominal frame and fix hillas-related parameters
LukasNickel Feb 4, 2021
4f61b73
Use image parameters in nominal frame, bump data level version
LukasNickel Feb 4, 2021
3863add
Fix import
LukasNickel Feb 5, 2021
34f9d5f
Clean up container structure, use telescope frame for image parameters
LukasNickel Feb 5, 2021
9fb0481
Precompute camera geometries
LukasNickel Feb 5, 2021
058746a
Merge remote-tracking branch 'upstream/cache_url' into fov_hillas
LukasNickel Feb 5, 2021
9433e77
Merge branch 'master' of github.com:cta-observatory/ctapipe into fov_…
LukasNickel Feb 5, 2021
8f9a81a
Fix unit test
LukasNickel Feb 5, 2021
3bd826b
Rename lon/lat to fov_lon/fov_lat
LukasNickel Feb 6, 2021
8ded4e7
Fix use of ImageParametersContainer
LukasNickel Feb 7, 2021
2647f8a
Address codacy complaints
LukasNickel Feb 8, 2021
dbb0f23
Fix default ImageParametersContainer
LukasNickel Feb 8, 2021
b413f85
Change datamodel version
LukasNickel Feb 8, 2021
ecf61e1
Merge branch 'master' of github.com:cta-observatory/ctapipe into fov_…
LukasNickel Mar 31, 2021
f20d04a
Merge branch 'master' into fov_hillas
LukasNickel Jul 7, 2021
4dc2243
Use fov_lon/lat instead of x/y
LukasNickel Jul 9, 2021
0275401
Distinguish between CameraHillasParametersContainer and HillasParamet…
LukasNickel Jul 9, 2021
2b108b0
Adapt hillas intersection to work with hillas parameters in the
LukasNickel Jul 22, 2021
c03e5c0
Remove duplicated tests of the results
LukasNickel Jul 22, 2021
d6641e2
Remove unused declarations and imports
LukasNickel Jul 22, 2021
bfa7505
Fix datamodel version
LukasNickel Jul 23, 2021
ff648fb
Fix unit test
LukasNickel Jul 23, 2021
46ae892
Fix comparison to nan in unit tests
LukasNickel Jul 23, 2021
98b7794
Fix reading of dl1 files
LukasNickel Jul 23, 2021
6a0f5d4
Fix tests for the dl1eventsource
LukasNickel Jul 23, 2021
b281696
Write out correct datamodel version
LukasNickel Jul 23, 2021
ffb8de1
Fix unit test
LukasNickel Jul 23, 2021
54f2e06
Try to fix the docs by making the base containers private
LukasNickel Jul 23, 2021
19a30cc
Fix codacy complaints
LukasNickel Jul 23, 2021
52a46b6
Add base containers to __all__
LukasNickel Jul 23, 2021
958283f
Merge branch 'master' into fov_hillas
LukasNickel Aug 2, 2021
91fdeba
Add option to choose camera frame instead of telescope frame
LukasNickel Aug 2, 2021
42ce511
Remove unused import
LukasNickel Aug 2, 2021
25cca1a
Add missing underscore
LukasNickel Aug 2, 2021
a62e95f
Fix selection of camera frame geometry
LukasNickel Aug 2, 2021
85e0659
Fix test converting to wrong unit
LukasNickel Aug 2, 2021
6f7c5bc
Add missing variable name
LukasNickel Aug 2, 2021
dee99b0
Use traits for the selection of the frame
LukasNickel Aug 3, 2021
24755f3
Merge branch 'master' of github.com:cta-observatory/ctapipe into fov_…
LukasNickel Aug 3, 2021
2e33e00
Fix unit test
LukasNickel Aug 4, 2021
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
101 changes: 86 additions & 15 deletions ctapipe/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
"MonitoringCameraContainer",
"MonitoringContainer",
"MorphologyContainer",
"BaseHillasParametersContainer",
"CameraHillasParametersContainer",
"CameraTimingParametersContainer",
"ParticleClassificationContainer",
"PedestalContainer",
"PixelStatusContainer",
Expand All @@ -46,6 +49,7 @@
"SimulatedShowerDistribution",
"SimulationConfigContainer",
"TelEventIndexContainer",
"BaseTimingParametersContainer",
"TimingParametersContainer",
"TriggerContainer",
"WaveformCalibrationContainer",
Expand Down Expand Up @@ -107,11 +111,25 @@ class TelEventIndexContainer(Container):
tel_id = Field(0, "telescope identifier")


class HillasParametersContainer(Container):
container_prefix = "hillas"
class BaseHillasParametersContainer(Container):
"""
Base container for hillas parameters to
allow the CameraHillasParametersContainer to
be assigned to an ImageParametersContainer as well.
"""

intensity = Field(nan, "total intensity (size)")
skewness = Field(nan, "measure of the asymmetry")
kurtosis = Field(nan, "measure of the tailedness")


class CameraHillasParametersContainer(BaseHillasParametersContainer):
"""
Hillas Parameters in the camera frame. The cog position
is given in meter from the camera center.
"""

container_prefix = "camera_frame_hillas"
x = Field(nan * u.m, "centroid x coordinate", unit=u.m)
y = Field(nan * u.m, "centroid x coordinate", unit=u.m)
r = Field(nan * u.m, "radial coordinate of centroid", unit=u.m)
Expand All @@ -123,8 +141,33 @@ class HillasParametersContainer(Container):
width_uncertainty = Field(nan * u.m, "uncertainty of width", unit=u.m)
psi = Field(nan * u.deg, "rotation angle of ellipse", unit=u.deg)

skewness = Field(nan, "measure of the asymmetry")
kurtosis = Field(nan, "measure of the tailedness")

class HillasParametersContainer(BaseHillasParametersContainer):
"""
Hillas Parameters in a spherical system centered on the pointing position
(TelescopeFrame). The cog position is given as offset in
longitude and latitude in degree.
"""

container_prefix = "hillas"
fov_lon = Field(
nan * u.deg,
"longitude angle in a spherical system centered on the pointing position",
unit=u.deg,
)
fov_lat = Field(
nan * u.deg,
"latitude angle in a spherical system centered on the pointing position",
unit=u.deg,
)
r = Field(nan * u.deg, "radial coordinate of centroid", unit=u.deg)
phi = Field(nan * u.deg, "polar coordinate of centroid", unit=u.deg)

length = Field(nan * u.deg, "standard deviation along the major-axis", unit=u.deg)
length_uncertainty = Field(nan * u.deg, "uncertainty of length", unit=u.deg)
width = Field(nan * u.deg, "standard spread along the minor-axis", unit=u.deg)
width_uncertainty = Field(nan * u.deg, "uncertainty of width", unit=u.deg)
psi = Field(nan * u.deg, "rotation angle of ellipse", unit=u.deg)


class LeakageContainer(Container):
Expand Down Expand Up @@ -167,16 +210,13 @@ class ConcentrationContainer(Container):
pixel = Field(nan, "Percentage of photo-electrons in the brightest pixel")


class TimingParametersContainer(Container):
class BaseTimingParametersContainer(Container):
"""
Slope and Intercept of a linear regression of the arrival times
along the shower main axis
Base container for timing parameters to
allow the CameraTimingParametersContainer to
be assigned to an ImageParametersContainer as well.
"""

container_prefix = "timing"
slope = Field(
nan / u.m, "Slope of arrival times along main shower axis", unit=1 / u.m
)
intercept = Field(nan, "intercept of arrival times along main shower axis")
deviation = Field(
nan,
Expand All @@ -185,6 +225,31 @@ class TimingParametersContainer(Container):
)


class CameraTimingParametersContainer(BaseTimingParametersContainer):
"""
Slope and Intercept of a linear regression of the arrival times
along the shower main axis in the camera frame.
"""

container_prefix = "camera_frame_timing"
slope = Field(
nan / u.m, "Slope of arrival times along main shower axis", unit=1 / u.m
)


class TimingParametersContainer(BaseTimingParametersContainer):
"""
Slope and Intercept of a linear regression of the arrival times
along the shower main axis in a
spherical system centered on the pointing position (TelescopeFrame)
"""

container_prefix = "timing"
slope = Field(
nan / u.deg, "Slope of arrival times along main shower axis", unit=1 / u.deg
)
kosack marked this conversation as resolved.
Show resolved Hide resolved


class MorphologyContainer(Container):
""" Parameters related to pixels surviving image cleaning """

Expand Down Expand Up @@ -225,8 +290,16 @@ class ImageParametersContainer(Container):
""" Collection of image parameters """

container_prefix = "params"
hillas = Field(HillasParametersContainer(), "Hillas Parameters")
timing = Field(TimingParametersContainer(), "Timing Parameters")
hillas = Field(
HillasParametersContainer(),
"Hillas Parameters",
type=BaseHillasParametersContainer,
)
timing = Field(
TimingParametersContainer(),
"Timing Parameters",
type=BaseTimingParametersContainer,
)
leakage = Field(LeakageContainer(), "Leakage Parameters")
concentration = Field(ConcentrationContainer(), "Concentration Parameters")
morphology = Field(MorphologyContainer(), "Image Morphology Parameters")
Expand Down Expand Up @@ -260,15 +333,13 @@ class DL1CameraContainer(Container):
dtype=np.float32,
ndim=1,
)

image_mask = Field(
None,
"Boolean numpy array where True means the pixel has passed cleaning."
" Shape: (n_pixel, )",
dtype=np.bool_,
ndim=1,
)

parameters = Field(None, "Image parameters", type=ImageParametersContainer)


Expand Down
37 changes: 30 additions & 7 deletions ctapipe/image/concentration.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import numpy as np
import astropy.units as u

from ..instrument import CameraGeometry
from ..containers import ConcentrationContainer
from ..containers import (
ConcentrationContainer,
HillasParametersContainer,
CameraHillasParametersContainer,
)
from .hillas import camera_to_shower_coordinates
from ..instrument import CameraGeometry
from ..utils.quantities import all_to_value

__all__ = ["concentration_parameters"]
Expand All @@ -20,11 +24,30 @@ def concentration_parameters(geom: CameraGeometry, image, hillas_parameters):
"""

h = hillas_parameters
unit = h.x.unit

pix_x, pix_y, x, y, length, width, pixel_width = all_to_value(
geom.pix_x, geom.pix_y, h.x, h.y, h.length, h.width, geom.pixel_width, unit=unit
)
if isinstance(h, CameraHillasParametersContainer):
unit = h.x.unit
pix_x, pix_y, x, y, length, width, pixel_width = all_to_value(
geom.pix_x,
geom.pix_y,
h.x,
h.y,
h.length,
h.width,
geom.pixel_width,
unit=unit,
)
elif isinstance(h, HillasParametersContainer):
unit = h.fov_lon.unit
pix_x, pix_y, x, y, length, width, pixel_width = all_to_value(
geom.pix_x,
geom.pix_y,
h.fov_lon,
h.fov_lat,
h.length,
h.width,
geom.pixel_width,
unit=unit,
)

delta_x = pix_x - x
delta_y = pix_y - y
Expand Down
26 changes: 19 additions & 7 deletions ctapipe/image/hillas.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
import numpy as np
from astropy.coordinates import Angle
from astropy.units import Quantity
from ..containers import HillasParametersContainer
from ..containers import CameraHillasParametersContainer, HillasParametersContainer


HILLAS_ATOL = np.finfo(np.float64).eps


__all__ = [
"hillas_parameters",
"HillasParameterizationError",
]
__all__ = ["hillas_parameters", "HillasParameterizationError"]


def camera_to_shower_coordinates(x, y, cog_x, cog_y, psi):
Expand Down Expand Up @@ -198,9 +195,24 @@ def hillas_parameters(geom, image):
np.sum(((((b * A) + (a * B) + (-c * C))) ** 2.0) * image)
) / (2 * width)

if unit.is_equivalent(u.m):
return CameraHillasParametersContainer(
x=u.Quantity(cog_x, unit),
y=u.Quantity(cog_y, unit),
r=u.Quantity(cog_r, unit),
phi=Angle(cog_phi, unit=u.rad),
intensity=size,
length=u.Quantity(length, unit),
length_uncertainty=u.Quantity(length_uncertainty, unit),
width=u.Quantity(width, unit),
width_uncertainty=u.Quantity(width_uncertainty, unit),
psi=Angle(psi, unit=u.rad),
skewness=skewness_long,
kurtosis=kurtosis_long,
)
return HillasParametersContainer(
x=u.Quantity(cog_x, unit),
y=u.Quantity(cog_y, unit),
fov_lon=u.Quantity(cog_x, unit),
LukasNickel marked this conversation as resolved.
Show resolved Hide resolved
fov_lat=u.Quantity(cog_y, unit),
r=u.Quantity(cog_r, unit),
phi=Angle(cog_phi, unit=u.rad),
intensity=size,
Expand Down
30 changes: 22 additions & 8 deletions ctapipe/image/image_processor.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"""
High level image processing (ImageProcessor Component)
"""
from ctapipe.coordinates import TelescopeFrame
import numpy as np


from ..containers import (
ArrayEventContainer,
ImageParametersContainer,
IntensityStatisticsContainer,
PeakTimeStatisticsContainer,
ImageParametersContainer,
TimingParametersContainer,
PeakTimeStatisticsContainer,
)
from ..core import QualityQuery, TelescopeComponent
from ..core.traits import List, create_class_enum_trait
Expand All @@ -25,6 +25,7 @@
)


# avoid use of base containers for unparameterized images
DEFAULT_IMAGE_PARAMETERS = ImageParametersContainer()
DEFAULT_TRUE_IMAGE_PARAMETERS = ImageParametersContainer()
DEFAULT_TRUE_IMAGE_PARAMETERS.intensity_statistics = IntensityStatisticsContainer(
Expand Down Expand Up @@ -64,6 +65,7 @@ def __init__(
self,
subarray: SubarrayDescription,
is_simulation,
use_telescope_frame=True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a Traitlet, not a construction option. Otherwise it is not configurable. The default should be set to True.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will not change the data model - it is only a configuration option, and the current data model supports both units in the output. However, we may in the future deprecate the CameraFrame one and clean up all that code as I said before, but now we want to be able to run the full analysis twice on a large dataset and ensure they results are compatible with both frames.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we are at it, please also remove the is_simulation option. It should not be needed. Code that needs to check that should just check if event.simulation is None

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, not really connected. Will just make a PR

config=None,
parent=None,
**kwargs,
Expand Down Expand Up @@ -93,6 +95,15 @@ def __init__(
)
self.check_image = ImageQualityQuery(parent=self)
self._is_simulation = is_simulation
self._use_telescope_frame = use_telescope_frame
if self.use_telescope_frame:
telescope_frame = TelescopeFrame()
self.telescope_frame_geometries = {
tel_id: self.subarray.tel[tel_id].camera.geometry.transform_to(
telescope_frame
)
for tel_id in self.subarray.tel
}

def __call__(self, event: ArrayEventContainer):
self._process_telescope_event(event)
Expand All @@ -102,11 +113,11 @@ def _parameterize_image(
tel_id,
image,
signal_pixels,
geometry,
peak_time=None,
default=DEFAULT_IMAGE_PARAMETERS,
) -> ImageParametersContainer:
"""Apply image cleaning and calculate image features

Parameters
----------
tel_id: int
Expand All @@ -117,15 +128,12 @@ def _parameterize_image(
image mask
peak_time: np.ndarray
peak time image

Returns
-------
ImageParametersContainer:
cleaning mask, parameters
"""

tel = self.subarray.tel[tel_id]
geometry = tel.camera.geometry
image_selected = image[signal_pixels]

# check if image can be parameterized:
Expand Down Expand Up @@ -184,9 +192,13 @@ def _process_telescope_event(self, event):
"""
Loop over telescopes and process the calibrated images into parameters
"""

for tel_id, dl1_camera in event.dl1.tel.items():

if self.use_telescope_frame:
# Use the transformed geometries
geometry = self.telescope_frame_geometries[tel_id]
else:
geometry = dl1_camera.geometry
# compute image parameters only if requested to write them
dl1_camera.image_mask = self.clean(
tel_id=tel_id,
Expand All @@ -199,6 +211,7 @@ def _process_telescope_event(self, event):
image=dl1_camera.image,
signal_pixels=dl1_camera.image_mask,
peak_time=dl1_camera.peak_time,
geometry=geometry,
)

self.log.debug("params: %s", dl1_camera.parameters.as_dict(recursive=True))
Expand All @@ -212,6 +225,7 @@ def _process_telescope_event(self, event):
tel_id,
image=sim_camera.true_image,
signal_pixels=sim_camera.true_image > 0,
geometry=geometry,
peak_time=None, # true image from simulation has no peak time
default=DEFAULT_TRUE_IMAGE_PARAMETERS,
)
Expand Down
Loading