def fit_origin_crosses(self): '''calculates the origin of the gamma as the weighted average direction of the intersections of all great circles Returns ------- gamma : shape (3) numpy array direction of origin of the reconstructed shower as a 3D vector crossings : shape (n,3) list list of all the crossings of the `GreatCircle`s ''' crossings = [] for perm in combinations(self.circles.values(), 2): n1, n2 = perm[0].norm, perm[1].norm # cross product automatically weighs in the angle between # the two vectors: narrower angles have less impact, # perpendicular vectors have the most crossing = np.cross(n1, n2) # two great circles cross each other twice (one would be # the origin, the other one the direction of the gamma) it # doesn't matter which we pick but it should at least be # consistent: make sure to always take the "upper" solution if crossing[2] < 0: crossing *= -1 crossings.append(crossing * perm[0].weight * perm[1].weight) # averaging over the solutions of all permutations return linalg.normalise(np.sum(crossings, axis=0)) * u.dimless, crossings
def __init__(self, dirs, weight=1): '''the constructor takes two directions on the circle and creates the normal vector belonging to that plane Parameters ----------- dirs : shape (2,3) ndarray contains two 3D direction-vectors weight : float, optional weight of this telescope for later use during the reconstruction Algorithm: ---------- c : length 3 ndarray c = (a × b) × a -> a and c form an orthogonal base for the great circle (only orthonormal if a and b are of unit-length) norm : length 3 ndarray normal vector of the circle's plane, perpendicular to a, b and c ''' self.a = dirs[0] self.b = dirs[1] # a and c form an orthogonal basis for the great circle # not really necessary since the norm can be calculated # with a and b just as well self.c = np.cross(np.cross(self.a, self.b), self.a) # normal vector for the plane defined by the great circle self.norm = linalg.normalise(np.cross(self.a, self.c)) # some weight for this circle # (put e.g. uncertainty on the Hillas parameters # or number of PE in here) self.weight = weight
def fit_origin_crosses(self): '''calculates the origin of the gamma as the weighted average direction of the intersections of all great circles ''' if len(self.circles) < 2: raise TooFewTelescopesException( "need at least two telescopes, have {}" .format(len(self.circles))) crossings = [] for perm in combinations(self.circles.values(), 2): n1, n2 = perm[0].norm, perm[1].norm # cross product automatically weighs in the angle between # the two vectors: narrower angles have less impact, # perpendicular vectors have the most crossing = np.cross(n1, n2) # two great circles cross each other twice (one would be # the origin, the other one the direction of the gamma) it # doesn't matter which we pick but it should at least be # consistent: make sure to always take the "upper" # solution if crossing[2] < 0: crossing *= -1 crossings.append(crossing * perm[0].weight * perm[1].weight) # averaging over the solutions of all permutations return linalg.normalise(np.sum(crossings, axis=0)) * u.dimless, crossings
def fit_origin_crosses(self): '''calculates the origin of the gamma as the weighted average direction of the intersections of all great circles ''' if len(self.circles) < 2: raise TooFewTelescopesException( "need at least two telescopes, have {}".format( len(self.circles))) crossings = [] for perm in combinations(self.circles.values(), 2): n1, n2 = perm[0].norm, perm[1].norm # cross product automatically weighs in the angle between # the two vectors: narrower angles have less impact, # perpendicular vectors have the most crossing = np.cross(n1, n2) # two great circles cross each other twice (one would be # the origin, the other one the direction of the gamma) it # doesn't matter which we pick but it should at least be # consistent: make sure to always take the "upper" # solution if crossing[2] < 0: crossing *= -1 crossings.append(crossing * perm[0].weight * perm[1].weight) # averaging over the solutions of all permutations return linalg.normalise(np.sum(crossings, axis=0)) * u.dimless, crossings
def fit_core_minimise(self, seed=(0, 0), test_function=dist_to_traces): """ reconstructs the shower core position from the already set up great circles Notes ----- The core of the shower lies on the cross section of the great circle with the horizontal plane. The direction of this cross section is the cross-product of the circle's normal vector and the horizontal plane. Here, we only care about the direction; not the orientation... Parameters ---------- seed : tuple shape (2) tuple with optional starting coordinates tuple of floats or astropy.length -- if floats, assume metres test_function : function object, optional (default: dist_to_traces) function to be used by the minimiser """ if type(seed) == u.Quantity: unit = seed.unit else: unit = u.m # the direction of the cross section of the great circle with # the horizontal frame is the cross product of the great # circle's normal vector with the z-axis: # n × z = (n[1], -n[0], 0) for circle in self.circles.values(): circle.trace = linalg.normalise( np.array([circle.norm[1], -circle.norm[0], 0])) # minimising the test function (note: minimize strips seed off its # unit) self.fit_result_core = minimize(test_function, seed[:2], args=(self.circles), method='BFGS', options={'disp': False}) if not self.fit_result_core.success: print("fit_core: fit no success") return np.array(self.fit_result_core.x) * unit
def fit_origin_minimise(self, seed=(0, 0, 1), test_function=neg_angle_sum): ''' Fits the origin of the gamma with a minimisation procedure this function expects that :func:`get_great_circles<ctapipe.reco.FitGammaHillas.get_great_circles>` has been run already. A seed should be given otherwise it defaults to "straight up" supperted functions to minimise are an M-estimator and the negative sum of the angles to all normal vectors of the great circles Parameters ----------- seed : length-3 array starting point of the minimisation test_function : function object, optional (default: neg_angle_sum) either neg_angle_sum or MEst (or own implementation...) neg_angle_sum seemingly superior to MEst Returns ------- direction : length-3 numpy array as dimensionless quantity best fit for the origin of the gamma from the minimisation process ''' # using the sum of the cosines of each direction with every # other direction as weight; don't use the product -- with many # steep angles, the product will become too small and the # weight (and the whole fit) useless weights = [ np.sum([ linalg.length(np.cross(A.norm, B.norm)) for A in self.circles.values() ]) * B.weight for B in self.circles.values() ] # minimising the test function self.fit_result_origin = minimize(test_function, seed, args=(self.circles, weights), method='BFGS', options={'disp': False}) return np.array(linalg.normalise(self.fit_result_origin.x)) * u.dimless
def fit_core_minimise(self, seed=(0, 0), test_function=dist_to_traces): '''reconstructs the shower core position from the already set up great circles Notes ----- The core of the shower lies on the cross section of the great circle with the horizontal plane. The direction of this cross section is the cross-product of the circle's normal vector and the horizontal plane. Here, we only care about the direction; not the orientation... Parameters ---------- seed : tuple, optional (default: (0, 0)) shape (2) tuple with optional starting coordinates tuple of floats or astropy.length -- if floats, assume metres test_function : function object, optional (default: dist_to_traces) function to be used by the minimiser ''' if type(seed) == u.Quantity: unit = seed.unit else: unit = u.m # the direction of the cross section of the great circle with # the horizontal frame is the cross product of the great # circle's normal vector with the z-axis: # n × z = (n[1], -n[0], 0) for circle in self.circles.values(): circle.trace = linalg.normalise(np.array([circle.norm[1], -circle.norm[0], 0])) # minimising the test function (note: minimize strips seed off its # unit) self.fit_result_core = minimize(test_function, seed[:2], args=(self.circles), method='BFGS', options={'disp': False}) if not self.fit_result_core.success: print("fit_core: fit no success") return np.array(self.fit_result_core.x) * unit
def fit_origin_minimise(self, seed=[0, 0, 1], test_function=None): ''' fits the origin of the gamma with a minimisation procedure this function expects that get_great_circles has been run already a seed should be given otherwise it defaults to "straight up" supperted functions to minimise are an M-estimator and the negative sum of the angles to all normal vectors of the great circles Parameters: ----------- seed : length-3 array starting point of the minimisation test_function : member function if this class either _n_angle_sum or _MEst (or own implementation...) defaults to _n_angle_sum if none is given _n_angle_sum seemingly superior to _MEst Returns: -------- direction : length-3 numpy array as dimensionless quantity best fit for the origin of the gamma from the minimisation process ''' if test_function is None: test_function = self._n_angle_sum ''' using the sum of the cosines of each direction with every otherdirection as weight; don't use the product -- with many steep angles, the product will become too small and the weight (and the whole fit) useless ''' weights = [ np.sum([ linalg.length(np.cross(A.norm, B.norm)) for A in self.circles.values() ]) * B.weight for B in self.circles.values() ] ''' minimising the test function ''' self.fit_result_origin = minimize(test_function, seed, args=(self.circles, weights), method='BFGS', options={'disp': False}) return np.array(linalg.normalise(self.fit_result_origin.x)) * u.dimless
def fit_origin_minimise(self, seed=[0, 0, 1], test_function=None): ''' fits the origin of the gamma with a minimisation procedure this function expects that get_great_circles has been run already a seed should be given otherwise it defaults to "straight up" supperted functions to minimise are an M-estimator and the negative sum of the angles to all normal vectors of the great circles Parameters: ----------- seed : length-3 array starting point of the minimisation test_function : member function if this class either _n_angle_sum or _MEst (or own implementation...) defaults to _n_angle_sum if none is given _n_angle_sum seemingly superior to _MEst Returns: -------- direction : length-3 numpy array as dimensionless quantity best fit for the origin of the gamma from the minimisation process ''' if test_function is None: test_function = self._n_angle_sum ''' using the sum of the cosines of each direction with every otherdirection as weight; don't use the product -- with many steep angles, the product will become too small and the weight (and the whole fit) useless ''' weights = [np.sum([linalg.length(np.cross(A.norm, B.norm)) for A in self.circles.values()] ) * B.weight for B in self.circles.values()] ''' minimising the test function ''' self.fit_result_origin = minimize(test_function, seed, args=(self.circles, weights), method='BFGS', options={'disp': False} ) return np.array(linalg.normalise(self.fit_result_origin.x))*u.dimless
def fit_origin_minimise(self, seed=(0, 0, 1), test_function=neg_angle_sum): ''' Fits the origin of the gamma with a minimisation procedure this function expects that :func:`get_great_circles<ctapipe.reco.FitGammaHillas.get_great_circles>` has been run already. A seed should be given otherwise it defaults to "straight up" supperted functions to minimise are an M-estimator and the negative sum of the angles to all normal vectors of the great circles Parameters ----------- seed : length-3 array starting point of the minimisation test_function : function object, optional (default: neg_angle_sum) either neg_angle_sum or MEst (or own implementation...) neg_angle_sum seemingly superior to MEst Returns ------- direction : length-3 numpy array as dimensionless quantity best fit for the origin of the gamma from the minimisation process ''' # using the sum of the cosines of each direction with every # other direction as weight; don't use the product -- with many # steep angles, the product will become too small and the # weight (and the whole fit) useless weights = [np.sum([linalg.length(np.cross(A.norm, B.norm)) for A in self.circles.values()] ) * B.weight for B in self.circles.values()] # minimising the test function self.fit_result_origin = minimize(test_function, seed, args=(self.circles, weights), method='BFGS', options={'disp': False} ) return np.array(linalg.normalise(self.fit_result_origin.x)) * u.dimless
def fit_core(self, seed=(0 * u.m, 0 * u.m), test_function=dist_to_traces): '''reconstructs the shower core position from the already set up great circles .. note:: the core of the shower lies on the cross section of the great circle with the horizontal plane the direction of this cross section is the cross-product of the normal vectors of the circle and the horizontal plane here we only care about the direction; not the orientation... Parameters ---------- seed : python tuple * astropy quantity, optional shape (2) tuple with optional starting coordinates in test_function : function object, optional function to be used by the minimiser by default uses self._dist_to_traces ''' # the direction of the cross section of the great circle with # the horizontal frame is the cross product of the great # circle's normal vector with the z-axis zdir = np.array([0, 0, 1]) for circle in self.circles.values(): circle.trace = linalg.normalise(np.cross(circle.norm, zdir)) # minimising the test function self.fit_result_core = minimize(test_function, seed[:2] / u.m, args=(self.circles), method='BFGS', options={'disp': False}) if self.fit_result_core.success: return np.array(self.fit_result_core.x) * u.m else: print("fit no success") return np.array(self.fit_result_core.x) * u.m return np.array([np.nan] * 3) * u.m
def fit_core(self, seed=(0*u.m, 0*u.m), test_function=dist_to_traces): '''reconstructs the shower core position from the already set up great circles .. note:: the core of the shower lies on the cross section of the great circle with the horizontal plane the direction of this cross section is the cross-product of the normal vectors of the circle and the horizontal plane here we only care about the direction; not the orientation... Parameters ---------- seed : python tuple * astropy quantity, optional shape (2) tuple with optional starting coordinates in test_function : function object, optional function to be used by the minimiser by default uses self._dist_to_traces ''' # the direction of the cross section of the great circle with # the horizontal frame is the cross product of the great # circle's normal vector with the z-axis zdir = np.array([0, 0, 1]) for circle in self.circles.values(): circle.trace = linalg.normalise(np.cross(circle.norm, zdir)) # minimising the test function self.fit_result_core = minimize(test_function, seed[:2] / u.m, args=(self.circles), method='BFGS', options={'disp': False}) if self.fit_result_core.success: return np.array(self.fit_result_core.x) * u.m else: print("fit no success") return np.array(self.fit_result_core.x) * u.m return np.array([np.nan] * 3) * u.m
90 * u.deg).to(u.deg) length[k] = h.length width[k] = h.width for k, signal in { # 'p': pmt_signal_p, 'w': pmt_signal_w }.items(): h = hillas[k] p1_x = h.cen_x p1_y = h.cen_y p2_x = p1_x + h.length * np.cos(h.psi) p2_y = p1_y + h.length * np.sin(h.psi) T = linalg.normalise( np.array([(p1_x - p2_x) / u.m, (p1_y - p2_y) / u.m])) x = geom[k].pix_x y = geom[k].pix_y D = [p1_x - x, p1_y - y] dl = D[0] * T[0] + D[1] * T[1] dp = D[0] * T[1] - D[1] * T[0] for pe, pp in zip(signal[abs(dl) > 1 * hillas[k].length], dp[abs(dl) > 1 * hillas[k].length]): pe_vs_dp[k].fill([np.log10(sum_p), pp], pe) ''' do some plotting '''
def plot_perpendicular_hit_distribution(histogram_axis, image_axis, image_array, pixels_position, title): image_array = copy.deepcopy(image_array) ### #size_m = 0.2 # Size of the "phase space" in meter size_m = 0.2 # Size of the "phase space" in meter # # TODO: clean these following hard coded values for Astri # num_pixels_x = 40 # num_pixels_y = 40 # # x = np.linspace(-0.142555996776, 0.142555996776, num_pixels_x) # y = np.linspace(-0.142555996776, 0.142555996776, num_pixels_y) # # #x = np.arange(0, np.shape(ref_image_array)[0], 1) # TODO: wrong values -10 10 21 # #y = np.arange(0, np.shape(ref_image_array)[1], 1) # TODO: wrong values (30, ...) # # xx, yy = np.meshgrid(x, y) # print("delta x:", xx - pixels_position[0]) # print("delta y:", yy - pixels_position[1]) xx, yy = pixels_position[0], pixels_position[1] # Based on Tino's evaluate_cleaning.py (l. 277) hillas = hillas_parameters_1(xx.flatten() * u.meter, # TODO: essayer avec hillas param 2 !!! yy.flatten() * u.meter, image_array.flatten()) # [0] centroid = (hillas.cen_x, hillas.cen_y) length = hillas.length width = hillas.width angle = np.radians(hillas.psi) # - np.pi/2. # TODO print("centroid:", centroid) print("length:", length) print("width:", width) print("angle:", angle) ### # p1 = center of the ellipse p1_x = hillas.cen_x p1_y = hillas.cen_y #image_axis.scatter(p1_x, p1_y) # DEBUG plot # p2 = intersection between the ellipse and the shower track p2_x = p1_x + hillas.length * np.cos(angle) # hillas.psi + np.pi/2) p2_y = p1_y + hillas.length * np.sin(angle) # hillas.psi + np.pi/2) #image_axis.scatter(p2_x, p2_y) # DEBUG plot #image_axis.plot([p1_x, p2_x], [p1_y, p2_y]) # DEBUG plot print(p1_x, p2_x, p1_y, p2_y) # DEBUG plot d12_x = p1_x - p2_x d12_y = p1_y - p2_y print(d12_x, d12_y) # DEBUG plot # Slope of the shower track T = linalg.normalise(np.array([d12_x.value, d12_y.value])) # why a dedicated function ? if it's what I understand, it can easily be done on the fly #print("[p1_x-p2_x, p1_y-p2_y]:", [p1_x-p2_x, p1_y-p2_y]) #print("T:", T) #image_axis.plot([p1_x, p1_x*T[0]], [p1_y, p1_y*T[1]], "-g", linewidth=3) # DEBUG plot x = xx.flatten() y = yy.flatten() # Manhattan distance of pixels to the center of the ellipse # Translate (center) on P1 ? D = [p1_x.value-x, p1_y.value-y] # Pixels in the new base dl = D[0]*T[0] + D[1]*T[1] dp = D[0]*T[1] - D[1]*T[0] # nparray.ravel(): Return a flattened array. values, bins, patches = histogram_axis.hist(dp.ravel(), histtype='step', bins=np.linspace(-size_m, size_m, 31)) # -10 10 21 # TODO: scatter seulement les points qui sont dans le linspace de bins defini juste au dessus, faire apparaitre chaque bin d'une couleure différente ##image_axis.scatter(dl, dp, s=10, alpha=0.5) # DEBUG plot #image_axis.scatter(dl, dp, s=10, alpha=0.5) # DEBUG plot #image_axis.plot(dl.reshape(40, 40).T, dp.reshape(40, 40).T) # DEBUG plot #print(dl) #print(dp.shape) ### histogram_axis.set_xlim([-size_m, size_m]) histogram_axis.set_xlabel('Distance to the shower axis (m)', fontsize=14) histogram_axis.set_ylabel('Hits', fontsize=14) histogram_axis.set_title(title)
def plot_perpendicular_hit_distribution(histogram_axis, image_axis, image_array, pixels_position, title): image_array = copy.deepcopy(image_array) ### #size_m = 0.2 # Size of the "phase space" in meter size_m = 0.2 # Size of the "phase space" in meter # # TODO: clean these following hard coded values for Astri # num_pixels_x = 40 # num_pixels_y = 40 # # x = np.linspace(-0.142555996776, 0.142555996776, num_pixels_x) # y = np.linspace(-0.142555996776, 0.142555996776, num_pixels_y) # # #x = np.arange(0, np.shape(ref_image_array)[0], 1) # TODO: wrong values -10 10 21 # #y = np.arange(0, np.shape(ref_image_array)[1], 1) # TODO: wrong values (30, ...) # # xx, yy = np.meshgrid(x, y) # print("delta x:", xx - pixels_position[0]) # print("delta y:", yy - pixels_position[1]) xx, yy = pixels_position[0], pixels_position[1] # Based on Tino's evaluate_cleaning.py (l. 277) hillas = hillas_parameters_1( xx.flatten() * u.meter, # TODO: essayer avec hillas param 2 !!! yy.flatten() * u.meter, image_array.flatten()) # [0] centroid = (hillas.x, hillas.y) length = hillas.length width = hillas.width angle = np.radians(hillas.psi) # - np.pi/2. # TODO print("centroid:", centroid) print("length:", length) print("width:", width) print("angle:", angle) ### # p1 = center of the ellipse p1_x = hillas.x p1_y = hillas.y #image_axis.scatter(p1_x, p1_y) # DEBUG plot # p2 = intersection between the ellipse and the shower track p2_x = p1_x + hillas.length * np.cos(angle) # hillas.psi + np.pi/2) p2_y = p1_y + hillas.length * np.sin(angle) # hillas.psi + np.pi/2) #image_axis.scatter(p2_x, p2_y) # DEBUG plot #image_axis.plot([p1_x, p2_x], [p1_y, p2_y]) # DEBUG plot print(p1_x, p2_x, p1_y, p2_y) # DEBUG plot d12_x = p1_x - p2_x d12_y = p1_y - p2_y print(d12_x, d12_y) # DEBUG plot # Slope of the shower track T = linalg.normalise( np.array([d12_x.value, d12_y.value]) ) # why a dedicated function ? if it's what I understand, it can easily be done on the fly #print("[p1_x-p2_x, p1_y-p2_y]:", [p1_x-p2_x, p1_y-p2_y]) #print("T:", T) #image_axis.plot([p1_x, p1_x*T[0]], [p1_y, p1_y*T[1]], "-g", linewidth=3) # DEBUG plot x = xx.flatten() y = yy.flatten() # Manhattan distance of pixels to the center of the ellipse # Translate (center) on P1 ? D = [p1_x.value - x, p1_y.value - y] # Pixels in the new base dl = D[0] * T[0] + D[1] * T[1] dp = D[0] * T[1] - D[1] * T[0] # nparray.ravel(): Return a flattened array. values, bins, patches = histogram_axis.hist(dp.ravel(), histtype='step', bins=np.linspace( -size_m, size_m, 31)) # -10 10 21 # TODO: scatter seulement les points qui sont dans le linspace de bins defini juste au dessus, faire apparaitre chaque bin d'une couleure différente ##image_axis.scatter(dl, dp, s=10, alpha=0.5) # DEBUG plot #image_axis.scatter(dl, dp, s=10, alpha=0.5) # DEBUG plot #image_axis.plot(dl.reshape(40, 40).T, dp.reshape(40, 40).T) # DEBUG plot #print(dl) #print(dp.shape) ### histogram_axis.set_xlim([-size_m, size_m]) histogram_axis.set_xlabel('Distance to the shower axis (m)', fontsize=14) histogram_axis.set_ylabel('Hits', fontsize=14) histogram_axis.set_title(title)