Module sipmarray.array

Expand source code
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import PatchCollection
from matplotlib.patches import Circle, Rectangle

from sipmarray.unit import SiPMunit


class SiPMarray():
    """Class to represent a SiPM array.
    """
    def __init__(self, array_diameter: float,
                 border_margin:float, sipm_model:str):
        """SiPMarray class

        Args:
            array_diameter (float): diameter of the array
            border_margin (float): margin between the sipms and the array border
            sipm_model (str): model of the sipm to use
        """
        
        self.array_diameter = array_diameter
        self.border_margin = border_margin
        self.sipmunit = self.load_sipmunit(sipm_model)
        
        corner_meshes = self.make_corners()
        self.cut_outside_array(corner_meshes)
        self.n_sipms = self.A_corners_xx.count()

        self.total_array_area = np.pi * (self.array_diameter/2)**2
        self.total_sipm_area = self.n_sipms * self.sipmunit.total_area
        self.total_sipm_active_area = self.n_sipms * self.sipmunit.active_area
        self.sipm_coverage = self.total_sipm_active_area/self.total_array_area


    def make_corners(self) -> tuple:
        """Define where the corners of the sipms are

        Returns:
            tuple: (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
                        C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy)
        """
        
        # make the center a not
        D_corner_x = np.arange(0,self.array_diameter/2 + self.sipmunit.width_unit, self.sipmunit.width_unit)
        D_corner_y = np.arange(0,self.array_diameter/2 +  self.sipmunit.height_unit, self.sipmunit.height_unit)

        D_corner_x = np.concatenate([-np.flip(D_corner_x[1:]),D_corner_x])
        D_corner_y = np.concatenate([-np.flip(D_corner_y[1:]),D_corner_y])

        D_corner_xx, D_corner_yy = np.meshgrid(D_corner_x, D_corner_y, indexing = 'ij') 

        A_corner_xx = D_corner_xx
        A_corner_yy = B_corner_yy = D_corner_yy + self.sipmunit.height_unit

        B_corner_xx = C_corner_xx = D_corner_xx + self.sipmunit.width_unit
        C_corner_yy = D_corner_yy
        
        return (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
                C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy)
    
    def cut_outside_array(self, corner_meshes:tuple):
        """Mask the sipms that are outside the array in a masked array.

        Args:
            corner_meshes (tuple): tuple with all the corner meshes
        """        
        (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
                C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy) = corner_meshes
        D_corner_rr = np.sqrt(D_corner_xx**2 + D_corner_yy**2)

        A_corner_rr = np.sqrt(A_corner_xx**2 + A_corner_yy**2)
        B_corner_rr = np.sqrt(B_corner_xx**2 + B_corner_yy**2)
        C_corner_rr = np.sqrt(C_corner_xx**2 + C_corner_yy**2)
        
        A_mask_inside_array_rr = A_corner_rr < self.array_diameter/2 - self.border_margin
        B_mask_inside_array_rr = B_corner_rr < self.array_diameter/2 - self.border_margin
        C_mask_inside_array_rr = C_corner_rr < self.array_diameter/2 - self.border_margin
        D_mask_inside_array_rr = D_corner_rr < self.array_diameter/2 - self.border_margin
        
        merged_mask = (~A_mask_inside_array_rr | 
                       ~B_mask_inside_array_rr | 
                       ~C_mask_inside_array_rr | 
                       ~D_mask_inside_array_rr)
        
        self.D_corners_xx = np.ma.masked_array(D_corner_xx, mask= merged_mask)
        self.D_corners_yy = np.ma.masked_array(D_corner_yy, mask= merged_mask)

        self.A_corners_xx = np.ma.masked_array(A_corner_xx, mask= merged_mask)
        self.A_corners_yy = np.ma.masked_array(A_corner_yy, mask= merged_mask)

        self.B_corners_xx = np.ma.masked_array(B_corner_xx, mask= merged_mask)
        self.B_corners_yy = np.ma.masked_array(B_corner_yy, mask= merged_mask)

        self.C_corners_xx = np.ma.masked_array(C_corner_xx, mask= merged_mask)
        self.C_corners_yy = np.ma.masked_array(C_corner_yy, mask= merged_mask)
    
    def load_sipmunit(self, model: str):
        """Load the SiPM unit.

        Args:
            model (str): name of the SiPM model

        Returns:
            SiPMunit: a SiPM unit class object
        """
        return SiPMunit(model=model)
    
    def get_centres(self, active_area: bool = True):
        """Get centres of the SiPMs.

        Args:
            active_area (bool, optional): Returns the centres of teh 
                active areas if true, otherwise the geometric centres of 
                the packaging if false. Defaults to True.
        Retuns:
            list: list of the centres of the SiPMs
        """
        if active_area:
            (x_sipm_centre, y_sipm_centre) = self.sipmunit.get_unit_active_centre()
        else:
            (x_sipm_centre, y_sipm_centre) = self.sipmunit.get_unit_centre()
        
        D_corners_x_flatten = self.D_corners_xx.flatten().compressed()
        D_corners_y_flatten = self.D_corners_yy.flatten().compressed()

        xs = D_corners_x_flatten + x_sipm_centre
        ys = D_corners_y_flatten + y_sipm_centre

        return np.vstack((xs, ys))
    
    def export_centres(self, file_name, active_area: bool = True) -> None:
        """Export the centres of the SiPMs to a file.

        Args:
            file_name (str): name of the file to write the centres into
        """
        centres = self.get_centres(active_area=active_area)
        np.savetxt(file_name, centres.T, delimiter=", ", fmt='%.3f')
    
    def get_corners_active(self) -> np.ndarray:
        """Get all the positions of the corners of the active area of the SiPMs.

        Returns:
            np.ndarray: an array with the x and y coordinates of the 
                corners of the active area of the SiPMs
        """
        
        A_corner_x = (self.D_corners_xx.flatten().compressed() + 
                        self.sipmunit.D_corner_x_active)
        B_corner_x = (self.D_corners_xx.flatten().compressed() + 
                        self.sipmunit.D_corner_x_active +
                        self.sipmunit.width_active)
        C_corner_x = (self.D_corners_xx.flatten().compressed() + 
                        self.sipmunit.D_corner_x_active +
                        self.sipmunit.width_active)
        D_corner_x = (self.D_corners_xx.flatten().compressed() + 
                        self.sipmunit.D_corner_x_active)
        A_corner_y = (self.D_corners_yy.flatten().compressed() + 
                        self.sipmunit.D_corner_y_active + 
                        self.sipmunit.height_active)
        B_corner_y = (self.D_corners_yy.flatten().compressed() + 
                        self.sipmunit.D_corner_y_active + 
                        self.sipmunit.height_active)
        C_corner_y = (self.D_corners_yy.flatten().compressed() + 
                        self.sipmunit.D_corner_y_active)
        D_corner_y = (self.D_corners_yy.flatten().compressed() + 
                        self.sipmunit.D_corner_y_active)
        
        corners = np.vstack((A_corner_x, A_corner_y, B_corner_x, B_corner_y,
                             C_corner_x, C_corner_y, D_corner_x, D_corner_y))
        
        return corners
    
    def export_corners_active(self,file_name:str):
        """Export the corners of the active area of the SiPMs to a file.

        Args:
            file_name (str): name of the file to write the corners into
        """
        corners = self.get_corners_active()
        np.savetxt(file_name, corners.T, 
                       header = 'A_x, A_y, B_x, B_y, C_x, C_y, D_x, D_y', 
                       delimiter=', ',
                       fmt = '%.3f')


    def get_corners_package(self) -> np.ndarray:
        """Get all the positions of the corners of the total (includes 
        packaging) area of the SiPMs.

        Returns:
            np.ndarray: an array with the x and y coordinates of the 
                corners of the total area (including packaging) of the SiPMs
        """
        
        A_corner_x = (self.A_corners_xx.flatten().compressed() + 
                        self.sipmunit.width_tolerance)
        B_corner_x = (self.B_corners_xx.flatten().compressed() -
                        self.sipmunit.width_tolerance)
        C_corner_x = (self.C_corners_xx.flatten().compressed() -
                        self.sipmunit.width_tolerance)
        D_corner_x = (self.D_corners_xx.flatten().compressed() +
                        self.sipmunit.width_tolerance)
        A_corner_y = (self.A_corners_yy.flatten().compressed() -
                        self.sipmunit.height_tolerance)
        B_corner_y = (self.B_corners_yy.flatten().compressed() -
                        self.sipmunit.height_tolerance)
        C_corner_y = (self.C_corners_yy.flatten().compressed() +
                        self.sipmunit.height_tolerance)
        D_corner_y = (self.D_corners_yy.flatten().compressed() +
                        self.sipmunit.height_tolerance)
        
        corners = np.vstack((A_corner_x, A_corner_y, B_corner_x, B_corner_y,
                             C_corner_x, C_corner_y, D_corner_x, D_corner_y))

        return corners
    
    def export_corners_package(self,file_name:str):
        """Export the corners of the total area of the SiPMs to a file.

        Args:
            file_name (str): name of the file to write the corners into
        """
        corners = self.get_corners_package()
        np.savetxt(file_name, corners.T, 
                       header = 'A_x, A_y, B_x, B_y, C_x, C_y, D_x, D_y', 
                       delimiter=', ',
                       fmt = '%.3f')
    
    def plot_empty_array(self, unit_width: float, unit_height:float):
        """Plot simple division of the array circle in units.

        Args:
            unit_width (float): width of the unit
            unit_height (float): height of the unit
        """
               
        patches_sipms = []

        fig = plt.figure(figsize = (6,6))
        ax = plt.subplot(111)

        n_corner_x, n_corner_y = np.shape(self.D_corners_xx)

        for _x_i in range(n_corner_x):
            for _y_i in range(n_corner_y):
                patches_sipms.append(
                    Rectangle(xy = (self.D_corners_xx[_x_i,_y_i], 
                                    self.D_corners_yy[_x_i,_y_i]), 
                              width = unit_width, 
                              height = unit_height, 
                              fill = True,
                              edgecolor = 'k',
                              facecolor = 'b',
                              zorder = 0,
                              alpha = 0.2,)
                              )
        ax.add_patch(Circle(xy=(0,0),
                            radius = self.array_diameter/2, 
                            fill = False, 
                            color = 'r',
                            zorder = 0, 
                            label = 'Array diameter'))

        p1 = PatchCollection(patches_sipms, match_original=True, 
                             label = 'SiPM units 1')
        ax.add_collection(p1)
        ax.set_xlabel('x [mm]')
        ax.set_ylabel('y [mm]')
        ax.set_aspect('equal')

        ax.set_xlim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)
        ax.set_ylim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)

        # make patches for legend while it is not fixed in matplotlib
        ax.add_patch(Rectangle((1e6,1e6),1,1,fill = True,
                                         edgecolor = 'k',
                                         facecolor = 'b',
                                         alpha = 0.2,
                                         label = 'SiPM unit inside the array'))
        ax.legend()
        plt.show()
        
    def plot_sipm_array(self, figax:tuple = None):
        """Plot the array of SiPMs.

        Args:
            figax (tuple, optional): figure and axis objects to draw in. 
                Defaults to None.

        Returns:
            tuple: figure and axis objects
        """
        if figax is None:
            fig, ax = plt.subplots(figsize = (6,6))
        else:
            fig, ax = figax

        patches_sipms = []

        n_corner_x, n_corner_y = np.shape(self.D_corners_xx)

        for _x_i in range(n_corner_x):
            for _y_i in range(n_corner_y):
                patches_sipms += self.sipmunit.get_unit_patches(
                    (self.D_corners_xx[_x_i,_y_i], 
                     self.D_corners_yy[_x_i,_y_i]))
                
        ax.add_patch(Circle(xy=(0,0),
                            radius = self.array_diameter/2, 
                            fill = False, 
                            color = 'r',
                            zorder = 0, 
                            label = 'Array diameter'))

        p1 = PatchCollection(patches_sipms, match_original=True, 
                             label = 'SiPM units 1')
        ax.add_collection(p1)
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        ax.set_aspect('equal')

        ax.set_xlim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)
        ax.set_ylim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)

        ax.legend()

        if figax is None:
            plt.show()
        else:
            return fig, ax
    
    def print_properties(self, unit_properties = False):
        """Prints the main properties of the array object.

        Args:
            unit_properties (bool, optional): _description_. Defaults to False.
        """
        print(f'Array diameter: {self.array_diameter} mm')
        print(f'Margin from the array edge: {self.border_margin} mm')
        print(f'Number of units: {self.n_sipms}')
        print(f'Total array area: {self.total_array_area:.2f} mm^2')
        print(f'Total photosensor area: {self.total_sipm_area:.2f} mm^2')
        print(f'Total SiPM active area: {self.total_sipm_active_area:.2f} mm^2')
        print(f'SiPM coverage: {self.sipm_coverage:.2f}')

        if unit_properties:
            self.sipmunit.print_properties()
           

Classes

class SiPMarray (array_diameter: float, border_margin: float, sipm_model: str)

Class to represent a SiPM array.

SiPMarray class

Args

array_diameter : float
diameter of the array
border_margin : float
margin between the sipms and the array border
sipm_model : str
model of the sipm to use
Expand source code
class SiPMarray():
    """Class to represent a SiPM array.
    """
    def __init__(self, array_diameter: float,
                 border_margin:float, sipm_model:str):
        """SiPMarray class

        Args:
            array_diameter (float): diameter of the array
            border_margin (float): margin between the sipms and the array border
            sipm_model (str): model of the sipm to use
        """
        
        self.array_diameter = array_diameter
        self.border_margin = border_margin
        self.sipmunit = self.load_sipmunit(sipm_model)
        
        corner_meshes = self.make_corners()
        self.cut_outside_array(corner_meshes)
        self.n_sipms = self.A_corners_xx.count()

        self.total_array_area = np.pi * (self.array_diameter/2)**2
        self.total_sipm_area = self.n_sipms * self.sipmunit.total_area
        self.total_sipm_active_area = self.n_sipms * self.sipmunit.active_area
        self.sipm_coverage = self.total_sipm_active_area/self.total_array_area


    def make_corners(self) -> tuple:
        """Define where the corners of the sipms are

        Returns:
            tuple: (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
                        C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy)
        """
        
        # make the center a not
        D_corner_x = np.arange(0,self.array_diameter/2 + self.sipmunit.width_unit, self.sipmunit.width_unit)
        D_corner_y = np.arange(0,self.array_diameter/2 +  self.sipmunit.height_unit, self.sipmunit.height_unit)

        D_corner_x = np.concatenate([-np.flip(D_corner_x[1:]),D_corner_x])
        D_corner_y = np.concatenate([-np.flip(D_corner_y[1:]),D_corner_y])

        D_corner_xx, D_corner_yy = np.meshgrid(D_corner_x, D_corner_y, indexing = 'ij') 

        A_corner_xx = D_corner_xx
        A_corner_yy = B_corner_yy = D_corner_yy + self.sipmunit.height_unit

        B_corner_xx = C_corner_xx = D_corner_xx + self.sipmunit.width_unit
        C_corner_yy = D_corner_yy
        
        return (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
                C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy)
    
    def cut_outside_array(self, corner_meshes:tuple):
        """Mask the sipms that are outside the array in a masked array.

        Args:
            corner_meshes (tuple): tuple with all the corner meshes
        """        
        (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
                C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy) = corner_meshes
        D_corner_rr = np.sqrt(D_corner_xx**2 + D_corner_yy**2)

        A_corner_rr = np.sqrt(A_corner_xx**2 + A_corner_yy**2)
        B_corner_rr = np.sqrt(B_corner_xx**2 + B_corner_yy**2)
        C_corner_rr = np.sqrt(C_corner_xx**2 + C_corner_yy**2)
        
        A_mask_inside_array_rr = A_corner_rr < self.array_diameter/2 - self.border_margin
        B_mask_inside_array_rr = B_corner_rr < self.array_diameter/2 - self.border_margin
        C_mask_inside_array_rr = C_corner_rr < self.array_diameter/2 - self.border_margin
        D_mask_inside_array_rr = D_corner_rr < self.array_diameter/2 - self.border_margin
        
        merged_mask = (~A_mask_inside_array_rr | 
                       ~B_mask_inside_array_rr | 
                       ~C_mask_inside_array_rr | 
                       ~D_mask_inside_array_rr)
        
        self.D_corners_xx = np.ma.masked_array(D_corner_xx, mask= merged_mask)
        self.D_corners_yy = np.ma.masked_array(D_corner_yy, mask= merged_mask)

        self.A_corners_xx = np.ma.masked_array(A_corner_xx, mask= merged_mask)
        self.A_corners_yy = np.ma.masked_array(A_corner_yy, mask= merged_mask)

        self.B_corners_xx = np.ma.masked_array(B_corner_xx, mask= merged_mask)
        self.B_corners_yy = np.ma.masked_array(B_corner_yy, mask= merged_mask)

        self.C_corners_xx = np.ma.masked_array(C_corner_xx, mask= merged_mask)
        self.C_corners_yy = np.ma.masked_array(C_corner_yy, mask= merged_mask)
    
    def load_sipmunit(self, model: str):
        """Load the SiPM unit.

        Args:
            model (str): name of the SiPM model

        Returns:
            SiPMunit: a SiPM unit class object
        """
        return SiPMunit(model=model)
    
    def get_centres(self, active_area: bool = True):
        """Get centres of the SiPMs.

        Args:
            active_area (bool, optional): Returns the centres of teh 
                active areas if true, otherwise the geometric centres of 
                the packaging if false. Defaults to True.
        Retuns:
            list: list of the centres of the SiPMs
        """
        if active_area:
            (x_sipm_centre, y_sipm_centre) = self.sipmunit.get_unit_active_centre()
        else:
            (x_sipm_centre, y_sipm_centre) = self.sipmunit.get_unit_centre()
        
        D_corners_x_flatten = self.D_corners_xx.flatten().compressed()
        D_corners_y_flatten = self.D_corners_yy.flatten().compressed()

        xs = D_corners_x_flatten + x_sipm_centre
        ys = D_corners_y_flatten + y_sipm_centre

        return np.vstack((xs, ys))
    
    def export_centres(self, file_name, active_area: bool = True) -> None:
        """Export the centres of the SiPMs to a file.

        Args:
            file_name (str): name of the file to write the centres into
        """
        centres = self.get_centres(active_area=active_area)
        np.savetxt(file_name, centres.T, delimiter=", ", fmt='%.3f')
    
    def get_corners_active(self) -> np.ndarray:
        """Get all the positions of the corners of the active area of the SiPMs.

        Returns:
            np.ndarray: an array with the x and y coordinates of the 
                corners of the active area of the SiPMs
        """
        
        A_corner_x = (self.D_corners_xx.flatten().compressed() + 
                        self.sipmunit.D_corner_x_active)
        B_corner_x = (self.D_corners_xx.flatten().compressed() + 
                        self.sipmunit.D_corner_x_active +
                        self.sipmunit.width_active)
        C_corner_x = (self.D_corners_xx.flatten().compressed() + 
                        self.sipmunit.D_corner_x_active +
                        self.sipmunit.width_active)
        D_corner_x = (self.D_corners_xx.flatten().compressed() + 
                        self.sipmunit.D_corner_x_active)
        A_corner_y = (self.D_corners_yy.flatten().compressed() + 
                        self.sipmunit.D_corner_y_active + 
                        self.sipmunit.height_active)
        B_corner_y = (self.D_corners_yy.flatten().compressed() + 
                        self.sipmunit.D_corner_y_active + 
                        self.sipmunit.height_active)
        C_corner_y = (self.D_corners_yy.flatten().compressed() + 
                        self.sipmunit.D_corner_y_active)
        D_corner_y = (self.D_corners_yy.flatten().compressed() + 
                        self.sipmunit.D_corner_y_active)
        
        corners = np.vstack((A_corner_x, A_corner_y, B_corner_x, B_corner_y,
                             C_corner_x, C_corner_y, D_corner_x, D_corner_y))
        
        return corners
    
    def export_corners_active(self,file_name:str):
        """Export the corners of the active area of the SiPMs to a file.

        Args:
            file_name (str): name of the file to write the corners into
        """
        corners = self.get_corners_active()
        np.savetxt(file_name, corners.T, 
                       header = 'A_x, A_y, B_x, B_y, C_x, C_y, D_x, D_y', 
                       delimiter=', ',
                       fmt = '%.3f')


    def get_corners_package(self) -> np.ndarray:
        """Get all the positions of the corners of the total (includes 
        packaging) area of the SiPMs.

        Returns:
            np.ndarray: an array with the x and y coordinates of the 
                corners of the total area (including packaging) of the SiPMs
        """
        
        A_corner_x = (self.A_corners_xx.flatten().compressed() + 
                        self.sipmunit.width_tolerance)
        B_corner_x = (self.B_corners_xx.flatten().compressed() -
                        self.sipmunit.width_tolerance)
        C_corner_x = (self.C_corners_xx.flatten().compressed() -
                        self.sipmunit.width_tolerance)
        D_corner_x = (self.D_corners_xx.flatten().compressed() +
                        self.sipmunit.width_tolerance)
        A_corner_y = (self.A_corners_yy.flatten().compressed() -
                        self.sipmunit.height_tolerance)
        B_corner_y = (self.B_corners_yy.flatten().compressed() -
                        self.sipmunit.height_tolerance)
        C_corner_y = (self.C_corners_yy.flatten().compressed() +
                        self.sipmunit.height_tolerance)
        D_corner_y = (self.D_corners_yy.flatten().compressed() +
                        self.sipmunit.height_tolerance)
        
        corners = np.vstack((A_corner_x, A_corner_y, B_corner_x, B_corner_y,
                             C_corner_x, C_corner_y, D_corner_x, D_corner_y))

        return corners
    
    def export_corners_package(self,file_name:str):
        """Export the corners of the total area of the SiPMs to a file.

        Args:
            file_name (str): name of the file to write the corners into
        """
        corners = self.get_corners_package()
        np.savetxt(file_name, corners.T, 
                       header = 'A_x, A_y, B_x, B_y, C_x, C_y, D_x, D_y', 
                       delimiter=', ',
                       fmt = '%.3f')
    
    def plot_empty_array(self, unit_width: float, unit_height:float):
        """Plot simple division of the array circle in units.

        Args:
            unit_width (float): width of the unit
            unit_height (float): height of the unit
        """
               
        patches_sipms = []

        fig = plt.figure(figsize = (6,6))
        ax = plt.subplot(111)

        n_corner_x, n_corner_y = np.shape(self.D_corners_xx)

        for _x_i in range(n_corner_x):
            for _y_i in range(n_corner_y):
                patches_sipms.append(
                    Rectangle(xy = (self.D_corners_xx[_x_i,_y_i], 
                                    self.D_corners_yy[_x_i,_y_i]), 
                              width = unit_width, 
                              height = unit_height, 
                              fill = True,
                              edgecolor = 'k',
                              facecolor = 'b',
                              zorder = 0,
                              alpha = 0.2,)
                              )
        ax.add_patch(Circle(xy=(0,0),
                            radius = self.array_diameter/2, 
                            fill = False, 
                            color = 'r',
                            zorder = 0, 
                            label = 'Array diameter'))

        p1 = PatchCollection(patches_sipms, match_original=True, 
                             label = 'SiPM units 1')
        ax.add_collection(p1)
        ax.set_xlabel('x [mm]')
        ax.set_ylabel('y [mm]')
        ax.set_aspect('equal')

        ax.set_xlim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)
        ax.set_ylim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)

        # make patches for legend while it is not fixed in matplotlib
        ax.add_patch(Rectangle((1e6,1e6),1,1,fill = True,
                                         edgecolor = 'k',
                                         facecolor = 'b',
                                         alpha = 0.2,
                                         label = 'SiPM unit inside the array'))
        ax.legend()
        plt.show()
        
    def plot_sipm_array(self, figax:tuple = None):
        """Plot the array of SiPMs.

        Args:
            figax (tuple, optional): figure and axis objects to draw in. 
                Defaults to None.

        Returns:
            tuple: figure and axis objects
        """
        if figax is None:
            fig, ax = plt.subplots(figsize = (6,6))
        else:
            fig, ax = figax

        patches_sipms = []

        n_corner_x, n_corner_y = np.shape(self.D_corners_xx)

        for _x_i in range(n_corner_x):
            for _y_i in range(n_corner_y):
                patches_sipms += self.sipmunit.get_unit_patches(
                    (self.D_corners_xx[_x_i,_y_i], 
                     self.D_corners_yy[_x_i,_y_i]))
                
        ax.add_patch(Circle(xy=(0,0),
                            radius = self.array_diameter/2, 
                            fill = False, 
                            color = 'r',
                            zorder = 0, 
                            label = 'Array diameter'))

        p1 = PatchCollection(patches_sipms, match_original=True, 
                             label = 'SiPM units 1')
        ax.add_collection(p1)
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        ax.set_aspect('equal')

        ax.set_xlim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)
        ax.set_ylim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)

        ax.legend()

        if figax is None:
            plt.show()
        else:
            return fig, ax
    
    def print_properties(self, unit_properties = False):
        """Prints the main properties of the array object.

        Args:
            unit_properties (bool, optional): _description_. Defaults to False.
        """
        print(f'Array diameter: {self.array_diameter} mm')
        print(f'Margin from the array edge: {self.border_margin} mm')
        print(f'Number of units: {self.n_sipms}')
        print(f'Total array area: {self.total_array_area:.2f} mm^2')
        print(f'Total photosensor area: {self.total_sipm_area:.2f} mm^2')
        print(f'Total SiPM active area: {self.total_sipm_active_area:.2f} mm^2')
        print(f'SiPM coverage: {self.sipm_coverage:.2f}')

        if unit_properties:
            self.sipmunit.print_properties()

Methods

def cut_outside_array(self, corner_meshes: tuple)

Mask the sipms that are outside the array in a masked array.

Args

corner_meshes : tuple
tuple with all the corner meshes
Expand source code
def cut_outside_array(self, corner_meshes:tuple):
    """Mask the sipms that are outside the array in a masked array.

    Args:
        corner_meshes (tuple): tuple with all the corner meshes
    """        
    (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
            C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy) = corner_meshes
    D_corner_rr = np.sqrt(D_corner_xx**2 + D_corner_yy**2)

    A_corner_rr = np.sqrt(A_corner_xx**2 + A_corner_yy**2)
    B_corner_rr = np.sqrt(B_corner_xx**2 + B_corner_yy**2)
    C_corner_rr = np.sqrt(C_corner_xx**2 + C_corner_yy**2)
    
    A_mask_inside_array_rr = A_corner_rr < self.array_diameter/2 - self.border_margin
    B_mask_inside_array_rr = B_corner_rr < self.array_diameter/2 - self.border_margin
    C_mask_inside_array_rr = C_corner_rr < self.array_diameter/2 - self.border_margin
    D_mask_inside_array_rr = D_corner_rr < self.array_diameter/2 - self.border_margin
    
    merged_mask = (~A_mask_inside_array_rr | 
                   ~B_mask_inside_array_rr | 
                   ~C_mask_inside_array_rr | 
                   ~D_mask_inside_array_rr)
    
    self.D_corners_xx = np.ma.masked_array(D_corner_xx, mask= merged_mask)
    self.D_corners_yy = np.ma.masked_array(D_corner_yy, mask= merged_mask)

    self.A_corners_xx = np.ma.masked_array(A_corner_xx, mask= merged_mask)
    self.A_corners_yy = np.ma.masked_array(A_corner_yy, mask= merged_mask)

    self.B_corners_xx = np.ma.masked_array(B_corner_xx, mask= merged_mask)
    self.B_corners_yy = np.ma.masked_array(B_corner_yy, mask= merged_mask)

    self.C_corners_xx = np.ma.masked_array(C_corner_xx, mask= merged_mask)
    self.C_corners_yy = np.ma.masked_array(C_corner_yy, mask= merged_mask)
def export_centres(self, file_name, active_area: bool = True) ‑> NoneType

Export the centres of the SiPMs to a file.

Args

file_name : str
name of the file to write the centres into
Expand source code
def export_centres(self, file_name, active_area: bool = True) -> None:
    """Export the centres of the SiPMs to a file.

    Args:
        file_name (str): name of the file to write the centres into
    """
    centres = self.get_centres(active_area=active_area)
    np.savetxt(file_name, centres.T, delimiter=", ", fmt='%.3f')
def export_corners_active(self, file_name: str)

Export the corners of the active area of the SiPMs to a file.

Args

file_name : str
name of the file to write the corners into
Expand source code
def export_corners_active(self,file_name:str):
    """Export the corners of the active area of the SiPMs to a file.

    Args:
        file_name (str): name of the file to write the corners into
    """
    corners = self.get_corners_active()
    np.savetxt(file_name, corners.T, 
                   header = 'A_x, A_y, B_x, B_y, C_x, C_y, D_x, D_y', 
                   delimiter=', ',
                   fmt = '%.3f')
def export_corners_package(self, file_name: str)

Export the corners of the total area of the SiPMs to a file.

Args

file_name : str
name of the file to write the corners into
Expand source code
def export_corners_package(self,file_name:str):
    """Export the corners of the total area of the SiPMs to a file.

    Args:
        file_name (str): name of the file to write the corners into
    """
    corners = self.get_corners_package()
    np.savetxt(file_name, corners.T, 
                   header = 'A_x, A_y, B_x, B_y, C_x, C_y, D_x, D_y', 
                   delimiter=', ',
                   fmt = '%.3f')
def get_centres(self, active_area: bool = True)

Get centres of the SiPMs.

Args

active_area : bool, optional
Returns the centres of teh active areas if true, otherwise the geometric centres of the packaging if false. Defaults to True.

Retuns

list: list of the centres of the SiPMs

Expand source code
def get_centres(self, active_area: bool = True):
    """Get centres of the SiPMs.

    Args:
        active_area (bool, optional): Returns the centres of teh 
            active areas if true, otherwise the geometric centres of 
            the packaging if false. Defaults to True.
    Retuns:
        list: list of the centres of the SiPMs
    """
    if active_area:
        (x_sipm_centre, y_sipm_centre) = self.sipmunit.get_unit_active_centre()
    else:
        (x_sipm_centre, y_sipm_centre) = self.sipmunit.get_unit_centre()
    
    D_corners_x_flatten = self.D_corners_xx.flatten().compressed()
    D_corners_y_flatten = self.D_corners_yy.flatten().compressed()

    xs = D_corners_x_flatten + x_sipm_centre
    ys = D_corners_y_flatten + y_sipm_centre

    return np.vstack((xs, ys))
def get_corners_active(self) ‑> numpy.ndarray

Get all the positions of the corners of the active area of the SiPMs.

Returns

np.ndarray
an array with the x and y coordinates of the corners of the active area of the SiPMs
Expand source code
def get_corners_active(self) -> np.ndarray:
    """Get all the positions of the corners of the active area of the SiPMs.

    Returns:
        np.ndarray: an array with the x and y coordinates of the 
            corners of the active area of the SiPMs
    """
    
    A_corner_x = (self.D_corners_xx.flatten().compressed() + 
                    self.sipmunit.D_corner_x_active)
    B_corner_x = (self.D_corners_xx.flatten().compressed() + 
                    self.sipmunit.D_corner_x_active +
                    self.sipmunit.width_active)
    C_corner_x = (self.D_corners_xx.flatten().compressed() + 
                    self.sipmunit.D_corner_x_active +
                    self.sipmunit.width_active)
    D_corner_x = (self.D_corners_xx.flatten().compressed() + 
                    self.sipmunit.D_corner_x_active)
    A_corner_y = (self.D_corners_yy.flatten().compressed() + 
                    self.sipmunit.D_corner_y_active + 
                    self.sipmunit.height_active)
    B_corner_y = (self.D_corners_yy.flatten().compressed() + 
                    self.sipmunit.D_corner_y_active + 
                    self.sipmunit.height_active)
    C_corner_y = (self.D_corners_yy.flatten().compressed() + 
                    self.sipmunit.D_corner_y_active)
    D_corner_y = (self.D_corners_yy.flatten().compressed() + 
                    self.sipmunit.D_corner_y_active)
    
    corners = np.vstack((A_corner_x, A_corner_y, B_corner_x, B_corner_y,
                         C_corner_x, C_corner_y, D_corner_x, D_corner_y))
    
    return corners
def get_corners_package(self) ‑> numpy.ndarray

Get all the positions of the corners of the total (includes packaging) area of the SiPMs.

Returns

np.ndarray
an array with the x and y coordinates of the corners of the total area (including packaging) of the SiPMs
Expand source code
def get_corners_package(self) -> np.ndarray:
    """Get all the positions of the corners of the total (includes 
    packaging) area of the SiPMs.

    Returns:
        np.ndarray: an array with the x and y coordinates of the 
            corners of the total area (including packaging) of the SiPMs
    """
    
    A_corner_x = (self.A_corners_xx.flatten().compressed() + 
                    self.sipmunit.width_tolerance)
    B_corner_x = (self.B_corners_xx.flatten().compressed() -
                    self.sipmunit.width_tolerance)
    C_corner_x = (self.C_corners_xx.flatten().compressed() -
                    self.sipmunit.width_tolerance)
    D_corner_x = (self.D_corners_xx.flatten().compressed() +
                    self.sipmunit.width_tolerance)
    A_corner_y = (self.A_corners_yy.flatten().compressed() -
                    self.sipmunit.height_tolerance)
    B_corner_y = (self.B_corners_yy.flatten().compressed() -
                    self.sipmunit.height_tolerance)
    C_corner_y = (self.C_corners_yy.flatten().compressed() +
                    self.sipmunit.height_tolerance)
    D_corner_y = (self.D_corners_yy.flatten().compressed() +
                    self.sipmunit.height_tolerance)
    
    corners = np.vstack((A_corner_x, A_corner_y, B_corner_x, B_corner_y,
                         C_corner_x, C_corner_y, D_corner_x, D_corner_y))

    return corners
def load_sipmunit(self, model: str)

Load the SiPM unit.

Args

model : str
name of the SiPM model

Returns

SiPMunit
a SiPM unit class object
Expand source code
def load_sipmunit(self, model: str):
    """Load the SiPM unit.

    Args:
        model (str): name of the SiPM model

    Returns:
        SiPMunit: a SiPM unit class object
    """
    return SiPMunit(model=model)
def make_corners(self) ‑> tuple

Define where the corners of the sipms are

Returns

tuple
(A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy)
Expand source code
def make_corners(self) -> tuple:
    """Define where the corners of the sipms are

    Returns:
        tuple: (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
                    C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy)
    """
    
    # make the center a not
    D_corner_x = np.arange(0,self.array_diameter/2 + self.sipmunit.width_unit, self.sipmunit.width_unit)
    D_corner_y = np.arange(0,self.array_diameter/2 +  self.sipmunit.height_unit, self.sipmunit.height_unit)

    D_corner_x = np.concatenate([-np.flip(D_corner_x[1:]),D_corner_x])
    D_corner_y = np.concatenate([-np.flip(D_corner_y[1:]),D_corner_y])

    D_corner_xx, D_corner_yy = np.meshgrid(D_corner_x, D_corner_y, indexing = 'ij') 

    A_corner_xx = D_corner_xx
    A_corner_yy = B_corner_yy = D_corner_yy + self.sipmunit.height_unit

    B_corner_xx = C_corner_xx = D_corner_xx + self.sipmunit.width_unit
    C_corner_yy = D_corner_yy
    
    return (A_corner_xx, A_corner_yy, B_corner_xx, B_corner_yy, 
            C_corner_xx, C_corner_yy, D_corner_xx, D_corner_yy)
def plot_empty_array(self, unit_width: float, unit_height: float)

Plot simple division of the array circle in units.

Args

unit_width : float
width of the unit
unit_height : float
height of the unit
Expand source code
def plot_empty_array(self, unit_width: float, unit_height:float):
    """Plot simple division of the array circle in units.

    Args:
        unit_width (float): width of the unit
        unit_height (float): height of the unit
    """
           
    patches_sipms = []

    fig = plt.figure(figsize = (6,6))
    ax = plt.subplot(111)

    n_corner_x, n_corner_y = np.shape(self.D_corners_xx)

    for _x_i in range(n_corner_x):
        for _y_i in range(n_corner_y):
            patches_sipms.append(
                Rectangle(xy = (self.D_corners_xx[_x_i,_y_i], 
                                self.D_corners_yy[_x_i,_y_i]), 
                          width = unit_width, 
                          height = unit_height, 
                          fill = True,
                          edgecolor = 'k',
                          facecolor = 'b',
                          zorder = 0,
                          alpha = 0.2,)
                          )
    ax.add_patch(Circle(xy=(0,0),
                        radius = self.array_diameter/2, 
                        fill = False, 
                        color = 'r',
                        zorder = 0, 
                        label = 'Array diameter'))

    p1 = PatchCollection(patches_sipms, match_original=True, 
                         label = 'SiPM units 1')
    ax.add_collection(p1)
    ax.set_xlabel('x [mm]')
    ax.set_ylabel('y [mm]')
    ax.set_aspect('equal')

    ax.set_xlim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)
    ax.set_ylim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)

    # make patches for legend while it is not fixed in matplotlib
    ax.add_patch(Rectangle((1e6,1e6),1,1,fill = True,
                                     edgecolor = 'k',
                                     facecolor = 'b',
                                     alpha = 0.2,
                                     label = 'SiPM unit inside the array'))
    ax.legend()
    plt.show()
def plot_sipm_array(self, figax: tuple = None)

Plot the array of SiPMs.

Args

figax : tuple, optional
figure and axis objects to draw in. Defaults to None.

Returns

tuple
figure and axis objects
Expand source code
def plot_sipm_array(self, figax:tuple = None):
    """Plot the array of SiPMs.

    Args:
        figax (tuple, optional): figure and axis objects to draw in. 
            Defaults to None.

    Returns:
        tuple: figure and axis objects
    """
    if figax is None:
        fig, ax = plt.subplots(figsize = (6,6))
    else:
        fig, ax = figax

    patches_sipms = []

    n_corner_x, n_corner_y = np.shape(self.D_corners_xx)

    for _x_i in range(n_corner_x):
        for _y_i in range(n_corner_y):
            patches_sipms += self.sipmunit.get_unit_patches(
                (self.D_corners_xx[_x_i,_y_i], 
                 self.D_corners_yy[_x_i,_y_i]))
            
    ax.add_patch(Circle(xy=(0,0),
                        radius = self.array_diameter/2, 
                        fill = False, 
                        color = 'r',
                        zorder = 0, 
                        label = 'Array diameter'))

    p1 = PatchCollection(patches_sipms, match_original=True, 
                         label = 'SiPM units 1')
    ax.add_collection(p1)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_aspect('equal')

    ax.set_xlim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)
    ax.set_ylim(-self.array_diameter*1.2/2,self.array_diameter*1.2/2)

    ax.legend()

    if figax is None:
        plt.show()
    else:
        return fig, ax
def print_properties(self, unit_properties=False)

Prints the main properties of the array object.

Args

unit_properties : bool, optional
description. Defaults to False.
Expand source code
def print_properties(self, unit_properties = False):
    """Prints the main properties of the array object.

    Args:
        unit_properties (bool, optional): _description_. Defaults to False.
    """
    print(f'Array diameter: {self.array_diameter} mm')
    print(f'Margin from the array edge: {self.border_margin} mm')
    print(f'Number of units: {self.n_sipms}')
    print(f'Total array area: {self.total_array_area:.2f} mm^2')
    print(f'Total photosensor area: {self.total_sipm_area:.2f} mm^2')
    print(f'Total SiPM active area: {self.total_sipm_active_area:.2f} mm^2')
    print(f'SiPM coverage: {self.sipm_coverage:.2f}')

    if unit_properties:
        self.sipmunit.print_properties()