/* -*- 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 Jalal Fadili - 02-02-2005
 * @author Christophe Glinel - 20-07-2005
 */

/**
 * @file udwt.cpp
 *
 * 2D Undecimated Discrete Wavelet Transform (UDWT).
 * The UDWT is implemented only with the 
 * 2D gray level images.
 *
 * Transformée discrète par ondelettes non décimées. (UDWT)
 * La UDWT implémentée ici ne traite que le cas des images
 * 2D de niveaux de gris.
 */

#include "wutils.cpp"

/**
 * Convolution of the input vector x_in by a pair of 
 * QMF filters ~h and ~g
 * lh: length of the filter  h.
 * x_outl: approximation.
 * x_outh: detail.
 */
void fpconv(Double *x_in, int lx, Float *h, Float *g, int lh, Double *x_outl, Double *x_outh) {
   int i, j;
   Double x0, x1;
	
   for (i=lx; i < lx+lh-1; i++)
      x_in[i] = x_in[i-lx];
	
   for (i=0; i<lx; i++){
      x0 = x1 = 0;
      for (j=0; j<lh; j++){
	 x0 = x0 + x_in[j+i]*h[lh-1-j];
	 x1 = x1 + x_in[j+i]*g[lh-1-j];
      }
      x_outl[i] = x0;
      x_outh[i] = x1;
   }
}

/**
 * 2D Undecimated Wavelet Transform
 * Inputs :
 * 	ims    : The input image.
 * 	lh     : The length of the filter h.
 * 	x_outl : Approximation.
 * 	x_outh : Detail.
 */
template<typename U,typename V>
void mr2dwt(Img2d<U> &ims, int m, int n, Filter &filter, int L,Img3d<V> &imd) {	
   Double *ydummyll, *ydummylh, *ydummyhl;
   Double *ydummyhh, *xdummyl , *xdummyh;
   Long i;
   int actual_L, actual_m, actual_n, c_o_a, ir, n_c, n_cb;
   int ic, n_r, n_rb, c_o_a_p2n, sample_f;
	
   xdummyl  = new Double[MAX(m,n)+filter.Size-1];
   xdummyh  = new Double[MAX(m,n)+filter.Size-1];
   ydummyll = new Double[MAX(m,n)];
   ydummylh = new Double[MAX(m,n)];
   ydummyhl = new Double[MAX(m,n)];
   ydummyhh = new Double[MAX(m,n)];
	
   actual_m = 2*m;
   actual_n = 2*n;
	
   // The actual image is the approximation at the finest scale.
   copy(imd.Vector(),ims.Vector(),m*n);
	
   /* main loop */
   sample_f = 1;
   for (actual_L=1; actual_L <= L; actual_L++){
      actual_m = actual_m/2;
      actual_n = actual_n/2;
      c_o_a = 3*n*(actual_L-1);
      c_o_a_p2n = c_o_a + 2*n;
	
      /* go by rows */
      n_cb = n/actual_n;                 /* # of column blocks per row */
      for (ir = 0; ir < m; ir++){        /* loop over rows */
	 for (n_c = 0; n_c < n_cb; n_c++){  /* loop within one row */      
		
	    /* store in dummy variable */
	    ic = -sample_f + n_c;
	    for (i = 0; i < actual_n; i++){    
	       ic = ic + sample_f;
	       xdummyl[i] = imd[0][ir][ic];
	    }
	    
	    /* perform filtering lowpass/highpass */
	    fpconv(xdummyl, actual_n, filter.H, filter.G, filter.Size, ydummyll, ydummyhh); 
		
	    /* restore dummy variables in matrices */
	    ic = -sample_f + n_c;
	    for  (i=0; i<actual_n; i++){    
	       ic = ic + sample_f;
	       imd[0][ir][ic] = (Float)ydummyll[i];	    
	       imd[3*(actual_L-1)+1][ir][ic] = (Float)ydummyhh[i];
	    } 
	 }
      }
	
      /* go by columns of 2D images*/
      n_rb = m/actual_m;               /* # of row blocks per column */
      for (ic = 0; ic < n; ic++){      /* loop over column */
	 for (n_r=0; n_r<n_rb; n_r++){    /* loop within one column */
		
	    /* store in dummy variables */
	    ir = -sample_f + n_r;
	    for (i = 0; i < actual_m; i++){    
	       ir = ir + sample_f;
	       xdummyl[i] = imd[0][ir][ic];  
	       xdummyh[i] = imd[3*(actual_L-1)+1][ir][ic];  
	    }
		
	    /* perform filtering: first LL/LH, then HL/HH */
	    fpconv(xdummyl, actual_m, filter.H, filter.G, filter.Size, ydummyll, ydummylh); 
	    fpconv(xdummyh, actual_m, filter.H, filter.G, filter.Size, ydummyhl, ydummyhh); 
		
	    /* restore dummy variables in matrices */
	    ir = -sample_f + n_r;
	    for (i=0; i<actual_m; i++){    
	       ir = ir + sample_f;
	       imd[0][ir][ic] = (Float)ydummyll[i];  
	       imd[3*(actual_L-1)+1][ir][ic] = (Float)ydummylh[i];  
	       imd[3*(actual_L-1)+2][ir][ic] = (Float)ydummyhl[i];  
	       imd[3*(actual_L-1)+3][ir][ic] = (Float)ydummyhh[i];  
	    }
	 }
      }
      sample_f = sample_f*2;
   }
	
   delete []xdummyl;
   delete []xdummyh;
   delete []ydummyll;
   delete []ydummylh;
   delete []ydummyhl;
   delete []ydummyhh;
}


/*     
 * Undecimated Wavelet Transform
 * Integer 2D gray image. (Img2duc)
 * Images MUST have their dimensions all powers of 2 -> reshape.
 * The dimensions of the input and output must be the same.
 * Inputs :
 * 	ims   : The input image.
 * 	q     : The QMF.
 * 	scale : The scale of the transform.
 */
Errc UDWT(Img2duc & ims, Qmf &q, Img3dsf & imd, Long scale) {
   Long n  = imd.Width();
   Long m  = imd.Height();
   UdwtFilter filter;
   
   if (imd.Depth() != (3*scale+1)) {
      std::cerr<<"ERROR: Output image do not have the proper depth\n";
      Exit(FAILURE);
   }
   if (scale >= nextpow2(MIN(m,n))) {
      std::cerr<<"The scale is not correct : scale > nexpow2(N)"<<std::endl;
      Exit(FAILURE);
   }

   filter.LoadFilter(q);
   // Decomposition
   mr2dwt(ims, m, n, filter, scale, imd);
   return SUCCESS;
}
/*     
 * Undecimated Wavelet Transform
 * Integer 2D gray image. (Img2dsl)
 * Images MUST have their dimensions all powers of 2 -> reshape.
 * The dimensions of the input and output must be the same.
 * Inputs :
 * 	ims   : The input image.
 * 	q     : The QMF.
 * 	scale : The scale of the transform.
 */
Errc UDWT(Img2dsl & ims, Qmf &q, Img3dsf & imd, Long scale) {
   Long n  = imd.Width();
   Long m  = imd.Height();
   UdwtFilter filter;
   
   if (imd.Depth() != (3*scale+1)) {
      std::cerr<<"ERROR: Output image do not have the proper depth\n";
      Exit(FAILURE);
   }
   if (scale >= nextpow2(MIN(m,n))) {
      std::cerr<<"The scale is not correct : scale > nexpow2(N)"<<std::endl;
      Exit(FAILURE);
   }

   filter.LoadFilter(q);
   // Decomposition
   mr2dwt(ims, m, n, filter, scale, imd);
   return SUCCESS;
}
/*     
 * Undecimated Wavelet Transform
 * Integer 2D gray image. (Img2dsf)
 * Images MUST have their dimensions all powers of 2 -> reshape.
 * The dimensions of the input and output must be the same.
 * Inputs :
 * 	ims   : The input image.
 * 	q     : The QMF.
 * 	scale : The scale of the transform.
 */
Errc UDWT(Img2dsf & ims, Qmf &q, Img3dsf & imd, Long scale) {
   Long n  = imd.Width();
   Long m  = imd.Height();
   UdwtFilter filter;
   
   if (imd.Depth() != (3*scale+1)) {
      std::cerr<<"ERROR: Output image do not have the proper depth\n";
      Exit(FAILURE);
   }
   if (scale >= nextpow2(MIN(m,n))) {
      std::cerr<<"The scale is not correct : scale > nexpow2(N)"<<std::endl;
      Exit(FAILURE);
   }

   filter.LoadFilter(q);
   // Decomposition
   mr2dwt(ims, m, n, filter, scale, imd);
   return SUCCESS;
}
#ifdef MAIN
#define USAGE   "USAGE : %s  scale [col_in|-] [im_in|-] [im_out|-]"
#define PARC    1
#define FINC    2
#define FOUTC   1
#define MASK    0
int main( int argc, char *argv[] ) {
   Errc result;              // The result code of the execution.
   Pobject* mask;            // The region map.
   Pobject* objin[FINC+1];   // The input objects;
   Pobject* objs[FINC+1];    // The source objects masked.
   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_Collection && objs[1]->Type() == Po_Img2duc ){
      Collection * const colin = (Collection *) objs[0];
      Img2duc* const ims   = (Img2duc *)objs[1];
      Check(*ims);
      objd[0]  = new Img3dsf( 3*atoi(parv[0])+1, ims->Height(), ims->Width());
      Img3dsf* const imd = (Img3dsf*)objd[0];
      Qmf q(*colin);
      result = UDWT(*ims, q, *imd, atol(parv[0]));
      goto end;
   }
   else 
   if ( objs[0]->Type() == Po_Collection && objs[1]->Type() == Po_Img2dsl ){
      Collection * const colin = (Collection *) objs[0];
      Img2dsl* const ims   = (Img2dsl *)objs[1];
      Check(*ims);
      objd[0]  = new Img3dsf( 3*atoi(parv[0])+1, ims->Height(), ims->Width());
      Img3dsf* const imd = (Img3dsf*)objd[0];
      Qmf q(*colin);
      result = UDWT(*ims, q, *imd, atol(parv[0]));
      goto end;
   }
   else 
   if ( objs[0]->Type() == Po_Collection && objs[1]->Type() == Po_Img2dsf ){
      Collection * const colin = (Collection *) objs[0];
      Img2dsf* const ims   = (Img2dsf *)objs[1];
      Check(*ims);
      objd[0]  = new Img3dsf( 3*atoi(parv[0])+1, ims->Height(), ims->Width());
      Img3dsf* const imd = (Img3dsf*)objd[0];
      Qmf q(*colin);
      result = UDWT(*ims, q, *imd, atol(parv[0]));
      goto end;
   }
   else 
  {
     PrintErrorFormat(objin,FINC);
     result = FAILURE;
  }	

end:
  if (result)
	WriteArgs(argc,argv,PARC,FINC,FOUTC,&mask,objin,objs,objout,objd,MASK);
  Exit(result);
  return 0;
}
#endif
