Skip to content

Commit 12a32c2

Browse files
burmist-gitParsonsRD
authored andcommitted
Fix muon normalization for different pixel shapes (cta-observatory#2865)
* Add a function to calculate the trivial solution for the number of photons (from muon) incident on the telescope mirror. * Add the PixelShape. For image_prediction, we fixed the normalization factor in this way. * add Add the PixelShape * Add test for normalisation factor * Enhance the chord_length function by applying modulo two pi * Change the parameter rho to absolute values instead of values relative to the mirror radius. * Add phi0 and change phi signe. * Add phi0. * Documentation update. Add desctiption of phi0. * Remove uncovered code. * Removing unnecessary sign verification and correction. * Add a wrapper to preserve the functional API. * from camel case to snake case * from camel case to snake case * add the bugfix changelog statement * Add the bugfix changelog statement - for chord calculation * Get back to initail intersect_circle API * swap phi and phi0 * remove : Fix the modulo 2 pi for chord calculation. * white space * Prune unrelated elements to pixel shape normalization in the fitter. * Prune unrelated elements to pixel shape normalization in the test * renormalise rho * renames: 2845.bugfix.rst -> 2865.bugfix.rst * deleted : ../docs/changes/2845.bugfix.rst
1 parent 106c3c7 commit 12a32c2

3 files changed

Lines changed: 103 additions & 4 deletions

File tree

docs/changes/2865.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix the normalization for muon analysis to account for different PixelShapes.

src/ctapipe/image/muon/intensity_fitter.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ...core.env import CTAPIPE_DISABLE_NUMBA_CACHE
1818
from ...core.traits import FloatTelescopeParameter, IntTelescopeParameter
1919
from ...exceptions import OptionalDependencyMissing
20+
from ...instrument.camera.geometry import PixelShape
2021
from ..pixel_likelihood import neg_log_likelihood_approx
2122

2223
try:
@@ -31,6 +32,9 @@
3132
# ratio of the areas of the unit circle and a square of side lengths 2
3233
CIRCLE_SQUARE_AREA_RATIO = np.pi / 4
3334

35+
# ratio of the areas of the unit circle and a hexagon of flat-to-flat lengths 2
36+
CIRCLE_HEXAGON_AREA_RATIO = np.pi / 2 / np.sqrt(3)
37+
3438
# Sqrt of 2, as it is needed multiple times
3539
SQRT2 = np.sqrt(2)
3640

@@ -176,6 +180,7 @@ def image_prediction(
176180
oversampling=3,
177181
min_lambda=300 * u.nm,
178182
max_lambda=600 * u.nm,
183+
pix_type=PixelShape.HEXAGON,
179184
):
180185
"""
181186
Predict muon ring image from given parameters.
@@ -217,6 +222,7 @@ def image_prediction(
217222
oversampling=oversampling,
218223
min_lambda_m=min_lambda.to_value(u.m),
219224
max_lambda_m=max_lambda.to_value(u.m),
225+
pix_type=pix_type,
220226
)
221227

222228

@@ -257,6 +263,7 @@ def image_prediction_no_units(
257263
oversampling=3,
258264
min_lambda_m=300e-9,
259265
max_lambda_m=600e-9,
266+
pix_type=PixelShape.HEXAGON,
260267
):
261268
"""
262269
Unit-less version of `image_prediction`.
@@ -317,7 +324,12 @@ def image_prediction_no_units(
317324
# diameter. In any case, since in the end we do a data-MC comparison of the muon
318325
# ring analysis outputs, it is not critical that this value is exact.
319326

320-
pred *= CIRCLE_SQUARE_AREA_RATIO
327+
if pix_type == PixelShape.HEXAGON:
328+
pred *= CIRCLE_HEXAGON_AREA_RATIO
329+
elif pix_type == PixelShape.SQUARE:
330+
pred *= CIRCLE_SQUARE_AREA_RATIO
331+
elif pix_type == PixelShape.CIRCLE:
332+
return pred
321333

322334
return pred
323335

@@ -333,6 +345,7 @@ def build_negative_log_likelihood(
333345
spe_width,
334346
pedestal,
335347
hole_radius=0 * u.m,
348+
pix_type=PixelShape.HEXAGON,
336349
):
337350
"""Create an efficient negative log_likelihood function that does
338351
not rely on astropy units internally by defining needed values as closures
@@ -414,6 +427,7 @@ def negative_log_likelihood(
414427
oversampling=oversampling,
415428
min_lambda_m=min_lambda,
416429
max_lambda_m=max_lambda,
430+
pix_type=pix_type,
417431
)
418432

419433
# scale prediction by optical efficiency of the telescope
@@ -535,6 +549,7 @@ def __call__(self, tel_id, center_x, center_y, radius, image, pedestal, mask=Non
535549
spe_width=self.spe_width.tel[tel_id],
536550
pedestal=pedestal,
537551
hole_radius=self.hole_radius_m.tel[tel_id] * u.m,
552+
pix_type=telescope.camera.geometry.pix_type,
538553
)
539554
negative_log_likelihood.errordef = Minuit.LIKELIHOOD
540555

src/ctapipe/image/muon/tests/test_intensity_fit.py

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import astropy.units as u
44
import numpy as np
55
import pytest
6+
from scipy.constants import alpha
67

78
parameter_names = [
89
"radius",
@@ -24,19 +25,19 @@
2425
),
2526
Parameters(
2627
radius=12,
27-
rho=1.0,
28+
rho=1,
2829
phi=90.0 * u.deg,
2930
expected_length=0,
3031
),
3132
Parameters(
3233
radius=12,
33-
rho=3.0,
34+
rho=1.1,
3435
phi=180.0 * u.deg,
3536
expected_length=0,
3637
),
3738
Parameters(
3839
radius=12,
39-
rho=2.0,
40+
rho=2,
4041
phi=0.0 * u.deg,
4142
expected_length=24,
4243
),
@@ -102,6 +103,7 @@ def test_muon_efficiency_fit(prod5_lst, reference_location):
102103
pixel_x=x,
103104
pixel_y=y,
104105
pixel_diameter=pixel_diameter,
106+
pix_type=telescope.camera.geometry.pix_type,
105107
)
106108

107109
result = fitter(
@@ -145,3 +147,84 @@ def test_scts(prod5_sst, reference_location):
145147
image=np.zeros(telescope.camera.geometry.n_pixels),
146148
pedestal=np.zeros(telescope.camera.geometry.n_pixels),
147149
)
150+
151+
152+
def test_normalisation_factor(prod5_lst, reference_location):
153+
"""Test of the absolute normalization factor."""
154+
from ctapipe.coordinates import TelescopeFrame
155+
from ctapipe.image.muon.intensity_fitter import (
156+
image_prediction,
157+
)
158+
159+
pytest.importorskip("iminuit")
160+
161+
telescope = prod5_lst
162+
163+
geom = telescope.camera.geometry.transform_to(TelescopeFrame())
164+
mirror_radius = np.sqrt(telescope.optics.mirror_area / np.pi)
165+
166+
pixel_diameter = geom.pixel_width[0]
167+
x = geom.pix_x
168+
y = geom.pix_y
169+
170+
image = image_prediction(
171+
mirror_radius,
172+
hole_radius=0 * u.m,
173+
impact_parameter=0 * u.m,
174+
phi=0 * u.rad,
175+
center_x=0.0 * u.deg,
176+
center_y=0.0 * u.deg,
177+
radius=1.1 * u.deg,
178+
ring_width=0.05 * u.deg,
179+
pixel_x=x,
180+
pixel_y=y,
181+
pixel_diameter=pixel_diameter,
182+
oversampling=3,
183+
min_lambda=300 * u.nm,
184+
max_lambda=600 * u.nm,
185+
pix_type=telescope.camera.geometry.pix_type,
186+
)
187+
188+
measured = np.sum(image)
189+
expected = expected_nphot(
190+
r_mirror=mirror_radius,
191+
theta_cher=1.1 * u.deg,
192+
lambda_min=300 * u.nm,
193+
lambda_max=600 * u.nm,
194+
)
195+
196+
assert u.isclose(measured, expected, rtol=0.02)
197+
198+
199+
def expected_nphot(r_mirror, theta_cher, lambda_min, lambda_max):
200+
"""
201+
The trivial solution for the number of photons incident on the telescope mirror.
202+
203+
It is a trivial case, since we assume a muon impact at the center of the dish,
204+
with no shadowing and a constant Cherenkov angle.
205+
We neglect the light yield attenuation due to atmospheric absorption.
206+
207+
Parameters
208+
----------
209+
Rmirror: quantity[length]
210+
mirror radius
211+
theta_cher: quantity[angle]
212+
Cherenkov angle
213+
lambda_min: quantity[length]
214+
photon wavelength
215+
lambda_max: quantity[length]
216+
photon wavelength
217+
218+
Returns
219+
-------
220+
float: number of Cherenkov photons
221+
222+
"""
223+
224+
return (
225+
np.pi
226+
* alpha
227+
* r_mirror.to_value(u.m)
228+
* np.sin(2 * theta_cher)
229+
* (lambda_min.to_value(u.m) ** -1 - lambda_max.to_value(u.m) ** -1)
230+
)

0 commit comments

Comments
 (0)