/* -*- mode: c++; c-basic-offset: 3 -*-
 *
 * PANDORE (PANTHEON Project)
 *
 * GREYC IMAGE
 * 6 Boulevard Maréchal Juin
 * F-14050 Caen Cedex France
 *
 * This file is free software. You can use it, distribute it
 * and/or modify it. However, the entire risk to the quality
 * and performance of this program is with you.
 *
 * Please email any bugs, comments, and/or additions to this file to:
 * pandore@greyc.ensicaen.fr
 * http://www.greyc.ensicaen.fr/EquipeImage/Pandore/
 */

/**
 * @author Jean-Baptiste Hoock - 2005-07-28
 */

/**
 * @file fct.cpp
 *
 * Fast Curvelet Transform
 *
 * Transformee de curvelets rapide
 */

#include <math.h>
#include <pandore.h>

#include "curvelet.cpp"

#ifdef MAIN
#undef MAIN
#include "frequency/pfftshift.cpp"
#include "fftshift2.cpp"
#define MAIN
#else
#include "frequency/pfftshift.cpp"
#include "fftshift2.cpp"
#endif

/*
 * Makes the curvelet transform.
 * nbscales gives the number of scale including the coarsest wavelet level
 * nbangles_coarse is the number of angles at the 2nd coarsest level,
 * minimum 8, must be a multiple of 4.
 * First, we produce a FFT. Then, we carry out an angular partition and
 * we apply a band-pass filter. We "make right-angled" and finally, we
 * carry out an ifft. 
 */

/*
 * algorithme de transformation:
 * La transformee consiste d'abord a faire une FFT. On effectue ensuite
 * une partition angulaire et on applique un filtre passe-bande. On
 * rectangularise et enfin on effectue une ifft. 
 */
Errc FCT(Img2dsf& ims, Collection& curvelet, Long nbscales, Long nbangles_coarse) {
   /**
    * global commentaries for the names of variables:
    * the suffix _re for an image means real part
    * the suffix _im for an image means imaginaire part
    */

  // declarations of important images
  Img2dsf X_re,X_im;  //image in the frequency domain
  X_re.New(ims.Size());
  X_im.New(ims.Size());
  Img2dsf lowpass, Xlow_re, Xlow_im, Xhi_re, Xhi_im;
  // for the moment, allocations of Xlow and Xhi are made in the block lowpass
  // lowpass is allocated in using operator Lowpass 
  // dimensions of the initial image and number of elements
  Long N1=X_re.Height();
  Long N2=X_re.Width();
  Long nb_pixels=ims.Height()*ims.Width();
  Double inv_sqrt_nb_pixels; //value of normalization
  inv_sqrt_nb_pixels=1/sqrt((Double)nb_pixels);
  //declaration of variables of loop
  Long i,j,k,l;

  //initialization of the structures in order to calculate the FCT
  CurveletImage ci(ims,nbscales,nbangles_coarse);
  CurveletTransformAlgorithm cta;
  LowPass lp;
  
  //variables used to calculate the FCT
  //for any steps of the pyramidal decomposition, M1 and M2 are divised by 2
  Double M1, M2; 
  Long bigN1,bigN2;
  {//block initialization

    {//the FFT
      Img2dsf temp0, temp_re, temp_im;  //temporary images
      temp0.New(ims.Size());
      temp0=0;
      temp_re.New(ims.Size());
      temp_im.New(ims.Size());
      
      //the transformation
      FFTW(ims,temp0,temp_re,temp_im,1); //fft
      FFTShift2(temp_re,temp_im,X_re,X_im);
      //normalization
      for(i=0; i<X_re.Width(); i++)
	for(j=0; j<X_re.Height(); j++) {
	    X_re[j][i]*=(float)inv_sqrt_nb_pixels;
	    X_im[j][i]*=(float)inv_sqrt_nb_pixels; 
	  }
      
      //desallocation
      temp0.Delete();
      temp_re.Delete();
      temp_im.Delete();
    }
    {//reinitialization of X
      Img2dsf* cloneX_re=(Img2dsf*)X_re.Clone();
      Img2dsf* cloneX_im=(Img2dsf*)X_im.Clone();
      cta.init_Mi_bigNi(N1,N2,M1,M2,bigN1,bigN2);
    
      //reallocation of the X
      X_re.Delete();
      X_im.Delete();
      X_re.New(bigN1,bigN2);
      X_im.New(bigN1,bigN2);
    
      for(i=0; i<bigN2; i++)    
	for(j=0; j<bigN1; j++) {
	    Long equiv_index_1=((Long)(floor(N1/2.0)-floor(2*M1))+j)%N1;
	    Long equiv_index_2=((Long)(floor(N2/2.0)-floor(2*M2))+i)%N2;
	    if(equiv_index_1<0) equiv_index_1+=N1;
	    if(equiv_index_2<0) equiv_index_2+=N2;
	    X_re[j][i]=(*cloneX_re)[equiv_index_1][equiv_index_2];
	    X_im[j][i]=(*cloneX_im)[equiv_index_1][equiv_index_2];
	  } 
      
      delete cloneX_re;
      delete cloneX_im;
    }
    {//block: initialization of lowpass
      Xlow_re.New(bigN1,bigN2);
      Xlow_im.New(bigN1,bigN2);

      Xhi_re.New(bigN1,bigN2);
      Xhi_im.New(bigN1,bigN2);      

      lp.initPasseBas(lowpass,M1,M2,N1,N2);  //we use operator Lowpass here
      lp.applyLowPass(X_re,lowpass,Xlow_re); 
      lp.applyLowPass(X_im,lowpass,Xlow_im);
    }  
  }//end of block initialization
  
  {//block: pyramidal scale decomposition
    PyramidalScaleDecomposition psd(N1,N2);
    psd.decompose(ci.getNbangles(),ci.getCurv(),Xlow_re,Xlow_im,Xhi_re,Xhi_im);
  }

  {//final block: coarsest wavelet level
    Img2dsf Xlow_temp_re,Xlow_temp_im;
    Img2dsf curv_re;
    Img2dsf curv_im;  //this image isn't used
    const Long nb_elts=Xlow_re.Height()*Xlow_im.Width();
    const Double inv_sqrt_nb_elts=1/sqrt((Double)nb_elts);
    //allocations of temporary images 
    Xlow_temp_re.New(Xlow_re.Size());
    Xlow_temp_im.New(Xlow_im.Size());
    curv_re.New(Xlow_re.Size());
    curv_im.New(Xlow_im.Size());
    //ifft
    PFFTShift(Xlow_re,Xlow_im,Xlow_temp_re,Xlow_temp_im);
    FFTW(Xlow_temp_re,Xlow_temp_im,curv_re,curv_im,-1); //ifft
    //normalization
    for(i=0; i<Xlow_re.Width(); i++)
      for(j=0; j<Xlow_re.Height(); j++)
	curv_re[j][i]*=(float)inv_sqrt_nb_elts;
    ci.setImage(curv_re,nbscales-1,0); 
    //desallocation of temporary images
    Xlow_temp_re.Delete();
    Xlow_temp_im.Delete();
    curv_re.Delete();
    curv_im.Delete();
  }
  {//block: filling of the Collection curvelet
    Img1dsl* nbangles=(Img1dsl*)((ci.getNbangles()).Clone());
    Collection** les_curvelets;
    Img2dsf**  curvdata_tmp;
    Img2dsl** curv_size;//=new (Img3dsf*);

    
    les_curvelets=new Collection*[nbscales];
    for(i=0; i<nbscales; i++) {
       	  les_curvelets[i]=new Collection;
	  curvdata_tmp=new Img2dsf*[(*nbangles)[i]];
	  for(j=0; j<(*nbangles)[i]; j++){
	     curvdata_tmp[j]=new Img2dsf((ci.getCurv())[i][j].Props());
	     for(k=0; k<(ci.getCurv())[i][j].Width(); k++)
	        for(l=0; l<(ci.getCurv())[i][j].Height(); l++)
		   (*(curvdata_tmp[j]))[l][k]=(ci.getCurv())[i][j][l][k];
	  }
	  les_curvelets[i]->SETPARRAY("curvangles",Img2dsf,curvdata_tmp,(*nbangles)[i]);
       }
    
    
    //curv_size reference la taille des curvelets a une echelle et 
    //une orientation donnee
    curv_size=new Img2dsl*[nbscales];
    for(i=0; i<nbscales; i++) {
	  curv_size[i]=new Img2dsl((ci.getNbangles())[i],2);
	  for(j=0; j<(ci.getNbangles())[i]; j++) {
		(*(curv_size[i]))[j][0]=((ci.getCurv())[i][j]).Height();
		(*(curv_size[i]))[j][1]=((ci.getCurv())[i][j]).Width();
	     }
       }
    curvelet.SETPARRAY("curvelets",Collection,les_curvelets,nbscales);
    curvelet.SETPARRAY("curv_size",Img2dsl,curv_size,nbscales);
    curvelet.SETPOBJECT("nbangles",Img1dsl,nbangles);
    curvelet.SETVALUE("nbscales",Long,ci.getNbscales());
    curvelet.SETVALUE("height",Long,ci.getHeight());
    curvelet.SETVALUE("width",Long,ci.getWidth());
    curvelet.SETVALUE("nbangles_coarse",Long,ci.getNbangles_coarse());
  }
  //desallocation of important images
  X_re.Delete();
  X_im.Delete();
  Xhi_re.Delete();
  Xhi_im.Delete();
  Xlow_re.Delete();
  Xlow_im.Delete();
 
  return SUCCESS;  
} 

#ifdef MAIN 
/* 
 * Modify only the following constants, and the operator switches. 
 */ 
#define USAGE "USAGE : %s nbscales nbangle_coarse [-m mask] [im_in|-] [col_out|-]" 
#define PARC 2 // Number of parameters 
#define FINC 1 // Number of input images 
#define FOUTC 1 // Number of output images 
#define MASK 0 // Level of masking 

int main(int argc,char* argv[]) {
  Errc result; // The result code of the execution. 
  Pobject* mask; // The mask. 
  Pobject* objin[FINC+1]; // The input objects; 
  Pobject* objs[FINC+1]; // The source objects masked by the mask. 
  Pobject* objout[FOUTC+1]; // The output object. 
  Pobject* objd[FOUTC+1]; // The result object of the execution. 
  char *parv[PARC+1]; // The input parameters. 
 
  ReadArgs(argc,argv,PARC,FINC,FOUTC,&mask, objin,objs,objout,objd,parv,USAGE,MASK); 

  if ( objs[0]->Type() == Po_Img2dsf ){
     Img2dsf* const ims=(Img2dsf*)objs[0];
     objd[0]=new Collection; 
     Collection* const curv=(Collection*)objd[0];
     result=FCT(*ims,*curv,atol(parv[0]),atol(parv[1]));
  } else {
     PrintErrorFormat(objin,FINC);
     result = FAILURE;
  }	
  
  if (result)
     WriteArgs(argc,argv,PARC,FINC,FOUTC,&mask,objin,objs,objout,objd,MASK);
  Exit(result);
  return 0;
}
#endif
