예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
    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
예제 #8
0
    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
예제 #9
0
    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
예제 #10
0
    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
예제 #11
0
    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
예제 #12
0
    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
예제 #13
0
    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
예제 #14
0
                                   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)