44Hillas-style moment-based shower image parametrization.
55"""
66
7+
78import astropy .units as u
89import numpy as np
910from astropy .coordinates import Angle
@@ -87,6 +88,7 @@ def hillas_parameters(geom, image):
8788 unit = geom .pix_x .unit
8889 pix_x = geom .pix_x .to_value (unit )
8990 pix_y = geom .pix_y .to_value (unit )
91+ pix_area = geom .pix_area .to_value (unit ** 2 )
9092 image = np .asanyarray (image , dtype = np .float64 )
9193
9294 if isinstance (image , np .ma .masked_array ):
@@ -131,22 +133,41 @@ def hillas_parameters(geom, image):
131133 # avoid divide by 0 warnings
132134 # psi will be consistently defined in the range (-pi/2, pi/2)
133135 if length == 0 :
134- psi = skewness_long = kurtosis_long = np .nan
136+ psi = (
137+ psi_uncert
138+ ) = transverse_cog_uncert = skewness_long = kurtosis_long = np .nan
135139 else :
136140 if vx != 0 :
137141 psi = np .arctan (vy / vx )
138142 else :
139143 psi = np .pi / 2
140144
141145 # calculate higher order moments along shower axes
142- longitudinal = delta_x * np .cos (psi ) + delta_y * np .sin (psi )
146+ cos_psi = np .cos (psi )
147+ sin_psi = np .sin (psi )
148+ longi = delta_x * cos_psi + delta_y * sin_psi
149+ trans = delta_x * - sin_psi + delta_y * cos_psi
143150
144- m3_long = np .average (longitudinal ** 3 , weights = image )
151+ m3_long = np .average (longi ** 3 , weights = image )
145152 skewness_long = m3_long / length ** 3
146153
147- m4_long = np .average (longitudinal ** 4 , weights = image )
154+ m4_long = np .average (longi ** 4 , weights = image )
148155 kurtosis_long = m4_long / length ** 4
149156
157+ # lsq solution to determine uncertainty on phi
158+ W = np .diag (image / pix_area )
159+ X = np .column_stack ([longi , np .ones_like (longi )])
160+ lsq_cov = np .linalg .inv (X .T @ W @ X )
161+ p = lsq_cov @ X .T @ W @ trans
162+ # p[0] is the psi angle in the rotated frame, which should be zero.
163+ # Now we add the non-zero residual psi angle in the rotated frame to psi uncertainty
164+ # We also add additional uncertainty to account for elongation of the image (i.e. width / length)
165+ psi_uncert = np .sqrt (lsq_cov [0 , 0 ] + p [0 ] * p [0 ]) * (
166+ 1.0 + pow (np .tan (width / length * np .pi / 2.0 ), 2 )
167+ )
168+ sin_p0 = np .sin (p [0 ])
169+ transverse_cog_uncert = np .sqrt (lsq_cov [1 , 1 ] * (1.0 + sin_p0 * sin_p0 ))
170+
150171 # Compute of the Hillas parameters uncertainties.
151172 # Implementation described in [hillas_uncertainties]_ This is an internal MAGIC document
152173 # not generally accessible.
@@ -190,6 +211,8 @@ def hillas_parameters(geom, image):
190211 width = u .Quantity (width , unit ),
191212 width_uncertainty = u .Quantity (width_uncertainty , unit ),
192213 psi = Angle (psi , unit = u .rad ),
214+ psi_uncertainty = Angle (psi_uncert , unit = u .rad ),
215+ transverse_cog_uncertainty = u .Quantity (transverse_cog_uncert , unit ),
193216 skewness = skewness_long ,
194217 kurtosis = kurtosis_long ,
195218 )
@@ -204,6 +227,8 @@ def hillas_parameters(geom, image):
204227 width = u .Quantity (width , unit ),
205228 width_uncertainty = u .Quantity (width_uncertainty , unit ),
206229 psi = Angle (psi , unit = u .rad ),
230+ psi_uncertainty = Angle (psi_uncert , unit = u .rad ),
231+ transverse_cog_uncertainty = u .Quantity (transverse_cog_uncert , unit ),
207232 skewness = skewness_long ,
208233 kurtosis = kurtosis_long ,
209234 )
0 commit comments