/*------------------------------------------------------------------------------------

  File        : inrcast.cpp

  Description : 1D/2D/3D Image converter and visualizer based on command line arguments

  Author      : David Tschumperlé
    
  This software is governed by the CeCILL  license under French law and
  abiding by the rules of distribution of free software.  You can  use, 
  modify and/ or redistribute the software under the terms of the CeCILL
  license as circulated by CEA, CNRS and INRIA at the following URL
  "http://www.cecill.info". 
  
  As a counterpart to the access to the source code and  rights to copy,
  modify and redistribute granted by the license, users are provided only
  with a limited warranty  and the software's author,  the holder of the
  economic rights,  and the successive licensors  have only  limited
  liability. 
  
  In this respect, the user's attention is drawn to the risks associated
  with loading,  using,  modifying and/or developing or reproducing the
  software by the user in light of its specific status of free software,
  that may mean  that it is complicated to manipulate,  and  that  also
  therefore means  that it is reserved for developers  and  experienced
  professionals having in-depth computer knowledge. Users are therefore
  encouraged to load and test the software's suitability as regards their
  requirements in conditions enabling the security of their systems and/or 
  data to be ensured and,  more generally, to use and operate it in the 
  same conditions as regards security. 
  
  The fact that you are presently reading this means that you have had
  knowledge of the CeCILL license and that you accept its terms.
   		
  -----------------------------------------------------------------------------------*/

#include "../CImg.h"
using namespace cimg_library;
// The lines below are necessary with compilers that do not respect the C++ standart.
#if ( defined(_MSC_VER) && _MSC_VER<=1200 ) || defined(__DMC__)
#define std
#endif

//---------------------------------------------
// Convert to output image, save and visualize
//---------------------------------------------
template<typename T,typename t> void output_file(int argc,const char **argv,CImgl<T>& src,const t& pixel_type) {

  // Read command line arguments
  const char* typei = cimg_option("-i","float","Input pixel type (can be : char,uchar,ushort,short,uint,int,ulong,long,float,double)");
  cimg_option("-t",typei,"Output pixel type (can be : char,uchar,ushort,short,uint,int,ulong,long,float,double)");
  const bool a_orient   = cimg_option("-orient",false,"Compute vector orientations");
  const bool a_norm     = cimg_option("-norm",false,"Compute vector norms");
  const float a_rotate  = (float)cimg_option("-angle",0.0f,"Rotate image(s) (angle in degrees)");
  const char  a_flip    = cimg_option("-flip",'\0',"Flip image(s) along axis (can be 'x','y','z' or 'v')");
  const float a_blur    = (float)cimg_option("-blur",0.0f,"Blur image(s) (variance)");
  const float a_noiseg  = (float)cimg_option("-ng",0.0f,"Add gaussian noise (variance)");
  const float a_noiseu  = (float)cimg_option("-nu",0.0f,"Add uniform noise (variance)");
  const float a_noises  = (float)cimg_option("-ns",0.0f,"Add salt&pepper noise (percentage)");
  const char *a_cut     = cimg_option("-cut",(const char*)NULL,"Pixel value cutting (ex : '0,255')");
  const char *a_vnorm   = cimg_option("-val",(const char*)NULL,"Pixel value normalization (ex : '0,255')");
  const int a_quantify  = cimg_option("-quantify",0,"Uniform quantification (number of levels)");
  const char *a_bcrop   = cimg_option("-beg",(const char*)NULL,"Upper-left coordinates crop (2D,3D or 4D coordinates)");
  const char *a_ecrop   = cimg_option("-end",(const char*)NULL,"Lower-right coordinates crop (2D,3D or 4D coordinates");
  const char *a_resize  = cimg_option("-g",(const char*)NULL,"Resize image(s) (ex : '100x100' or '50%x30%')");
  const int a_interp    = cimg_option("-interp",3,"Resizing interpolation type (0=none, 1=bloc, 2=mosaic, 3=linear, 4=grid, 5=cubic");
  const bool a_equalize = cimg_option("-equalize",false,"Equalize histogram");
  const float a_mul     = (float)cimg_option("-mul",1.0f,"Multiply pixel values by a float constant");
  const float a_add     = (float)cimg_option("-add",0.0f,"Add a float constant to pixel values");
  const int a_erode     = cimg_option("-erode",0,"Erode image(s) (number of steps)");
  const int a_dilate    = cimg_option("-dilate",0,"Dilate image(s) (number of steps)");
  const int a_histo     = cimg_option("-histogram",0,"Compute image histogram (number of levels)");
  const char *a_break   = cimg_option("-b",(const char*)NULL,"Break image along axis (can be 'z', 'x3', 'y2', etc...)");
  const char a_append   = cimg_option("-a",'\0',"Append images along axis (can be 'x','y','z' or 'v')");
  const bool a_ucrop    = cimg_option("-crop",false,"User-selected crop");
  const char *a_annot   = cimg_option("-annotate",(char*)NULL,"Add annotation to image(s)");
  const bool a_reverse  = cimg_option("-r",false,"Reverse image list");
  const char *file_o    = cimg_option("-o",(const char*)NULL,"Output file");
  const bool a_visu     = cimg_option("-visu",false,"Image visualization");
  if (!src.size) exit(0);

  // Display image statistics
  std::fprintf(stderr,"\n- Input image(s) statistics :\n");
  src.print("\tInput");

  // Vector orientation computation
  if (a_orient) {
    std::fprintf(stderr,"\n- Vector orientation computation\t"); std::fflush(stderr);
    cimgl_map(src,l) src[l].orientation_pointwise();
  }

  // Vector norm computation
  if (a_norm) {
    std::fprintf(stderr,"\n- Vector norm computation\t"); std::fflush(stderr);
    cimgl_map(src,l) src[l].norm_pointwise();
  }

  // Image rotation
  if (a_rotate!=0) {
    std::fprintf(stderr,"\n- Image rotation ( %g degrees )\t",a_rotate); std::fflush(stderr);
    cimgl_map(src,l) src[l].rotate(a_rotate,3);
  }
  
  // Flip Image
  if (a_flip!='\0') {
    std::fprintf(stderr,"\n- Flip image along axis '%c'\t",a_flip); std::fflush(stderr);
    cimgl_map(src,l) src[l].flip(a_flip);
  }

  // Image blur
  if (a_blur!=0) {
    std::fprintf(stderr,"\n- Image blur ( sigma = %g )\t",a_blur); std::fflush(stderr);
    cimgl_map(src,l) src[l].blur(a_blur);
  }

  // Image noise
  if (a_noiseg) {
    std::fprintf(stderr,"\n- Add gaussian noise ( variance = %g )\t",a_noiseg); std::fflush(stderr);
    cimgl_map(src,l) src[l].noise(a_noiseg,0);
  }
  if (a_noiseu) {
    std::fprintf(stderr,"\n- Add uniform noise ( variance = %g )\t",a_noiseu); std::fflush(stderr);
    cimgl_map(src,l) src[l].noise(a_noiseu,1);
  }
  if (a_noises) {
    std::fprintf(stderr,"\n- Add uniform noise ( variance = %g )\t",a_noises); std::fflush(stderr);
    cimgl_map(src,l) src[l].noise(a_noises,2);
  }

  // Pixel value cutting
  if (a_cut) {
    std::fprintf(stderr,"\n- Pixel cutting in [%s]\t",a_cut); std::fflush(stderr);
    cimgl_map(src,l) {
      CImg<T>& img=src[l];
      CImgStats stats(img);
      double vmin=stats.min, vmax=stats.max;
      std::sscanf(a_cut,"%lg%*c%lg",&vmin,&vmax);
      img.cut((T)vmin,(T)vmax);
    }
  }
  
  // Pixel value normalization
  if (a_vnorm) {
    std::fprintf(stderr,"\n- Pixel normalization in [%s]\t",a_vnorm); std::fflush(stderr);
    cimgl_map(src,l) {
      CImg<T>& img=src[l];
      CImgStats stats(img);
      double vmin=stats.min, vmax=stats.max;
      std::sscanf(a_vnorm,"%lg%*c%lg",&vmin,&vmax);
      img.normalize((T)vmin,(T)vmax);
    }
  }

  // Image quantification
  if (a_quantify) {
    std::fprintf(stderr,"\n- Quantify image in %d levels\t",a_quantify); std::fflush(stderr);
    cimgl_map(src,l) src[l].quantify(a_quantify);
  }

  // Image cropping
  if (a_bcrop || a_ecrop) {
    std::fprintf(stderr,"\n- Crop image(s) to %s-%s\t",a_bcrop?a_bcrop:"*",a_ecrop?a_ecrop:"*"); std::fflush(stderr);
    cimgl_map(src,l) {
      CImg<T> &img=src[l];
      int x0=0,y0=0,z0=0,v0=0,x1=img.dimx()-1,y1=img.dimy()-1,z1=img.dimz()-1,v1=img.dimv()-1;
      char buf[16];
      if (a_bcrop) std::sscanf(a_bcrop,"%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]",&x0,buf+0,&y0,buf+1,&z0,buf+2,&v0,buf+3);
      if (a_ecrop) std::sscanf(a_ecrop,"%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]",&x1,buf+4,&y1,buf+5,&z1,buf+6,&v1,buf+7);
      if (buf[0]=='%') x0 = -x0*img.dimx()/100;
      if (buf[1]=='%') y0 = -y0*img.dimy()/100;
      if (buf[2]=='%') z0 = -z0*img.dimz()/100;
      if (buf[3]=='%') v0 = -v0*img.dimv()/100;
      if (buf[4]=='%') x1 = -x1*img.dimx()/100;
      if (buf[5]=='%') y1 = -y1*img.dimy()/100;
      if (buf[6]=='%') z1 = -z1*img.dimz()/100;
      if (buf[7]=='%') v1 = -v1*img.dimv()/100;
      if (x0>x1) cimg::swap(x0,x1);
      if (y0>y1) cimg::swap(y0,y1);
      if (z0>z1) cimg::swap(z0,z1);
      if (v0>v1) cimg::swap(v0,v1);
      if (x0>=img.dimx()) x0=img.dimx()-1;
      if (y0>=img.dimy()) y0=img.dimy()-1;
      if (z0>=img.dimz()) z0=img.dimz()-1;
      if (v0>=img.dimv()) v0=img.dimv()-1;
      if (x1>=img.dimx()) x1=img.dimx()-1;
      if (y1>=img.dimy()) y1=img.dimy()-1;
      if (z1>=img.dimz()) z1=img.dimz()-1;
      if (v1>=img.dimv()) v1=img.dimv()-1;
      img.crop(x0,y0,z0,v0,x1,y1,z1,v1);
    }
  }
  
  // Image resizing
  if (a_resize) {
    std::fprintf(stderr,"\n- Resize image(s) to '%s'\t",a_resize); std::fflush(stderr);
    int dimx=-100,dimy=-100,dimz=-100,dimv=-100;
    char buf[16];
    std::sscanf(a_resize,"%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]%d%15[^0-9]",&dimx,buf,&dimy,buf+1,&dimz,buf+2,&dimv,buf+3);
    if (buf[0]=='%') dimx=-dimx;
    if (buf[1]=='%') dimy=-dimy;
    if (buf[2]=='%') dimz=-dimz;
    if (buf[3]=='%') dimv=-dimv;
    cimgl_map(src,l) src[l].resize(dimx,dimy,dimz,dimv,a_interp);
  }

  if (a_equalize) {
    std::fprintf(stderr,"\n- Equalize histogram\t"); std::fflush(stderr);
    cimgl_map(src,l) src[l].equalize_histogram(4096);
  }
  
  // Multiplication of pixel values
  if (a_mul!=1) {
    std::fprintf(stderr,"\n- Multiply pixel values by %g\t",a_mul); std::fflush(stderr);
    cimgl_map(src,l) src[l]*=a_mul;
  }
  
  // Add a constant to pixel values
  if (a_add!=0) {
    std::fprintf(stderr,"\n- Add %g to pixel values\t",a_add); std::fflush(stderr);
    cimgl_map(src,l) src[l]+=(T)a_add;
  }

  // Erode image
  if (a_erode) {
    std::fprintf(stderr,"\n- Erode image(s) %d times",a_erode); std::fflush(stderr);
    cimgl_map(src,l) src[l].erode(a_erode);
  }

  // Dilate image
  if (a_dilate) {
    std::fprintf(stderr,"\n- Dilate image(s) %d times",a_dilate); std::fflush(stderr);
    cimgl_map(src,l) src[l].dilate(a_dilate);
  }

  // Compute image histogram
  if (a_histo) {
    std::fprintf(stderr,"\n- Compute histogram(s)\t"); std::fflush(stderr);
    cimgl_map(src,l) src[l] = src[l].get_histogram(a_histo);
  }

  // Image break
  if (a_break) {
    int nb = 0;
    std::sscanf(a_break,"%*[xyzvXYZV]%d",&nb);
    std::fprintf(stderr,"\n- Break image along axis '%c'\t",a_break[0]); std::fflush(stderr);
    CImgl<T> tmp;
    cimgl_map(src,l) tmp.insert(src[l].get_split(a_break[0],nb));
    src = tmp;
  }
  
  // Images append
  if (a_append!='\0') {
    std::fprintf(stderr,"\n- Append images along axis '%c'\t",a_append); std::fflush(stderr);
    src = CImgl<T>(src.get_append(a_append,'p'));    
  }

  // User-selected crop
  if (a_ucrop) {
    std::fprintf(stderr,"\n- User crop : Please select the image region to crop.\t"); std::fflush(stderr);
    int selection[6] = {-1,-1,-1,-1,-1,-1};
    src[0].feature_selection(selection,2);
    if (selection[0]<0 || selection[1]<0 || selection[2]<0 ||
	selection[3]<0 || selection[4]<0 || selection[5]<0) std::fprintf(stderr,".. Aborted..");
    else {
      std::fprintf(stderr,"-> Crop [%d,%d,%d]-[%d,%d,%d]",
		   selection[0],selection[1],selection[2],
		   selection[3],selection[4],selection[5]);
      cimgl_map(src,l) src[l].crop(selection[0],selection[1],selection[2],selection[3],selection[4],selection[5]);
    }
  }

  // Annotate images
  if (a_annot) {
    std::fprintf(stderr,"\n- Annotate image with \"%s\".\t",a_annot); std::fflush(stderr);
    cimgl_map(src,l) {
      const CImg<T> col(src[l].dimv(),1,1,1,(T)CImgStats(src[l],false).max);     
      src[l].draw_text(a_annot,5,5,col.ptr());
    }
  }

  // Reverse image list
  if (a_reverse) {
    std::fprintf(stderr,"\n- Reverse image list"); std::fflush(stderr);
    src.reverse();
  }

  // Convert image to destination type
  std::fprintf(stderr,"\n- Conversion to '%s' pixels\t",cimg::get_type(pixel_type)); std::fflush(stderr);
  CImgl<t> dest(src);
  std::fprintf(stderr,"\n- Output image(s) :\n");
  dest.print("Output");

  // Save destination image
  if (file_o) {    
    std::fprintf(stderr,"\n- Save image(s) to '%s'\t",file_o); std::fflush(stderr);
    dest.save(file_o);
  }  

  // Display destination image
  if (a_visu || !file_o) {
    std::fprintf(stderr,"\n- Visualize image(s)\n"); std::fflush(stderr);
    if ( dest.size==1 && (
	 ( dest[0].dimy()==1 && dest[0].dimz()==1  ) ||
	 ( dest[0].dimx()==1  && dest[0].dimz()==1  ) ||
	 ( dest[0].dimx()==1  && dest[0].dimy()==1 ) ||
	 ( dest[0].dimx()==1  && dest[0].dimy()==1 && dest[0].dimz()==1))) { // 1D plot representation
      const CImg<t>& img = dest[0].dimv()<=8?dest[0]:dest[0].get_resize(-100,-100,-100,8,0);
      CImgStats stats(img,false);
      const CImg<unsigned char> palette = CImg<unsigned char>(3,img.dimv(),1,1,0).noise(300,1);
      const unsigned char gray[3]={128,128,128},white[3]={255,255,255};
      unsigned long trame = 0x0F0F0F0F;
      char message[1024];
      palette[0]=255; palette[1]=0; palette[2]=0;
      if (img.dimv()>1) { palette[3]=0; palette[4]=255; palette[5]=0; }
      if (img.dimv()>2) { palette[6]=0; palette[7]=0; palette[8]=255; }
      CImgDisplay disp(500,300,"1D plot of the output image",0);
      CImg<unsigned char> visu0;
      do {
	if (!visu0.data || disp.resized) { // plot graphics
	  disp.resize();
	  visu0 = CImg<unsigned char>(disp.dimx(),disp.dimy(),1,3,0);
	  if (img.dimv()==1) visu0.draw_graph(img,palette.ptr(),1,stats.max,stats.min,0.4f);
	  cimg_mapV(img,k) visu0.draw_graph(img.ref_channel(k),&palette(0,k),0,stats.max,stats.min);
	  visu0.draw_axeXY(0,img.size(),stats.max,stats.min,gray);
	}
	CImg<unsigned char> visu(visu0);
	if (disp.mousex>=0) {
	  visu.draw_line(disp.mousex,0,disp.mousex,visu.dimy()-1,gray,trame,0.5);
	  trame = cimg::rol(trame);
	  const unsigned x = disp.mousex*img.size()/(img.dimv()*disp.dimx());
	  std::sprintf(message,"x=%d\n[ ",x);
	  cimg_mapV(img,k) std::sprintf(message+std::strlen(message),"%s%g%s",k==0?"":"  ",
					(float)img(x,0,0,k),k==img.dimv()-1?"":"\n");
	  std::sprintf(message+std::strlen(message)," %s]",dest[0].dimv()<=8?"":"\n...");
	  visu.draw_text(message,disp.mousex-20,10,white,NULL,1);
	} else visu=visu0;	
	disp.display(visu).wait(40);	
      } while (!disp.closed && !disp.key);
    }
    else dest.display("inrcast");
  }
}

//---------------------------------------------------------
// Read input image and do conversion to output pixel type
//---------------------------------------------------------
template<typename T> void input_file(int argc,const char **argv,const T& pixel_type) {
  
  // Read input image list
  std::fprintf(stderr,"- Load images...\t"); std::fflush(stderr);
  CImgl<T> src;
  bool opt = false;
  for (int k=1; k<argc; k++)
    if (argv[k][0]=='-' && argv[k][1]!='\0') opt=true;
    else {
      if (!opt ||
	  !cimg::strcasecmp(argv[k-1],"-h") ||
	  !cimg::strcasecmp(argv[k-1],"-orient") ||
	  !cimg::strcasecmp(argv[k-1],"-norm") ||
	  !cimg::strcasecmp(argv[k-1],"-equalize") ||
	  !cimg::strcasecmp(argv[k-1],"-crop") ||
	  !cimg::strcasecmp(argv[k-1],"-plot") ||
	  !cimg::strcasecmp(argv[k-1],"-visu")
	  ) {
	std::fprintf(stderr,"\n\t> Loading [%d]='%s'\t",k-1,argv[k]); std::fflush(stderr);
	src.insert(CImgl<T>::load(argv[k])); }
      opt=false;
    }
  std::fputc('\n',stderr);
  if (!src.size) { 
    std::fprintf(stderr,"\n** You must specify at least one input image\n** Try '%s -h' to get help\n**\n",cimg::basename(argv[0]));
    unsigned char x=0;
    CImgl<unsigned char> tmp;
    output_file(argc,argv,tmp,x); 
  }
  else {
    // Convert to output pixel type (if specified)
    const char *typei = cimg_option("-i","float",NULL);
    const char *typeo = cimg_option("-t",typei,NULL);
    bool saved = false;
    if (!saved && !cimg::strcasecmp(typeo,"char")) { char t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"uchar")) { unsigned char t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"ushort")) { unsigned short t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"short")) { short t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"uint")) { unsigned int t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"int")) { int t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"ulong")) { unsigned long t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"long")) { long t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"float")) { float t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved && !cimg::strcasecmp(typeo,"double")) { float t=0; output_file(argc,argv,src,t); saved=true; }
    if (!saved) throw CImgException("Cannot save image with pixel type '%s' (see help)",typeo);
  }
}

//----------------
// Main procedure
//----------------
int main(int argc,const char **argv) {
  cimg_usage("1D/2D/3D Image(s) converter and visualizer.\n\t\tusage : inrcast [options] image_in1 [image_in2] [...] [-o image_out]");
  if (argc==1) { std::fprintf(stderr,"Try '%s -h' to get help\n",cimg::basename(argv[0])); exit(0); }
  
  // Read input image and call converter
  bool casted = false;
  const char *typei = cimg_option("-i","float",NULL);
  if (!casted && !cimg::strcasecmp(typei,"char")) { char t=0; input_file(argc,argv,t); casted=true; }
  if (!casted && !cimg::strcasecmp(typei,"uchar")) { unsigned char t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"ushort")) { unsigned short t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"short")) { short t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"uint")) { unsigned int t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"int")) { int t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"ulong")) { unsigned long t=0; input_file(argc,argv,t); casted=true; }
  if (!casted && !cimg::strcasecmp(typei,"long")) { long t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"float")) { float t=0; input_file(argc,argv,t); casted=true;}
  if (!casted && !cimg::strcasecmp(typei,"double")) { float t=0; input_file(argc,argv,t); casted=true;}
  if (!casted) throw CImgException("Cannot load image pixel type '%s' (see help)",typei);

  // Return to shell
  std::fprintf(stderr,"Done !\n");
  exit(0);
  return 0;
}
