#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: samuel.niang@cern.ch
"""

import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
from skimage.color import rgb2gray
import codecs
import dill


class BeamSpot:
    """
    Class to analyse the pictures comming from the MCP
    #### Attributes
    * BeamSpot.fname: string, file name of the picture
    * BeamSpot.img: 2D array, picture as an array
    * BeamSpot.pix: the pixels along the x axis
    * BeamSpot.piy: the pixels along the y axis
    * BeamSpot.Ix: array of floats, integral along the x axis
    * BeamSpot.Iy: array of float, integral along the y axis
    * BeamSpot.Ax: float, Amplitude, fit along the x axis
    * BeamSpot.Ay: float, Amplitude, fit along the y axis
    * BeamSpot.sigx: float, Sigma, fit along the x axis
    * BeamSpot.sigy: float, Sigma, fit along the x axis
    * BeamSpot.r0x: float, Center, fit along the x axis
    * BeamSpot.r0y: float, Center, fit along the x axis
    * BeamSpot.offsetx: float, offset, fit along the x axis
    * BeamSpot.offsety: float, offset, fit along the x axis
    * BeamSpot.poptx: array of floats, the parameters of the fit along the x-axis
    * BeamSpot.perrx: array of floats, errors on the parameters of the fit along the x-axis
    * BeamSpot.popty: array of floats, the parameters of the fit along the y-axis
    * BeamSpot.perry: array of floats, errors on the parameters of the fit along the y-axis
    * BeamSpot.reshape: array of int, the parameters to reshape, see help(import_image)
    * BeamSpot.Fit:
    """
    
    
    def __init__(self,fname,reshape=[],mcpp=None,fit="Filtered gaussian"):
        """
        Constructor of the class
        * Parameters
            * fname: string, file name of the picture, the accepted file format ["tif","jpg","jpeg","png","asc","bmp"]
            * reshape: array of 3 integers (optional), to reshape the pictures (square): x,y,length
            * fit: "Filtered gaussian", "Simple gaussian", "Two gaussians"
        * Example
            import GBARpy.MCPPicture as mcp
            bs = mcp.BeamSpot("name.tif")
        """
        # define ratio
        ratio = 1.
        if mcpp == None:
            self.mcpp = MCPParams()
        else:
            self.mcpp = mcpp
            if self.mcpp.checkRatioIsSet():
                ratio = self.mcpp.ratio

                
        self.fname = fname
        self.reshape = reshape
        self.img_original = self.fname
        self.img = import_image(self.fname,self.reshape)
        self.pix,self.Ix = integrate_picture_along_X(self.img)
        self.pix = self.pix * ratio
        self.piy,self.Iy = integrate_picture_along_Y(self.img)
        self.piy = self.piy * ratio
        if fit == "Filtered gaussian":
            self.fit = FilteredGaussian(self.pix,self.Ix,
                                            self.piy,self.Iy)
        elif fit == "Simple gaussian":
            self.fit = SimpleGaussian(self.pix,self.Ix,
                                            self.piy,self.Iy)
        elif fit == "Two gaussians":
            self.fit = TwoGaussians(self.pix,self.Ix,
                                            self.piy,self.Iy)
        else:
            self.fit = FilteredGaussian(self.pix,self.Ix,
                                            self.piy,self.Iy)
        
        
        self.poptx,self.perrx = self.fit.params1,self.fit.errors1
        self.popty,self.perry = self.fit.params2,self.fit.errors2
        self.offsetx = self.fit.params1[-1]
        self.offsety = self.fit.params2[-1]


    def __repr__(self):
        """
        To represent the object as a string
        * Returns
            * a string variable
        * Example
            import GBARpy.MCPPicture as mcp
            bs = mcp.BeamSpot("name.tif")
            repr = bs.__repr__()
            #or to print it in the python console
            print(bs)
        """
        return self.fit.__repr__()

    
    def plot_Y_int(self,label=""):
        """
        To plot the integral of the picture along the "y" axis
        * Parameters
            * label: (optional) a string
        * Example
            import GBARpy.MCPPicture as mcp
            bs = mcp.BeamSpot("name.tif")
            bs.plot_Y_int("Integral along the y-axis")
        """
        popt = self.popty
        popt[-1] = 0
        if np.any(np.isnan(popt)):
            plt.plot(self.piy,self.Iy,'.',ms=1,label=label)
        else:
            D = self.Iy - self.offsety
            p = plt.plot(self.piy,D,'.',ms=1,label=label)
            plt.plot(self.piy,self.fit.fitfunc(self.piy,*popt),color=p[0].get_color())
        plt.xlim([0,np.max(self.piy)])
        if len(label) != 0:
            plt.legend()
        
        
    def plot_X_int(self,label=""):
        """
        To plot the integral of the picture along the "x" axis
        * Parameters
            * label: (optional) a string
        * Example
            import GBARpy.MCPPicture as mcp
            bs = mcp.BeamSpot("name.tif")
            bs.plot_X_int("Integral along the x-axis")
        """
        popt = self.poptx
        popt[-1] = 0
        if np.any(np.isnan(popt)):
            plt.plot(self.pix,self.Ix,'.',ms=1,label=label)
        else:
            D = self.Ix-self.offsetx
            p = plt.plot(self.pix,D,'.',ms=1,label=label)
            plt.plot(self.pix,self.fit.fitfunc(self.pix,*popt),color=p[0].get_color())
        plt.xlim([0,np.max(self.pix)])
        if len(label) != 0:
            plt.legend()
        
        
    def plot_X_int_revert(self):
        """
        To plot the integral of the picture along the "x" axis and reverse the picture
        * Example
            import GBARpy.MCPPicture as mcp
            bs = mcp.BeamSpot("name.tif")
            bs.plot_X_int("Integral along the x-axis")
        """
        pix, Ix = self.pix,self.Ix
        popt = self.poptx
        popt[-1] = 0
        if np.any(np.isnan(popt)):
            plt.plot(Ix,pix,'.',ms=1)
        else:
            D = Ix - self.offsetx
            p = plt.plot(D,pix,'.',ms=1)
            G = self.fit.fitfunc(pix,*popt)
            plt.plot(G,pix,color=p[0].get_color())
        plt.ylim([np.max(pix),0])
    
    
    def plot(self,fname="",figsize=(12,10),fontsize=12,ftsizeticks=12):
        """
        To plot the picture and the analysis
        * Parameters
            * fname: string (optional), the name of the file to save the plot
            * figsize: tuple (size in inch X, Y) (optional), size of the figure
            * fontsize: int (optional), size of the font
            * ftsizeticks: int (optional), size of the ticks' font
        * Returns
            * fig: a matplotlib.pyplot.figure
        * Example
            import GBARpy.MCPPicture as mcp
            bs = mcp.BeamSpot("name.tif")
            fig = bs.plot("analysis.pdf")
            # or
            fig = bs.plot()
            fig.savefig("analysis.pdf")
        """
        def conv(a):
            b = []
            for e in a:
                b.append(str(e))
            return np.array(b)
        if len(fname) == 0:
            fname = None
        fig = plt.figure(figsize=figsize)
        
        plt.subplot(223)
        plt.text(0,0,self.fit.__repr__().replace('\t',' '),fontsize = fontsize)
        plt.axis('off')
        
        plt.subplot(221)
        self.plot_X_int_revert()
        if self.mcpp.checkRatioIsSet():
            plt.ylabel("mm",fontsize=fontsize)
        else:
            plt.ylabel("pixels",fontsize=fontsize)
        plt.grid()
        plt.xticks(fontsize=ftsizeticks)
        plt.yticks(fontsize=ftsizeticks)
        plt.title("Integral along the x-axis",fontsize=fontsize)
        if not(np.isnan(self.poptx[0])):
            plt.xlim([0,max(self.Ix-self.offsetx)*1.1])

        plt.subplot(222)
        plt.imshow(self.img,vmin=self.img.min(),vmax=self.img.max())
        plt.xticks(fontsize=ftsizeticks)
        plt.yticks(fontsize=ftsizeticks)
        y = len(self.img)
        x = len(np.transpose(self.img))
        if self.mcpp.checkRatioIsSet():
            l = np.linspace(0,x,4)
            l2 = conv(np.floor(l*self.mcpp.ratio))
            plt.xticks(l,l2)
            l = np.linspace(0,y,4)
            l2 = conv(np.floor(l*self.mcpp.ratio))
            plt.yticks(l,l2)
            plt.xlim(0,x)
            plt.ylim(y,0)
                
        plt.subplot(224)
        self.plot_Y_int()
        if self.mcpp.checkRatioIsSet():
            plt.xlabel("mm",fontsize=fontsize)
        else:
            plt.xlabel("pixels",fontsize=fontsize)
        plt.grid()
        plt.xticks(fontsize=ftsizeticks)
        plt.yticks(fontsize=ftsizeticks)
        plt.title("Integral along the y-axis",fontsize=fontsize)
        if not(np.isnan(self.popty[0])):
            plt.ylim([0,max(self.Iy-self.offsety)*1.1])
        plt.tight_layout()
        if fname != None:
            fig.savefig(fname)
        return fig
    



###############################################################################


class FitInterface():
    """
    Super class to do the fit of the MCP pictures
    Need to be define 
    self.fitfunc : the fit function
    self.labels : the names the parameters
    
    """
    
    def __init__(self):
        self.params1 = []
        self.errors1 = []
        self.params2 = []
        self.errors2 = []
        self.labels = []
        self.title1 = "\nIntegral along the x-axis\n"
        self.title2 = "\nIntegral along the y-axis\n"
    
    
    def Fit(self,x1,y1,x2=[],y2=[],p0=None):
        """
        To do the fit y = f(x)

        Parameters
        ----------
        x1 : array of float
            the first x input data 
        y1 : array of float
            the first y input data
        x2 : array of float, optional
            the second x input data 
        y2 : array of float, optional
            the second y input data 

        Returns
        -------
        None.

        """
        
        ### self.fitfunc has te be defined
        popt,pcov = curve_fit(self.fitfunc,x1,y1)
        self.params1 = popt
        self.errors1 = np.diag(pcov)
        
        if len(x2) > 0:
            popt,pcov = curve_fit(self.fitfunc,x2,y2)
            self.params2 = popt
            self.errors2 = np.diag(pcov)
            
        if len(self.labels) < len(self.params1):
            self.labels = []
            for i in np.arange(len(self.params1)):
                self.labels.append("")
            
        
    def __repr__(self):
        """
        To turn the parameters of the fit to a string 

        Returns
        -------
        res : str
            the string containing the result of the fit

        """
        res = self.title1
        for i in np.arange(len(self.params1)):
            res += self.labels[i]+':\t'+num2str(self.params1[i])
            res += " ± "+num2str(self.errors1[i])+"\n"
        if len(self.params2) > 0:
            res+=self.title2
        for i in np.arange(len(self.params2)):
            res += self.labels[i]+':\t'+num2str(self.params2[i])
            res += " ± "+num2str(self.errors2[i])+"\n"
        return res


###############################################################################


class SimpleGaussian(FitInterface):
    """
    A gaussian fit of the integral along the x and y axis
    """
    
    def __init__(self,x1,y1,x2,y2):
        """
        The constructor of the class

        Parameters
        ----------
        x1 : array of float
            the x axis
        y1 : array of float
            integral along the x axis
        x2 : array of float
            the y axis
        y2 : array of float
            integral along the y axis

        """
        super().__init__()
        self.labels = ["Ampli","Center","Sigma","Offset"]
        self.fitfunc = gaussian_offset
        try:
            self.Fit(x1,y1,x2,y2)
        except:
            print("The Fit has failed")
            self.params1 = np.ones(len(self.labels))+np.nan
            self.params2 = self.params1
            self.errors1 = self.params1
            self.errors2 = self.params1
    
    def Fit(self,x1,y1,x2,y2):
        """
        To do the fit y = f(x)

        Parameters
        ----------
        x1 : array of float
            the first x input data 
        y1 : array of float
            the first y input data
        x2 : array of float, optional
            the second x input data 
        y2 : array of float, optional
            the second y input data 

        Returns
        -------
        None.

        """
        popt,pcov = curve_fit(gaussian,x1,y1,bounds=(0,np.inf))
        p0 = np.concatenate([popt,[min(y1)]])
        popt,pcov = curve_fit(gaussian_offset,x1,y1,bounds=(0,np.inf),p0=p0)
        self.params1 = popt
        self.errors1 = np.diag(pcov)
        
        popt,pcov = curve_fit(gaussian,x2,y2,bounds=(0,np.inf))
        p0 = np.concatenate([popt,[min(y2)]])
        popt,pcov = curve_fit(gaussian_offset,x2,y2,bounds=(0,np.inf),p0=p0)
        self.params2 = popt
        self.errors2 = np.diag(pcov)


###############################################################################


class FilteredGaussian(FitInterface):
    
    def __init__(self,x1,y1,x2,y2):
        """
        The constructor of the class

        Parameters
        ----------
        x1 : array of float
            the x axis
        y1 : array of float
            integral along the x axis
        x2 : array of float
            the y axis
        y2 : array of float
            integral along the y axis

        """
        super().__init__()
        self.labels = ["Ampli","Center","Sigma","Offset"]
        self.fitfunc = gaussian_offset
        try:
            self.params1,self.errors1 = fit_gaussian_offset_filtered(x1,y1)
            self.params2,self.errors2 = fit_gaussian_offset_filtered(x2,y2)
        except:
            print("The Fit has failed")
            self.params1 = np.ones(len(self.labels))+np.nan
            self.params2 = self.params1
            self.errors1 = self.params1
            self.errors2 = self.params1

###############################################################################

class TwoGaussians(FitInterface):
    
    def __init__(self,x1,y1,x2,y2):
        """
        The constructor of the class

        Parameters
        ----------
        x1 : array of float
            the x axis
        y1 : array of float
            integral along the x axis
        x2 : array of float
            the y axis
        y2 : array of float
            integral along the y axis

        """
        super().__init__()
        self.fitfunc = twoGaussians_offset
        self.labels = ["Ampli 1","Center1","Sigma 1","Ampli 2","Center2",
                       "Sigma 2","Offset"]
        try:
            self.Fit(x1,y1,x2,y2)
        except:
            print("The Fit has failed")
            self.params1 = np.ones(len(self.labels))+np.nan
            self.params2 = self.params1
            self.errors1 = self.params1
            self.errors2 = self.params1
    
    def Fit(self,x1,y1,x2,y2):
        """
        To do the fit y = f(x)

        Parameters
        ----------
        x1 : array of float
            the first x input data 
        y1 : array of float
            the first y input data
        x2 : array of float, optional
            the second x input data 
        y2 : array of float, optional
            the second y input data 

        Returns
        -------
        None.

        """
        popt,pcov = curve_fit(twoGaussians,x1,y1,bounds=(0,np.inf))
        p0 = np.concatenate([popt,[min(y1)]])
        popt,pcov = curve_fit(twoGaussians_offset,x1,y1,p0 = p0,
                              bounds=(0,np.inf))
        self.params1,self.errors1 = popt,np.diag(pcov)
        
        popt,pcov = curve_fit(twoGaussians,x2,y2,bounds=(0,np.inf))
        p0 = np.concatenate([popt,[min(y2)]])
        popt,pcov = curve_fit(twoGaussians_offset,x2,y2,p0 = p0,
                              bounds=(0,np.inf))
        self.params2,self.errors2 = popt,np.diag(pcov)
        
###############################################################################            
        



def gaussian_offset(x,a,x0,s0,c):
    """
    Gaussian distribution with an offset
    f(x) = amplitude/sqrt(2pi)/sigma * exp(-1/2 {(x-mu)/sigma}^2) + offset
    
    * Parameters
        * x:  an np array
        * a:  the amplitude
        * s0: floating number, the standard deviation
        * x0: floating number, the mean value, center of the distribution
        * c:  the offset
    * Returns
        * the value of the distribution
    """
    return a*normal_distribution(x,s0,x0) + c

    

def gaussian(x,a,x0,s0):
    """
    Gaussian distribution
    f(x) = amplitude/sqrt(2pi)/sigma * exp(-1/2 {(x-mu)/sigma}^2)
    
    * Parameters
        * x:  an np array
        * a:  the amplitude
        * s0: floating number, the standard deviation
        * x0: floating number, the mean value, center of the distribution
    * Returns
        * the value of the distribution
    """
    return gaussian_offset(x,a,x0,s0,0)

def normal_distribution(x,s0,x0):
    """
    Normal distribution 
    f(x) = 1/sqrt(2pi)/sigma * exp(-1/2 {(x-mu)/sigma}^2)
    * Parameters
        * x:  an np array
        * s0: floating number, the standard deviation
        * x0: floating number, the mean value, center of the distribution
    * Returns
        the value of the distribution
    """
    return 1/np.sqrt(2*np.pi)/s0*np.exp(-(((x-x0)/s0)**2)/2)

def twoGaussians(x,a1,x1,s1,a2,x2,s2):
    """
    Sum of two gaussian distribution

    Parameters
    ----------
    x : float, numpy array
        the variable.
    a1 : float
        Amplitude of the first distribution.
    x1 : float
        Mean value of the first distribution.
    s1 : float
        Standard deviation of the first distribution.
    a2 : float
        Amplitude of the second distribution.
    x2 : float
        Mean value of the second distribution.
    s2 : float
        Standard deviation of the second distribution.

    Returns
    -------
    float, numpy array
        Value of the distribution.

    """
    return a1*normal_distribution(x,s1,x1)+a2*normal_distribution(x,s2,x2)

def twoGaussians_offset(x,a1,x1,s1,a2,x2,s2,c):
    """
    Sum of two gaussian distribution with an offset

    Parameters
    ----------
    x : float, numpy array
        the variable.
    a1 : float
        Amplitude of the first distribution.
    x1 : float
        Mean value of the first distribution.
    s1 : float
        Standard deviation of the first distribution.
    a2 : float
        Amplitude of the second distribution.
    x2 : float
        Mean value of the second distribution.
    s2 : float
        Standard deviation of the second distribution.
    c : float
        The offset.

    Returns
    -------
    float, numpy array
        Value of the distribution.

    """
    return a1*normal_distribution(x,s1,x1)+a2*normal_distribution(x,s2,x2) + c
                   
                           
def num2str(a,n=3):
    """
    To turn a number into a string

    Parameters
    ----------
    a : float
        the number to convert.
    n : int, optional
        number of significant digits. The default is 3.

    Returns
    -------
    str
        the string.

    """
    n = str(n)
    y = '%.'+n+'g'
    return '%s' % float(y % a)

def fit_gaussian_offset_filtered(x,y):
    """
    Fit with the function gaussian_offset.
    * Parameters
        * x: numpy array
        * y: numpy array
    * Returns
        * popt,perr: numpy arrays, the parameters and the error of the fit
    """
    x = np.array(x)
    y = np.array(y)
    s = 100
    a = max(y)/np.sqrt(2*np.pi)/s
    x0 = x[np.argmax(y)]
    c = min(y)
    p0 = [a,x0,s,c]
    try:
        popt,pcov = curve_fit(gaussian_offset,x,y,p0=p0,bounds=(0,np.inf))
        mask = np.logical_or(np.abs(y-y.max())/y.max() < 0.2,
                             np.abs((y-popt[3])/popt[3]) < 0.2)
        x0 = x[mask]
        y0 = y[mask]
        popt,pcov = curve_fit(gaussian_offset,x0,y0,p0=popt,bounds=(0,np.inf))
        mask = np.logical_or(np.abs(x-popt[1])/popt[1] < 0.1,
                             np.abs((y-popt[3])/popt[3]) < 0.05)
        x0 = x[mask]
        y0 = y[mask]
        popt,pcov = curve_fit(gaussian_offset,x0,y0,p0=popt,bounds=(0,np.inf))
        mask = np.abs(y-gaussian_offset(x,*popt))/y < 0.1
        mask = np.logical_or(np.abs(x-popt[1])/popt[1] < 0.1,
                             np.abs((y-popt[3])/popt[3]) < 0.1)
        x0 = x[mask]
        y0 = y[mask]
        popt,pcov = curve_fit(gaussian_offset,x0,y0,p0=popt,bounds=(0,np.inf))
        perr = np.sqrt(np.diag(pcov))
    except:
        print("The fit failed")
    return popt,perr

def reshapeIMG(img,ix,iy,l):
    """
    To reshape an image to a squared one
    * Parameters
        * img: np.array([np.array]) the image array
        * ix: the index of the center of the square
        * iy: the index of the center of the square
        * l: the half length of the square
    *Returns
        * The reshaped picture as an array
    """
    ix = int(ix)
    iy = int(iy)
    IX = np.arange(ix-l,ix+l)
    IY = np.arange(iy-l,iy+l)
    img = img[IY]
    img = ((img.T)[IX]).T
    return img


def import_image(fname,reshape=[]):
    """
    To import the picture as an array
    * Parameters
        * fname: string, name of the file
        * reshape: array, to reshape the picture as a square [ix,iy,l]
    * Returns
        * the picture as 2D array
    """
    fileformat = ["tif","jpg","jpeg","png","asc","bmp"]
    ext = fname.split('.')[len(fname.split('.'))-1]
    ext = ext.lower()
    if not(ext in fileformat):
        raise TypeError(ext+' not in '+str(fileformat))
    # if asc file
    if   ext == "asc":
            with codecs.open(fname, encoding='utf-8-sig') as ff:
                img = np.array([[float(x) for x in line.split()] for line in ff])
    else:
        img = np.double(mpimg.imread(fname))
        img = rgb2gray(img)
    if len(reshape) == 3:
        img = reshapeIMG(img,reshape[0],reshape[1],reshape[2])
        
    N = np.concatenate(img).size
    N = int(0.01*N)
    img -= np.mean(np.sort(np.concatenate(img))[:N])
    
    return img


def integrate_picture_along_Y(img):
    """
    To integrate the picture along th Y axis
    * Parameters
        * img: image as a 2D numpy array
    * Returns
        * (pix,Iy): A tupple with pix the pixel numbers as a numpy array and Iy the integral as a numpy array
    """
    I = np.zeros_like(img[0])
    for l in img:
        I = I + l
    pix = np.arange(len(I))
    Iy = I/len(img)
    return pix,Iy


def integrate_picture_along_X(img):
    """
    To integrate the picture along th X axis
    *Parameters
        * img: image as a 2D numpy array
    * Returns
        * (pix,Ix): A tupple with pix the pixel numbers as a numpy array and Ix the integral as a numpy array
    """
    pix,ix = integrate_picture_along_Y(np.transpose(img))
    Ix = np.array([])
    for i in ix:
        Ix = np.concatenate([Ix,[i]])
    return pix,Ix


def getIndexStr(N,i):
    """
    To convert an int 'i' to a string
    * Example
        getIndexStr(100,15) #returns '015'
    """
    if i < 0 or i > N:
        raise ValueError("N >= i or i > 0 is required")
    l = len(str(N))
    res = str(i)
    while l > len(res):
        res = "0" + res
    return res


def significant(x, sig=4):
    """
    To turn a float as a str with a certain number of significant digits
    """
    res = np.round(x, sig-int(np.floor(np.log10(np.abs(x))))-1)
    return str(res)



###################################################################################





class MCPParams:
    """
    Class to store the parameters of the MCP to adapt the analysis
    
    #### Attributes
    * MCPParams.R: radius of the mirror in mm
    * MCPParams.x0: x position of the center of the mirror in pixels
    * MCPParams.y0: y position of the center of the mirror in pixels
    * MCPParams.R0: radius of the mirror in pixels
    * MCPParams.ratio: mm/pixels ratio
    * MCPParams.canBePlot: boolean to know if the mirror can be plot
    * MCPParams.ratioIsSet: boolean to know if a ratio has been defined
    """
    def __init__(self,name=None,R=None,x0=None,y0=None,
                R0=None,ratio=None):
            """
            Constructor of the class
            * Parameters
                * name: (optional) string, the name of the MCP
                * R: (optional), float, radius of the mirror in mm
                * x0: (optional), int, x position of the center of the mirror in pixels
                * y0: (optional), int, y position of the center of the mirror in pixels
                * R0: (optional), int, radius of the mirror in pixels
                * ratio: (optional), mm/pixels ratio
                    if R and R0 have been defined, then ratio is defined automatically
            """
            self.name = name
            self.R = R
            self.x0 = x0
            self.y0 = y0
            self.R0 = R0
            self.ratio = ratio
            if R!= None and R0!=None:
                self.ratio = R/R0
            self.checkRatioIsSet()
    
    
    def __repr__(self):
        """
        To translate the object as a string
        * Returns
            * the string
        * Example
            params = MCPParams()
            print(params)
            #or
            r = params.__repr__()
        """
        res = "MCP Parameters\n"
        res += "name: "+str(self.name)+"\n"
        res+= "R: "+str(self.R)+"\n"
        res+= "x0: "+str(self.x0)+"\n"
        res+= "y0: "+str(self.y0)+"\n"
        res+= "R0: "+str(self.R0)+"\n"
        res+= "ratio: "+str(self.ratio)+"\n"
        return res
    
    def defineRatio(self,mm,pix):
        """
        To define the ratio mm vs pixels
        * Parameters
            mm: float, a distance in mm
            pix: float, the equivalent distance in pixels
        """
        try:
            r = mm/pix
            self.ratio = r
            self.checkRatioIsSet()
        except:
            print("The setting of the ratio has failed")
            
            
    def checkRatioIsSet(self):
        """
        To check is the ratio has been set
        * Return
            boolean
        """
        if self.ratio == None:
            self.ratioIsSet = False
        else:
            self.ratioIsSet = True
        return self.ratioIsSet
    
    def checkAllSet(self):
        """
        To check is all has been set
        * Return
            boolean
        """
        if self.R == None:
            return False
        if self.x0 == None:
            return False
        if self.y0 == None:
            return False
        if self.R0 == None:
            return False
        if self.ratio == None:
            return False
        return True
        
        
    def save_conf(self,fname):
        """
        To save the parameters of the MCP as a binary file
        Use .mcp extension
        * Parameters
            fname: string, the name of the binary file
        * Example
            params = MCPParams()
            params.save_config("config.mcp")
        """
        with open(fname, 'wb') as f1:
            dill.dump(self, f1)
        print("Parameters saved as",fname)

############################################################################
def import_config(fname):
    """
    To import the MCP parameters from a binary file
    * Parameters
        fname: string, the file's name
    """
    pp = None
    try:
        with open(fname, 'rb') as f1:
            pp = dill.load(f1)
            print("MCP parameters",pp.name,"from",fname,"imported")
    except:
        print("Importation of",fname,"has failed")
    return pp

            
