def __init__(self, I, **args):
        """
        For mapper naive we just have a single object.

        The support projection is:
            Psup O = F . S . F-1 . O

        And the modulus projection is:
            Pmod O = O sqrt{ I / M }

        Where M is the forward mapping (self.Imap) of O
        to the diffraction intensity:
            M = (1 - gaus) sum_i | O_i |^2 +
                N * L * gaus * | sum_i O_i |^2 


        Parameters
        ----------
        I : numpy.ndarray, (N, M, K)
            Merged diffraction patterns to be phased. 
        
            N : the number of pixels along slowest scan axis of the detector
            M : the number of pixels along slow scan axis of the detector
            K : the number of pixels along fast scan axis of the detector
        
        O : numpy.ndarray, (N, M, K) 
            The real-space scattering density of the object such that:
                I = |F[O]|^2
            where F[.] is the 3D Fourier transform of '.'.     
        
        dtype : np.float32 or np.float64
            Determines the numerical precision of the calculation. 

        c_dtype : np.complex64 or np.complex128
            Determines the numerical precision of the complex variables. 

        support : (numpy.ndarray, None or int), optional (N, M, K)
            Real-space region where the object function is known to be zero. 
            If support is an integer then the N most intense pixels will be kept at
            each iteration.

        voxel_number : None or int, optional
            If int, then the voxel number support is used. If support is not None or False
            then the voxel number support is used within the sample support bounds.
        
        mask : numpy.ndarray, (N, M, K), optional, default (1)
            The valid detector pixels. Mask[i, j, k] = 1 (or True) when the detector pixel 
            i, j, k is valid, Mask[i, j, k] = 0 (or False) otherwise.
        
        alpha : float, optional, default (1.0e-10)
            A floating point number to regularise array division (prevents 1/0 errors).
        
        disorder['sigma'] : float
            The standard deviation of the isotropic displacement of the solid unit within
            the crystal.

        detector['shape'] : tupple
            The shape of the detector (3D).

        crystal['unit_cell'] : tupple
            The dimensions of the unit cell

        Returns
        -------
        O : numpy.ndarray, (U, V, K) 
            The real-space object function after 'iters' iterations of the ERA algorithm.
        
        info : dict
            contains diagnostics:
                
                'I'     : the diffraction pattern corresponding to object above
                'eMod'  : the modulus error for each iteration:
                          eMod_i = sqrt( sum(| O_i - Pmod(O_i) |^2) / I )
                'eCon'  : the convergence error for each iteration:
                          eCon_i = sqrt( sum(| O_i - O_i-1 |^2) / sum(| O_i |^2) )
        """
        # dtype
        #-----------------------------------------------
        if isValid('dtype', args) :
            dtype = args['dtype']
        else :
            dtype = np.float64
        
        if isValid('c_dtype', args) :
            c_dtype = args['c_dtype']
        else :
            c_dtype = np.complex128
        
        # initialise the object
        #-----------------------------------------------
        if isValid('O', args):
            modes = np.fft.fftn(args['O'])
        else :
            print('initialising object with random numbers')
            modes = np.random.random(I.shape).astype(c_dtype)
        
        # initialise the mask, alpha value and amp
        #-----------------------------------------------
        self.mask = 1
        if isValid('mask', args):
            self.mask = args['mask']
        
        self.alpha = 1.0e-10
        if isValid('alpha', args):
            self.alpha = args['alpha']
        
        self.I_norm = (self.mask * I).sum()
        self.amp    = np.sqrt(I.astype(dtype))
        
        # define the support projection
        #-----------------------------------------------
        if isValid('voxel_number', args) :
            self.voxel_number = args['voxel_number']
            self.support = None
        else :
            self.voxel_number = False
            #
            self.support = args['support']
            self.S       = self.support.copy()
        
        # make the unit cell and diffuse weightings
        #-----------------------------------------------
        self.sym_ops = get_sym_ops(args)
        
        N          = args['disorder']['n']
        exp        = make_exp(args['disorder']['sigma'], args['detector']['shape'])
        lattice    = symmetry_operations.lattice(args['crystal']['unit_cell'], args['detector']['shape'])
        #self.solid_syms = lambda x : sym_ops.solid_syms(x)
        
        self.unit_cell_weighting = N * lattice * exp
        self.diffuse_weighting   = (1. - exp)
        
        self.modes = modes
    def __init__(self, I, **args):
        """
        """
        # dtype
        #-----------------------------------------------
        if isValid('dtype', args) :
            dtype = args['dtype']
        else :
            dtype = np.float64
        
        if isValid('c_dtype', args) :
            c_dtype = args['c_dtype']
        else :
            c_dtype = np.complex128

        # initialise the object
        #-----------------------------------------------
        if isValid('O', args):
            O = np.fft.fftn(args['O'])
        else :
            print('initialising object with random numbers')
            O = np.random.random(I.shape).astype(c_dtype)

        # initialise the mask, alpha value and amp
        #-----------------------------------------------
        self.mask = 1
        if isValid('mask', args):
            self.mask = args['mask']
        
        self.alpha = 1.0e-10
        if isValid('alpha', args):
            self.alpha = args['alpha']
        
        self.I_norm = (self.mask * I).sum()
        self.amp    = np.sqrt(I.astype(dtype))
        
        # define the support projection
        #-----------------------------------------------
        if isValid('voxel_number', args) :
            self.voxel_number = args['voxel_number']
            self.support = None
            self.S       = None
        else :
            self.voxel_number = False
            #
            self.support = args['support']
            self.S       = self.support.copy()
        
        # make the unit cell and diffuse weightings
        #-----------------------------------------------
        self.sym_ops = get_sym_ops(args)
        
        N          = args['disorder']['n']
        exp        = make_exp(args['disorder']['sigma'], args['detector']['shape'])
        lattice    = symmetry_operations.lattice(args['crystal']['unit_cell'], args['detector']['shape'])
        self.unit_cell = args['crystal']['unit_cell']

        self.unit_cell_weighting = N * lattice * exp
        self.diffuse_weighting   = (1. - exp)
        
        self.modes = np.zeros( (2 * self.sym_ops.syms.shape[0],) + self.sym_ops.syms.shape[1:], O.dtype)
        # diffuse terms
        self.modes[:self.modes.shape[0]//2] = self.sym_ops.solid_syms_Fourier(O, apply_translation = False)
        # unit cell terms
        self.modes[self.modes.shape[0]//2:] = self.sym_ops.solid_syms_Fourier(O, apply_translation = True)

        print('eMod(modes0):', self.Emod(self.modes))

        # Ellipse axes
        #-----------------------------------------------
        # Here we have :
        # (x / e0)**2 + (y / e1)**2 = 1 , where
        # 
        # e0 = sqrt{ self.diffuse_weighting / I } and
        # 
        # e1 = sqrt{ self.unit_cell_weighting / I }
        #-----------------------------------------------
        
        # floating point tolerance for 1/x (log10)  
        tol = 100. #1.0e+100
        
        # check for numbers close to infinity in sqrt(I / self.diffuse_weighting)
        m     = self.diffuse_weighting <= 0.0
        m[~m] = 0.5 * (np.log10(I[~m]) - np.log10(self.diffuse_weighting[~m])) > tol
        
        self.e0_inf   = m.copy()
        self.e0       = np.zeros_like(self.diffuse_weighting)
        self.e0[~m]   = np.sqrt(I[~m]) / np.sqrt(self.diffuse_weighting[~m])
               
        # check for numbers close to infinity in sqrt(I / self.unit_cell_weighting)
        m     = self.unit_cell_weighting <= 0.0 
        m[~m] = 0.5 * (np.log10(I[~m]) - np.log10(self.unit_cell_weighting[~m])) > tol
              
        self.e1_inf = m.copy()
        self.e1     = np.zeros_like(self.unit_cell_weighting)
        self.e1[~m] = np.sqrt(I[~m]) / np.sqrt(self.sym_ops.syms.shape[0] * self.unit_cell_weighting[~m])

        self.iters = 0