
#/*##########################################################################
# Copyright (C) 2001-2013 European Synchrotron Radiation Facility
#
#              PyHST2  
#  European Synchrotron Radiation Facility, Grenoble,France
#
# PyHST2 is  developed at
# the ESRF by the Scientific Software  staff.
# Principal author for PyHST2: Alessandro Mirone.
#
# This program is free software; you can redistribute it and/or modify it 
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option) 
# any later version.
#
# PyHST2 is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# PyHST2; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
# Suite 330, Boston, MA 02111-1307, USA.
#
# PyHST2 follows the dual licensing model of Trolltech's Qt and Riverbank's PyQt
# and cannot be used as a free plugin for a non-free program. 
#
# Please contact the ESRF industrial unit (industry@esrf.fr) if this license 
# is a problem for you.
#############################################################################*/
using namespace std;

// #include<iostream.h>


#include <stdio.h>
#include <stdlib.h>
#include<math.h>
#include <cuda.h>
#include <cublas.h>
#include <cuComplex.h>
#include<time.h>
#define FROMCU
extern "C" {
#include<CCspace.h>
}
// #include <cutil.h>
#include<assert.h>
#  define CUDA_SAFE_CALL_NO_SYNC( call) {                                    \
    cudaError err = call;                                                    \
    if( cudaSuccess != err) {                                                \
        fprintf(stderr, "Cuda error in file '%s' in line %i : %s.\n",        \
                __FILE__, __LINE__, cudaGetErrorString( err) );              \
        exit(EXIT_FAILURE);                                                  \
    } }

#  define CUDA_SAFE_CALL( call)     CUDA_SAFE_CALL_NO_SYNC(call);                                            \

//Added required header and error handle macro for CUFFT. CUFFT returns DIFFERENT TYPE of error messages.

#include <cufft.h>
#define CUDA_SAFE_FFT(call){                                                   \
    cufftResult err = call;                                                    \
    if( CUFFT_SUCCESS != err) {                                                \
        fprintf(stderr, "Cuda error in file '%s' in line %i : %d.\n",          \
                __FILE__, __LINE__, err );                                     \
        exit(EXIT_FAILURE);                                                    \
    } }




#define CUFFT_SAFE_CALL(call, extramsg) {do {	\
       cufftResult err = call;    \
       if( CUFFT_SUCCESS  != err) {   \
	 if(err==   CUFFT_INVALID_PLAN   )				\
	   sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
                   __FILE__, __LINE__, "CUFFT_INVALID_PLAN"    ,  extramsg); \
	 if(err==  CUFFT_ALLOC_FAILED   )				\
	   sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
                   __FILE__, __LINE__, "CUFFT_ALLOC_FAILED"    ,  extramsg); \
	 if(err==  CUFFT_INVALID_TYPE   )				\
	   sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
                   __FILE__, __LINE__,  "CUFFT_INVALID_TYPE"  ,  extramsg); \
	 if(err==  CUFFT_INVALID_VALUE  )				\
	   sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
                   __FILE__, __LINE__, "CUFFT_INVALID_VALUE"   ,  extramsg); \
	 if(err==    CUFFT_INTERNAL_ERROR)				\
	   sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
                   __FILE__, __LINE__, "CUFFT_INTERNAL_ERROR"   ,  extramsg); \
	 if(err==  CUFFT_EXEC_FAILED )					\
	   sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
                   __FILE__, __LINE__, "CUFFT_EXEC_FAILED"     ,  extramsg); \
	 if(err==    CUFFT_SETUP_FAILED )				\
	   sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
                   __FILE__, __LINE__, "CUFFT_SETUP_FAILED"   ,  extramsg); \
	 if(err==  CUFFT_INVALID_SIZE)					\
	   sprintf(messaggio, "Error in file '%s' in line %i :%s -- %s \n", \
                   __FILE__, __LINE__, "CUFFT_INVALID_SIZE"   ,  extramsg); \
	 fprintf(stderr, messaggio );					\
	 exit(EXIT_FAILURE);						\
	 								\
       } } while (0) ;}






// -----------------------------------------------------------------------------------------------
texture<float, 2, cudaReadModeElementType> texProjes;

cudaChannelFormatDesc floatTex = cudaCreateChannelDesc<float>();

__global__ static void    gputomo_kernel(int num_proj,int num_bins, float axis_position,
					 float *d_SLICE,
					 float gpu_offset_x, float gpu_offset_y	, float * d_cos_s , float * d_sin_s ,float *  d_axis_s
					 ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;
  
  __shared__  float   shared[768] ; 
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;

  float pcos, psin;
  float h;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05;
  float res;
  
  res=0;
  	
  const float bx = (16 * bidx  + 2 * tidx   + 0 +   apos_off_x  ) ;
  const float by = (16 * bidy  + 2 * tidy   + 0 +   apos_off_y  ) ;

  
  int lettofino=0;  // letto= read   fino=till
  for(int proje=0; proje<num_proj; proje++) {
    if(proje>=lettofino) {
      syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; // populating the shared memory
	sh_sin [ip] = d_sin_s[lettofino+ip] ; 
	sh_axis[ip] = d_axis_s[lettofino+ip] ; 
      }
      lettofino=lettofino+256; // 256=16*16 block size
      syncthreads();
    }
    pcos = sh_cos[ -(lettofino-256)+ proje] ;
    psin = sh_sin[-(lettofino-256)+ proje] ;
    
    acorr05=   sh_axis[-(lettofino-256) +proje] ; 
    
    h =  (acorr05+ 
-	  bx*pcos -
-	  by*psin
	  );
    if(h>=0 && h<num_bins) 
      res=res+tex2D(texProjes,h +0.5f,proje  +0.5f ) ;
    
  };
  d_SLICE[ 16*gridDim.x*(bidy*16+tidy) + bidx*16 + tidx     ] = res;
}
// ---------------------------------------

// extern "C" {
//   int gpu_mainInit(Gpu_Context * self, float *filter );
//   int gpu_main(Gpu_Context * self, float *WORK , float * SLICE);
// }

//Round a / b to nearest higher integer value
int iDivUp(int a, int b){
    return (a % b != 0) ? (a / b + 1) : (a / b);
}

//Align a to nearest higher multiple of b
int iAlignUp(int a, int b){
    return (a % b != 0) ?  (a - a % b + b) : a;
}

__global__ static void    gputomo_oversampling_kernel(int num_proj,int num_bins, float axis_position,
						      float *d_SLICE,
						      float gpu_offset_x, float gpu_offset_y	, 
						      float * d_cos_s , float * d_sin_s ,float *  d_axis_s,
						      int oversampling
						      ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;
  
  __shared__  float   shared[768] ; 
  float  * sh_sin  = shared;
  float  * sh_cos  = shared+256;
  float  * sh_axis = sh_cos+256;
  
  float pcos0, psin0;
  float pcos1, psin1;
  float h,h_0,h_1;
  //  l'asse e' dato a partire dall' angolo  primo pixel, ma i centri dei pixel sono nel mezzo
  const float apos_off_x= gpu_offset_x   -  axis_position ;
  const float apos_off_y= gpu_offset_y   -  axis_position ;
  float acorr05_0;
  float acorr05_1;
  float res;
  
  res=0;
  
  const float bx = (16 * bidx  + tidx    +   apos_off_x  ) ;
  const float by = (16 * bidy  + tidy    +   apos_off_y  ) ;


  int lettofino=0;
  for(int proje=0; proje<num_proj; proje++) {
    if(proje>=lettofino) {
      syncthreads();
      int ip = tidy*16+tidx;
      if( lettofino+ip < num_proj) {
	sh_cos [ip] = d_cos_s[lettofino+ip] ; 
	sh_sin [ip] = d_sin_s[lettofino+ip] ; 
	sh_axis[ip] = d_axis_s[lettofino+ip] ; 
      }
      lettofino=lettofino+256;
      syncthreads();
    }
    if(proje<num_proj-1) {
      pcos0 = sh_cos[ -(lettofino-256)+ proje] ;
      psin0 = sh_sin[-(lettofino-256)+ proje] ;
      pcos1 = sh_cos[ -(lettofino-256)+ proje+1] ;
      psin1 = sh_sin[-(lettofino-256)+ proje+1] ;
      acorr05_0 =   sh_axis[-(lettofino-256) +proje] ; 
      acorr05_1 =   sh_axis[-(lettofino-256) +proje+1] ; 
    } else {
      pcos0 = sh_cos[ -(lettofino-256)+ proje] ;
      psin0 = sh_sin[-(lettofino-256)+ proje] ;
      pcos1 = 2*sh_cos[ -(lettofino-256)+ proje]-sh_cos[ -(lettofino-256)+ proje-1]   ;
      psin1 = 2*sh_sin[-(lettofino-256)+ proje] -sh_sin[-(lettofino-256)+ proje-1]   ;
      acorr05_0 =   sh_axis[-(lettofino-256) +proje] ; 
      acorr05_1 =   2*sh_axis[-(lettofino-256) +proje]-sh_axis[-(lettofino-256) +proje-1]  ; 
    }
    
    for(int i=0; i<oversampling; i++) {
      h_0 =  (  acorr05_0  + 
		bx*pcos0 -
		by*psin0 
		) ;
      h_1 =  (  acorr05_1  + 
		bx*pcos1 -
		by*psin1 
		) ;
      h = (i*h_1+(oversampling-i)*h_0)/oversampling ;
      
      if(h>=0 && h<num_bins) 
	res=res+tex2D(texProjes,h +0.5f,proje +i*1.0f/oversampling +0.5f ) ;
    }
  };
  
  d_SLICE[ 16*gridDim.x*(bidy*16+tidy) + bidx*16 + tidx     ] = res/oversampling;
}





#define blsize_cufft 32
__global__   void kern_cufft_filter_talbot(int Hfft_size,cufftReal *dev_iWork,  int batch_size, int dim_fft){
  int gid;
  float tmp0,tmp1, fact;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<Hfft_size) {
    if(gid==0) {
      fact=0.0f;
    } else  {
      fact= 1.0f/ (gid*M_PI*2.0f*2.0f/dim_fft);
    } 
    for(int i=0;i<batch_size;i++){
      tmp0 =  dev_iWork[2*gid   + i*(2*Hfft_size)];
      tmp1 =  dev_iWork[2*gid+1 + i*(2*Hfft_size)];
      dev_iWork[2*gid   + i*(2*Hfft_size)] = +tmp1*fact/dim_fft ;
      dev_iWork[2*gid+1 + i*(2*Hfft_size)] = -tmp0*fact/dim_fft ;
    }
  }
  __syncthreads();
}


#define blsize_cufft 32
__global__   void kern_cufft_filter(int Hfft_size,cufftReal *dev_iWork, float *dev_Filter, int batch_size, int dim_fft){
  int gid;
  float filter;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<Hfft_size) {
    filter=dev_Filter[gid]/dim_fft;;
    for(int i=0;i<batch_size;i++){
      dev_iWork[2*gid   + i*(2*Hfft_size)] *= filter;
      dev_iWork[2*gid+1 + i*(2*Hfft_size)] *= filter;
      // dev_iWork[2*gid + i*(2*Hfft_size)]  = dev_iWork[2*gid + i*(2*Hfft_size)]/dim_fft;
      // dev_iWork[2*gid+1 + i*(2*Hfft_size)]  = dev_iWork[2*gid+1 + i*(2*Hfft_size)]/dim_fft;
    }
  }
  __syncthreads();
}

__global__   void kern_cufft_filter_corrpersymm(int Hfft_size,cufftReal *dev_iWork, int batch_size, int dim_fft){
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<Hfft_size) {
    if(gid) {
      for(int i=0;i<batch_size;i++){
	dev_iWork[2*gid   + i*(2*Hfft_size)] *= 2;
	dev_iWork[2*gid+1 + i*(2*Hfft_size)] *= 2;
      }
    }
  }
  __syncthreads();
}

__global__   void kern_recopy(int num_bins,int dim_fft,int batch_size, float *dev_Work_perproje,  float *dev_rWork
			      )  {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<num_bins) {
    for(int i=0;i<batch_size;i++){
      dev_Work_perproje[ gid + i*num_bins ] = dev_rWork[ gid + i*(dim_fft) ] ;
    }
  }
   __syncthreads();
}


__global__ static void    slicerot_kernel( int dimslice,
					  float *d_SLICE,
					  float dangle,
					  int duty_oversampling) {

  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;

  const float axis_position =  (dimslice-1 )/ 2.0f  ; 
  const float rx = (16* bidx  + tidx     ) - axis_position;
  const float ry = (16* bidy  + tidy     ) - axis_position;

  const float anglestep     =  dangle/(duty_oversampling)        ;
  const float angleshift    = -anglestep*(duty_oversampling-1)/2.0f ;
  
  float res=0;
  float rotangle;
  float x,y;
  for(int irot=0; irot<duty_oversampling ; irot++) {
    rotangle =  angleshift + irot *   anglestep  ; 
    float cc = cos(rotangle), ss = sin(rotangle)  ; 
    x   =   rx*cc  -  ry*ss  +   axis_position   ; 
    y   =   ry*cc  +  rx*ss  +   axis_position   ; 
    res =   res + tex2D(texProjes ,x + 0.5f, y  +0.5f ) ;
  }
  d_SLICE[ 16*gridDim.x*(bidy*16+tidy) + bidx*16 + tidx     ] = res/duty_oversampling  ;
}


__global__   void kern_recopy_slope(int num_bins,int dim_fft,int batch_size, float *dev_Work_perproje,  float *dev_rWork,
				    float overlapping, float pente_zone, float flat_zone,
				    float *d_axis_s,
				    float prof_shift, float prof_fact)  {
  int gid;
  float dx, peso, ax_pos_corr ;   
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<num_bins) {
    for(int i=0;i<batch_size;i++){
      ax_pos_corr = d_axis_s[ i ];
      if(  fabs(gid-  ax_pos_corr ) <= overlapping ) {
	if(  fabs(gid- ax_pos_corr ) <= flat_zone) {
	  dev_Work_perproje[ gid + i*num_bins ]=  dev_rWork[ gid + i*(dim_fft) ];
	} else {
	  if( gid> ax_pos_corr  ) {
	    dx = ax_pos_corr  - gid + flat_zone;
	  } else {
	    dx = ax_pos_corr  - gid - flat_zone; 
	  }
	  peso = prof_shift + prof_fact * (0.5*(1.0+ (        dx      )/pente_zone));
	  dev_Work_perproje[ gid + i*num_bins ] = peso*dev_rWork[ gid + i*(dim_fft) ]* 2;   
	}
      } else if (gid>ax_pos_corr ) {
	dev_Work_perproje[ gid + i*num_bins ] = prof_shift  * dev_rWork[ gid + i*(dim_fft) ]* 2;
      } else {
	dev_Work_perproje[ gid + i*num_bins ] = (prof_shift + prof_fact*1.0)  *   dev_rWork[ gid + i*(dim_fft) ]  * 2;
      }
      __syncthreads();
    }
  }
  //  __syncthreads();
}
__global__   void kern_recopy_slope_leastsquare(int num_bins,int dim_fft,int batch_size, float *dev_Work_perproje,  float *dev_rWork,
						float overlapping, float pente_zone, float flat_zone,
						float *d_axis_s,
						float prof_shift, float prof_fact)  {
  int gid;
  float dx, peso, ax_pos_corr ;   
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<num_bins) {
    for(int i=0;i<batch_size;i++){
      ax_pos_corr = d_axis_s[ i ];
      if(  fabs(gid-  ax_pos_corr ) <= overlapping ) {
	if(  fabs(gid- ax_pos_corr ) <= flat_zone) {
	  dev_Work_perproje[ gid + i*num_bins ]=  dev_rWork[ gid + i*(dim_fft) ];
	} else {
	  if( gid> ax_pos_corr  ) {
	    dx = ax_pos_corr  - gid + flat_zone;
	  } else {
	    dx = ax_pos_corr  - gid - flat_zone; 
	  }
	  peso = prof_shift + prof_fact * (0.5*(1.0+ (        dx      )/pente_zone));
	  dev_Work_perproje[ gid + i*num_bins ] = peso*dev_rWork[ gid + i*(dim_fft) ]* 2;   
	}
      } else if (gid>ax_pos_corr ) {
	dev_Work_perproje[ gid + i*num_bins ] = prof_shift  * dev_rWork[ gid + i*(dim_fft) ];
      } else {
	dev_Work_perproje[ gid + i*num_bins ] = (prof_shift + prof_fact*1.0)  *   dev_rWork[ gid + i*(dim_fft) ] ;
      }
      __syncthreads();
    }
  }
  //  __syncthreads();
}

__global__   void     kern_mult( cuFloatComplex  * d_fftwork, cuFloatComplex * d_kernelbuffer, int dimbuff ) {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid<dimbuff) {
    d_fftwork[gid]=cuCmulf(d_fftwork[gid],d_kernelbuffer[gid]) ;
  }
}



__global__  static  void   kern_set( float *d_w , float value, int npoints ) {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  if(gid <npoints ) {
    d_w[gid] = value;
  }
}

#define fftbunch 128


typedef struct {
  cudaStream_t  stream[2]; 
} Gpu_Streams ;

  
int gpu_mainInit(Gpu_Context * self, float *filter) {

    printf(" HELLO ZUBAIR! from gpu_mainInit\n");

  int icount=0;

  // creare qui gli stream usando i type cast come al solito da void 

  if(filter[0] > 1.0e6) { // this should never happen in normal cases

        filter[0]-=2.0e6; // remove the trick-out added term
  CUDA_SAFE_CALL(cudaMemcpy(self->dev_Filter,filter,sizeof(float)*(self->dim_fft),cudaMemcpyHostToDevice));

        return 1;
  }

	
  self->gpuctx = (void*)  malloc(sizeof(CUcontext)) ; 

  // cudaSetDeviceFlags(	cudaDeviceMapHost	) ;
  cudaSetDevice(self->MYGPU);

  cuCtxCreate( (CUcontext *) self->gpuctx	,
	       // CU_CTX_SCHED_YIELD || CU_CTX_MAP_HOST ,
	       CU_CTX_SCHED_SPIN ,
	       self->MYGPU	 
	       ) ;	
  // cudaSetDeviceFlags(	cudaDeviceMapHost	) ;
  //cuCtxGetCurrent ( (CUcontext *) self->gpuctx  ) ; 
  CUDA_SAFE_CALL(cudaMalloc((void **)&(self->dev_rWork)   ,sizeof(float)*self->dim_fft*iAlignUp(self->num_projections*self->VECTORIALITY,fftbunch)));

  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->dev_iWork)    ,sizeof(cufftComplex)*(self->dim_fft/2 +1)*fftbunch));
  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->dev_iWork_copy)    ,sizeof(cufftComplex)*(self->dim_fft/2 +1)*fftbunch));

  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->dev_Filter)   ,sizeof(float)*(self->dim_fft))                     );

  CUDA_SAFE_CALL(cudaMemcpy(self->dev_Filter,filter,sizeof(float)*(self->dim_fft),cudaMemcpyHostToDevice));
  
//   CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planR2C),self->dim_fft,CUFFT_R2C,self->num_projections));
//   CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planC2R),self->dim_fft,CUFFT_C2R,self->num_projections));
  CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planR2C),self->dim_fft,CUFFT_R2C,fftbunch));
  CUDA_SAFE_FFT(cufftPlan1d( (cufftHandle *) &(self->planC2R),self->dim_fft,CUFFT_C2R,fftbunch));

  printf("gpu_mainInit icount %d \n" , icount); icount++ ; 

  // ------------------------------------------------------------------------------------------------------
  // cudaChannelFormatDesc floatTex = cudaCreateChannelDesc<float>();
  CUDA_SAFE_CALL( cudaMallocArray((cudaArray**) &(self->a_Proje_voidptr), &floatTex ,  self->num_bins    , self->num_projections ) );

  CUDA_SAFE_CALL(cudaMalloc((void **)&(self->dev_Work_perproje)   ,sizeof(float)*self->num_bins*self->num_projections));
 

//   CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_axis_s)   ,sizeof(float)*(self->num_projections)) );
//   CUDA_SAFE_CALL( cudaMemcpy(self->d_axis_s , self->axis_position_s   , sizeof(float) *self->num_projections   ,  cudaMemcpyHostToDevice) );

  printf("gpu_mainInit icount %d \n" , icount); icount++ ; 

  printf("HELLO ZUBAIR. ( from maininit) the calculation is broken in 16x16 tiles \n");

  self->NblocchiPerLinea   =(int ) (  self->num_x *1.0/16  +0.9999999999 );

  self->NblocchiPerColonna =(int ) (  self->num_y *1.0/16  +0.9999999999 )  ; 
  
  // crea il volume_d multiplo

  self->dimrecx = self->NblocchiPerLinea*16 ;
  self->dimrecy = self->NblocchiPerColonna*16 ; 
    

  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_SLICE) , sizeof(float) * self->dimrecx* self->dimrecy      ));

  CUDA_SAFE_CALL(cudaMemset(self->d_SLICE,0, sizeof(float) * self->dimrecx* self->dimrecy    ));


  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_cos_s  ), sizeof(float) *   self->num_projections    ));

  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_sin_s  ), sizeof(float) *   self->num_projections    ));

  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_axis_s ), sizeof(float) *   self->num_projections    ));

  printf("gpu_mainInit icount %d \n" , icount); icount++ ; 



  CUDA_SAFE_CALL( cudaMemcpy(self->  d_cos_s , self->cos_s              , sizeof(float) *  self->num_projections ,  cudaMemcpyHostToDevice) );
  CUDA_SAFE_CALL( cudaMemcpy(self->  d_sin_s,  self->sin_s              , sizeof(float) *  self->num_projections ,  cudaMemcpyHostToDevice) );
  CUDA_SAFE_CALL( cudaMemcpy(self-> d_axis_s , self->axis_position_s    , sizeof(float) *  self->num_projections ,  cudaMemcpyHostToDevice) );


  self->gpu_streams = new  Gpu_Streams ; 
  cudaStreamCreate(&((((Gpu_Streams*) self->gpu_streams)->stream[0])) );
  cudaStreamCreate(&((((Gpu_Streams*) self->gpu_streams)->stream[1])) );

    {
      cudaError_t last = cudaGetLastError();
      if(last!=cudaSuccess) {
	printf("ERROR0: %s \n", cudaGetErrorString( last));
	exit(1);
      }
    }



  return 1;
}

int gpu_fbdl(Gpu_Context * self, float *d_S , float * SLICE, int do_precondition,
	     float DETECTOR_DUTY_RATIO,
	     int DETECTOR_DUTY_OVERSAMPLING,
	     int ANGLEOVERSAMPLING, int doppio ) {

  assert(do_precondition==0);
  
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 


    {
      cudaError_t last = cudaGetLastError();
      if(last!=cudaSuccess) {
	printf("ERROR1: %s \n", cudaGetErrorString( last));
	exit(1);
      }
    }


  for(int iv=0; iv<doppio; iv++)    {
    {
      dim3 blk,grd;
      blk = dim3( blsize_cufft , 1 , 1 );
      grd = dim3( iDivUp(self->num_bins,blsize_cufft) , 1, 1 );
      if(  self->fai360==0) {
	kern_recopy<<<grd,blk>>>(self->num_bins,self->dim_fft*0+self->num_bins,self->num_projections,
				 self->dev_Work_perproje   ,  
				 d_S + iv * self->num_bins*self->num_projections
				 );
      } else {
	kern_recopy_slope_leastsquare<<<grd,blk>>>(self->num_bins,self->dim_fft*0+self->num_bins,self->num_projections,
						   self->dev_Work_perproje ,
						   d_S+ iv * self->num_bins*self->num_projections,
						   self->overlapping, self->pente_zone, self->flat_zone,
						   self->d_axis_s,
						   self->prof_shift  , self->prof_fact
						   );
      }
    }
    {
      cudaError_t last = cudaGetLastError();
      if(last!=cudaSuccess) {
	printf("ERROR: %s \n", cudaGetErrorString( last));
	exit(1);
      }
    }

    CUDA_SAFE_CALL( cudaMemcpyToArray((cudaArray*) self->a_Proje_voidptr, 0, 0,self->dev_Work_perproje , 
				      self->num_projections*self->num_bins*sizeof(float) , cudaMemcpyDeviceToDevice) );
    CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,(cudaArray*) self->a_Proje_voidptr) );  //  !! unbind ??
    texProjes.filterMode = cudaFilterModeLinear;
    
    // da fare : testare prima la copia di memoria in blocco 2D ===> OK
    // Allocare con pinned da CCspace.cx
    // async cambiare al volo self->NblocchiPerColonna e incrementare la posizione in y. si usera offset nel kernel e si
    // sciftera il float * self->d_SLICE
    // ASINC ANCORA NON FUNZIONA, PROBLEMI PROBABILMENTE CON LA MEMORIA PINNED
    {
      dim3 dimGrid ( self->NblocchiPerLinea ,   self->NblocchiPerColonna  );
      dim3      dimBlock         (  16  ,  16   );
      int y = 0; 
      int itime=0;
      if(ANGLEOVERSAMPLING==1) {
	gputomo_kernel<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>> 
	  (self->num_projections,self->num_bins, self->axis_position,
	   self->d_SLICE+ self->dimrecx*y,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s
	   );  
      } else {
	gputomo_oversampling_kernel<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>> 
	  (self->num_projections,self->num_bins, self->axis_position,
	   self->d_SLICE+ self->dimrecx*y,
	   self->gpu_offset_x, self->gpu_offset_y + y,
	   self->d_cos_s , self->d_sin_s , self->d_axis_s,
	   ANGLEOVERSAMPLING
	   );  
      }
      
      if(abs(DETECTOR_DUTY_OVERSAMPLING)>1){
	assert( self->num_x==self->num_y  );
	int dimslice = self->num_x ; 
	int NblocchiPerLinea= iDivUp(dimslice, 16 );
	int NblocchiPerColonna =  iDivUp(dimslice, 16 ) ; 
	int dimrecx, dimrecy;
	dimrecx = NblocchiPerLinea  *16 ;
	dimrecy = NblocchiPerColonna*16 ;
	
	float dangle = acos(self->cos_s[1]*self->cos_s[0]+self->sin_s[1]*self->sin_s[0])*DETECTOR_DUTY_RATIO;
	
	dim3 dimGrid ( NblocchiPerLinea ,   NblocchiPerLinea );
	dim3      dimBlock         (  16  ,  16   );
	
	cudaArray * a_slice;
	// cudaChannelFormatDesc floatTex = cudaCreateChannelDesc<float>();
	CUDA_SAFE_CALL( cudaMallocArray(&a_slice, &floatTex ,  (dimslice)   ,  (dimslice)) );
	CUDA_SAFE_CALL( cudaMemcpyToArray((cudaArray*) a_slice, 0, 0, self->d_SLICE, 
					  dimrecy*dimrecx*sizeof(float) ,
					  cudaMemcpyDeviceToDevice) );
	CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,(cudaArray*) a_slice) );  //  !! unbind ??
	texProjes.filterMode = cudaFilterModeLinear;
	
	printf("gputomo.cu:  DOING slicerot_kernel with DETECTOR_DUTY_OVERSAMPLING=%d\n",DETECTOR_DUTY_OVERSAMPLING);
	slicerot_kernel<<<dimGrid,dimBlock>>>( dimslice,
					       self->d_SLICE,
					       dangle,
					       abs(DETECTOR_DUTY_OVERSAMPLING) );
	
	CUDA_SAFE_CALL(cudaFreeArray(a_slice));
	
	
      }
      cudaMemcpy2D( 
		   // CUDA_SAFE_CALL(
		   // cudaMemcpy2DAsync( 
		   SLICE+y*self->num_x  +iv* self->num_x*self->num_y   ,self->num_x*sizeof(float),
		   self->d_SLICE +y*self->dimrecx,
		   self->dimrecx*sizeof(float),
		   self->num_x*sizeof(float),
		   self->num_y,
		   cudaMemcpyDeviceToHost
		   // , 
		   //  (((Gpu_Streams*) self->gpu_streams)->stream[itime%2])  
		    ); 
      //  );
      cudaError_t last = cudaGetLastError();
      if(last!=cudaSuccess) {
	printf("memcpy async Last error-reset: %s \n", cudaGetErrorString( last));
	exit(1);
      }
     

    }
  }
  return 1;
}


double put_patches_fbdl( float *pimg, float *imgresult,float *image, float *SLICE,
			 float *noverlap,
			 int dim0, int dim1,
			 int s0, int s1,
			 int end0, int end1,
			 int dim_patches,
			 int direction,
			 float *dists,
			 float *freqs, int id, int vectoriality, float peso_overlap,
			 float *imagesum=NULL, int nframes=0) {
  
  double fidelity=0.0;
  int doppio=vectoriality;
  int pos=0;
  if(direction==0) {
    float C = 0.5f*(dim_patches-1), d,d0;
    int address ; 
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {
	for(int pi0= 0; pi0<dim_patches; pi0++) {
	  d0=fabs(pi0-C);
	  for(int pi1= 0; pi1<dim_patches; pi1++) {
	    d = d0 + fabs(pi1-C);
	    address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ; 
	    if(d< dists[ address  ] ) {
	      dists[ address ]=d;
	    }
	  }
	}
      }
    }
  } else if(direction==-1) {
    float C = 0.5f*(dim_patches-1), d,d0;
    int address ; 
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {
	for(int pi0= 0; pi0<dim_patches; pi0++) {
	  d0=fabs(pi0-C);
	  for(int pi1= 0; pi1<dim_patches; pi1++) {
	    d = d0 + fabs(pi1-C);
	    address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ; 
	    if(d<= dists[ address  ] ) {
	      freqs[ address ]=id;
	    }
	  }
	}
      }
    }
  } else if(direction== 1) {
    int address ; 
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {
	for(int idop =0; idop<doppio; idop++) {
	  for(int pi0= 0; pi0<dim_patches; pi0++) {
	    for(int pi1= 0; pi1<dim_patches; pi1++) {
	      address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ; 
	      image[ address +idop*dim0*dim1 ]+= pimg[pos]; 
	      if(id == freqs[ address] ) {
		imgresult[ address +idop*dim0*dim1 ]=  pimg[pos] ;
	      } 
	      pos++;
	    }
	  }
	}
      }
    }
  } else if(direction== 10) {
    int address ; 
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {
	for(int idop =0; idop<doppio; idop++) {
	  for(int pi0= 0; pi0<dim_patches; pi0++) {
	    for(int pi1= 0; pi1<dim_patches; pi1++) {
	      address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ; 
	      noverlap[ address +idop*dim0*dim1 ]+= 1; 
	      pos++;
	    }
	  }
	}
      }
    }
  } else if(direction== 2) {
    int address ; 
    float diff ;
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {
	for(int idop =0; idop<doppio; idop++) {
	  for(int pi0= 0; pi0<dim_patches; pi0++) {
	    for(int pi1= 0; pi1<dim_patches; pi1++) {
	      address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ; 
	      if(id == freqs[ address] ) {
		imgresult[ address +idop*dim0*dim1 ]= SLICE[ address +idop*dim0*dim1 ];
		diff = imagesum[ address +idop*dim0*dim1 ]-pimg[pos]*noverlap[ address +idop*dim0*dim1   ]  ;
		imgresult[ address +idop*dim0*dim1 ] += diff*(peso_overlap);		
	      } else {
		diff = (image[ address +idop*dim0*dim1 ]-pimg[pos]);
		imgresult[ address +idop*dim0*dim1 ]= diff *(peso_overlap);
		fidelity += diff*diff *(peso_overlap/2.0f);
	      }
	      pos++;
	    }
	  }
	}
      }
    }
  } else if(direction== -2) {
    int address ; 
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {
	for(int idop =0; idop<doppio; idop++) {
	  for(int pi0= 0; pi0<dim_patches; pi0++) {
	    for(int pi1= 0; pi1<dim_patches; pi1++) {
	      address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ; 
	      pimg[pos] =   imgresult[ address +idop*dim0*dim1 ] ; 	  
	      pos++;
	    }
	  }
	}
      }
    }
  }
  return fidelity;
}




__global__  static  void kern_shrink_fista(int nitems,int ncomps, float *d_w,  float *d_dw, float *d_wold,  float  weight, 
					   float *d_notzero, float told,  float tnew  ){
  int gid;
  float w0,w1,tmp,  notzero, wold;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  // int iwidget = gid/ncomps  ; 

  if(gid<nitems*ncomps) {
    // ss = d_ss[iwidget];
    w0 = d_w[gid];
    tmp = w0+d_dw[gid];
    // w1 = copysignf( max(fabs(tmp)-weight*ss,0.0f) , tmp) ;
    w1 = copysignf( max(fabs(tmp)-weight,0.0f) , tmp) ;
    wold = d_wold[gid];
    d_wold[gid]=w1 ; 
    w1=w1 +  ((told-1)/tnew) * (w1-wold) ; 
    d_w [gid] = w1;
    notzero=0.0f;
    if(w1!=0.0f) {
      notzero = 1.0f;
    } 
    d_dw[gid] = w1 - w0;
    if(d_notzero)   d_notzero[gid]=notzero;
  }
}

__global__  static  void kern_shrink_fista_fbdl(int nitems,int ncomps, float *d_w,  float *d_dw, float *d_wold,  float  weight, 
						float *d_notzero, float told,  float tnew  ){
  int gid;
  float w0,w1,tmp,  notzero, wold;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  // int iwidget = gid/ncomps  ; 
  
  if(gid<nitems*ncomps ) {
    // ss = d_ss[iwidget];
    w0 = d_w[gid];
    tmp = w0+d_dw[gid];
    // w1 = copysignf( max(fabs(tmp)-weight*ss,0.0f) , tmp) ;
    w1=tmp;
    if( gid/nitems>1) {
      w1 = copysignf( max(fabs(tmp)-weight,0.0f) , tmp) ;
    } 
    wold = d_wold[gid];
    d_wold[gid]=w1 ; 
    w1=w1 +  ((told-1)/tnew) * (w1-wold) ; 
    d_w [gid] = w1;
    notzero=0.0f;
    if(w1!=0.0f) {
      notzero = 1.0f;
    } 
    d_dw[gid] = w1 - w0;
    if(d_notzero)   d_notzero[gid]=notzero;
  }
}

__global__  static  void   kern_subtract(float *d_R, float *d_vect , float *d_w , int npoints, int  sizepatch  ) {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  int np;
  np = gid/sizepatch;
  if(gid <npoints ) {
    d_R[gid]-= d_vect[gid]*d_w[np];
  }
}


void fb_dl(Gpu_Context * self, float *WORK , float * SLICE, int precondition,
	   float    DETECTOR_DUTY_RATIO        ,
	   int    DETECTOR_DUTY_OVERSAMPLING,
	   int    ANGLEOVERSAMPLING,  float weight,
	   float *patches, int npatches, int dim_patches, int vectoriality, float *Lipschitz) {
  


  printf(" in fb_dl\n");

  int i,j;
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 
  int doppio= vectoriality;
  int sizepatch = dim_patches*dim_patches*doppio; 
  int ncomps = npatches;
  int dim0, dim1;
  dim0=self->num_y  ;
  dim1=self->num_x  ;
  assert(dim0==dim1);
  int dimslice=dim1;

  int np0    = (dim0/dim_patches);
  int np1    = (dim1/dim_patches);
  
  float * pimg_tmp = (float *) malloc( dim0*dim1*doppio*sizeof(float));
  float *image = (float *) malloc( dim0*dim1*doppio*sizeof(float));
  float *imagesum = (float *) malloc( dim0*dim1*doppio*sizeof(float));
  float *imgresult = (float *) malloc( dim0*dim1*doppio*sizeof(float));
  float *noverlap = (float *) malloc( dim0*dim1*doppio*sizeof(float))  ; 

  memset( pimg_tmp , 0,  dim0*dim1*doppio*sizeof(float)   );
  memset(  image , 0,  dim0*dim1*doppio*sizeof(float)   );

  int nf0, nf1;
  nf0 =  1 +  (dim_patches-1)/self->STEPFORPATCHES; 
  nf1 =  1 +  (dim_patches-1)/self->STEPFORPATCHES; 
  
  float * dists = (float *) malloc( dim0*dim1*sizeof(float));
  float * freqs = (float *) malloc( dim0*dim1*sizeof(float));


  for(i=0; i< dim0*dim1; i++) {
    dists[i]=1.0e10;
  }
  
  for(i=0; i<nf0; i++) {
    for(j=0; j<nf1; j++) {
      int id;
      id = i*nf1+j  ; 
      int shift0, shift1; 
      shift0 = i* self->STEPFORPATCHES ; 
      shift1 = j* self->STEPFORPATCHES ; 
      int nw0    = ((dim0-shift0)/dim_patches);
      int nw1    = ((dim1-shift1)/dim_patches);

      put_patches_fbdl(pimg_tmp,imgresult, image, SLICE,noverlap,
		       dim0, dim1,
		       shift0, shift1,
		       nw0, nw1,
		       dim_patches ,
		       0 , // inizializzadists
		       dists, freqs, id ,  vectoriality, self->peso_overlap);  
    }
  }
  for(i=0; i<nf0; i++) {
    for(j=0; j<nf1; j++) {
      int id;
      id = i*nf1+j  ; 
      int shift0, shift1; 
      shift0 = i* self->STEPFORPATCHES ; 
      shift1 = j* self->STEPFORPATCHES ; 
      int nw0    = ((dim0-shift0)/dim_patches);
      int nw1    = ((dim1-shift1)/dim_patches);

      put_patches_fbdl(pimg_tmp,imgresult, image, SLICE,noverlap,
		       dim0, dim1,
		       shift0, shift1,
		       nw0, nw1,
		       dim_patches ,
		       -1 , // freqs
		       dists, freqs, id ,  vectoriality,self->peso_overlap);  
    }
  }


  memset(noverlap,0, dim0*dim1*doppio*sizeof(float))  ; 
  for(i=0; i<nf0; i++) {
    for(j=0; j<nf1; j++) {
      int id;
      id = i*nf1+j  ; 
      int shift0, shift1; 
      shift0 = i* self->STEPFORPATCHES ; 
      shift1 = j* self->STEPFORPATCHES ; 
      int nw0    = ((dim0-shift0)/dim_patches);
      int nw1    = ((dim1-shift1)/dim_patches);

      put_patches_fbdl(pimg_tmp,imgresult, image, SLICE,noverlap,
		       dim0, dim1,
		       shift0, shift1,
		       nw0, nw1,
		       dim_patches ,
		       10 ,   // si inizializza noverlap
		       dists, freqs, id ,  vectoriality,self->peso_overlap);  
    }
  }




  // {
  //   FILE *f;
  //   f = fopen("dists","w");
  //   fwrite(    dists , dim0*dim1*sizeof(int),  1    , f);
  //   fclose(f);

  //   f = fopen("freqs","w");
  //   fwrite(    freqs , dim0*dim1*sizeof(int),  1    , f);
  //   fclose(f);
  //   printf(" dimslice %d \n", dimslice);
  //   exit(0);
  // }
  
  float * d_w[nf0][nf1];
  float * d_dw;
  float * d_wold[nf0][nf1];

  float * pimg[nf0][nf1] ; 

  for(i=0; i<nf0; i++) {
    for(j=0; j<nf1; j++) {
      pimg[i][j] = (float*)malloc( doppio*dim0*dim1*sizeof(float)  );
      CUDA_SAFE_CALL( cudaMalloc( &(d_wold[i][j]),  (np0+1)*(np1+1)*ncomps*sizeof(float)    ) )	;
      CUDA_SAFE_CALL( cudaMalloc( &(d_w[i][j]),  (np0+1)*(np1+1)*ncomps*sizeof(float)    ) )	;
      CUDA_SAFE_CALL( cudaMemset(     (d_w   [i][j]), 0,  (np0+1)*(np1+1)*ncomps*sizeof(float)          ));
      CUDA_SAFE_CALL( cudaMemset(     (d_wold[i][j]), 0,  (np0+1)*(np1+1)*ncomps*sizeof(float)          ));


      if(*Lipschitz<0) {    
	int npoints = (np0+1)*(np1+1)*ncomps ; 
	dim3 blk,grd;
	blk = dim3( 256 , 1 , 1 );
	grd = dim3( iDivUp( npoints ,256) , 1, 1 );
	kern_set<<<grd,blk>>>(d_w [i][j], 1.0f, npoints  );
      }
    }
  }


  float *d_notzero;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_notzero,  (np0+1)*(np1+1)*ncomps*sizeof(float)    ) )	;

  CUDA_SAFE_CALL( cudaMalloc( &(d_dw),  (np0+1)*(np1+1)*ncomps*sizeof(float)    ) )	;
  
  float *d_R;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_R,  dim0*dim1*doppio*sizeof(float)    ) )	;
  float *d_X;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_X,  ncomps*dim_patches*dim_patches*doppio*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMemcpy (   d_X , patches , ncomps*dim_patches*dim_patches*doppio*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  
  float *d_S;
  float t=1.0f;
  
  CUDA_SAFE_CALL( cudaMalloc( 	&d_S,  iAlignUp(self->num_projections,fftbunch)*self->num_bins*doppio*sizeof(float)    ) )	;
  CUDA_SAFE_CALL(cudaMemcpy(d_S,WORK,doppio*sizeof(float)*(self->num_bins*self->num_projections),cudaMemcpyHostToDevice));  

  float * WORKbis  = (float*) malloc(   doppio*sizeof(float)*(self->num_bins*self->num_projections)     ) ; 

  memset(image , 0,  dim0*dim1*doppio*sizeof(float)   );
  int failip=0;
  if(*Lipschitz<0) {    
    failip=1;
  }

  float norma ; 

  {
    cudaError_t last = cudaGetLastError();
    if(last!=cudaSuccess) {
      printf("ERRORX: %s \n", cudaGetErrorString( last));
      exit(1);
    }
  }
  double fidelity = 0.0;
  double penality = 0.0 ; 
  
  for(int ivolta=0; ivolta< self->ITERATIVE_CORRECTIONS*(1-failip)+failip*50 ; ivolta++) {
    
    
    float told = t;
    t=(1.0f+sqrt(1.0f+4.0f*t*t))/2.0f;


    fidelity = 0.0;
    penality = 0.0;


    memset(image , 0,  dim0*dim1*doppio*sizeof(float)   );
    memset(imagesum , 0,  dim0*dim1*doppio*sizeof(float)   );

    for(i=0; i<nf0; i++) {
      for(j=0; j<nf1; j++) {
	int id;
	id = i*nf1+j  ; 
	
	int shift0, shift1; 
	shift0 = i* self->STEPFORPATCHES ; 
	shift1 = j* self->STEPFORPATCHES ; 
	int nw0    = ((dim0-shift0)/dim_patches);
	int nw1    = ((dim1-shift1)/dim_patches);
	
	int nwidget =  nw0*nw1  ; 
	
	float alpha=1.0f, beta=0.0f;
	
	cublasSgemm('N','T',sizepatch ,nwidget ,ncomps,
		    alpha,
		    d_X , sizepatch,
		    d_w[i][j], nwidget,
		    beta,
		    d_R  , sizepatch );     
	
	CUDA_SAFE_CALL(cudaMemcpy  ( pimg[i][j] , d_R ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
	
	put_patches_fbdl(pimg[i][j],image, imagesum,SLICE, noverlap,
			 dim0, dim1,
			 shift0, shift1,
			 nw0, nw1,
			 dim_patches ,
			 1 ,  // posa selettiva
			 dists, freqs, id,  vectoriality,self->peso_overlap);  
      }
    }
   
    for(int idop =0; idop<doppio; idop++) {
      // C_HST_PROJECT_1OVER_GPU(self,
      self->gpu_project(self->gpuctx,
			self->num_bins,        
			self->num_projections,
			self->angles_per_proj, 
			self->axis_position  ,
			WORKbis + idop*  self->num_bins*self->num_projections  ,
			image   + idop* dim0*dim1  ,  
			dimslice,
			self->axis_corrections,
			self->gpu_offset_x ,
			self->gpu_offset_y ,
			self->JOSEPHNOCLIP,
			DETECTOR_DUTY_RATIO        ,
			DETECTOR_DUTY_OVERSAMPLING
			);

      {
	cudaError_t last = cudaGetLastError();
	if(last!=cudaSuccess) {
	  printf("ERRORY: %s \n", cudaGetErrorString( last));
	  exit(1);
	}
      }
      
    }
    if( failip==0) {

      // qua per cross validation
      // double cross=0.0;
      for( i=0; i< doppio*self->num_projections *self->num_bins ; i++) {
	WORKbis[i]=(WORK[i]-WORKbis[i]) ; 

	// if(i<self->num_bins ) {
	//   cross+= WORKbis[i]*WORKbis[i];
	//   WORKbis[i]=0;
	// }// qua per cross validation

	WORKbis[i]= WORKbis[i]/ (M_PI*0.5f/ self->num_projections); 
	fidelity  +=  (WORKbis[i]*WORKbis[i] )/2.0 ; 
      }
      // printf(" CROSS = %e", cross);// qua per cross validation
    } else {
      for( i=0; i< doppio*self->num_projections *self->num_bins ; i++) {
	WORKbis[i]=(-WORKbis[i])  / (M_PI*0.5f/ self->num_projections); 
      }
    }
    CUDA_SAFE_CALL(cudaMemcpy(d_S,WORKbis,doppio*sizeof(float)*(self->num_bins*self->num_projections),cudaMemcpyHostToDevice));  
    
    {
      cudaError_t last = cudaGetLastError();
      if(last!=cudaSuccess) {
	printf("ERRORZ: %s \n", cudaGetErrorString( last));
	exit(1);
      }
    }

    int do_precondition = 0 ; 
    gpu_fbdl(self, d_S , SLICE,  do_precondition,
	     DETECTOR_DUTY_RATIO,
	     DETECTOR_DUTY_OVERSAMPLING,
	     ANGLEOVERSAMPLING,  doppio );

    // if(*Lipschitz>0) {
    //   printf("%d %d\n", dim0, dim1);
    //   FILE *f=fopen("slice","w");
    //   fwrite(SLICE, doppio*dim0*dim1*sizeof(float),1, f);
    //   fclose(f);
    //   exit(0);
    // }

    float sparsita=0.0f;
    for(i=0; i<nf0; i++) {
      for(j=0; j<nf1; j++) {
	int id;
	id = i*nf1+j  ; 
	
	int shift0, shift1; 
	shift0 = i* self->STEPFORPATCHES ; 
	shift1 = j* self->STEPFORPATCHES ; 
	int nw0    = ((dim0-shift0)/dim_patches);
	int nw1    = ((dim1-shift1)/dim_patches);
	
	int nwidget =  nw0*nw1  ; 
	
	float alpha=1.0f, beta=0.0f;
	
	cublasSgemm('N','T',sizepatch ,nwidget ,ncomps,
		    alpha,
		    d_X , sizepatch,
		    d_w[i][j], nwidget,
		    beta,
		    d_R  , sizepatch );     
	
	CUDA_SAFE_CALL(cudaMemcpy  ( pimg[i][j] , d_R ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
	
	fidelity+=put_patches_fbdl(pimg[i][j],imgresult, image,SLICE, noverlap,
				   dim0, dim1,
				   shift0, shift1,
				   nw0, nw1,
				   dim_patches ,
				   2 ,
				   dists, freqs, id,  vectoriality, self->peso_overlap,
				   imagesum, nf0*nf1 );  
	
	put_patches_fbdl(pimg_tmp,imgresult, image,SLICE, noverlap,
			 dim0, dim1,
			 shift0, shift1,
			 nw0, nw1,
			 dim_patches ,
			 -2 ,
			 dists, freqs, id,  vectoriality, self->peso_overlap);  
	
	CUDA_SAFE_CALL(cudaMemcpy  (d_R ,   pimg_tmp , nwidget*sizepatch*sizeof(float)   ,cudaMemcpyHostToDevice  ));
	
	{
	  penality +=   cublasSasum (  nwidget*ncomps ,d_w[i][j] , 1)*weight; 
	}

	{
	  float alpha= 1.0f/(*Lipschitz);
	  float beta = 0.0f;
	  
	  if(failip) alpha=1.0f;
	  
	  cublasSgemm('T','N', nwidget, ncomps ,  sizepatch ,
		      alpha,
		      d_R    , sizepatch ,
		      d_X    , sizepatch  ,
		      beta,
		      d_dw  , nwidget );
	}
	
	{
	  dim3 blk,grd;
	  blk = dim3( 512 , 1 , 1 );
	  grd = dim3( iDivUp( nwidget*ncomps  ,512) , 1, 1 );
	  
	  if(failip) {
	    t=1.0f;
	    float told=1.0f;
	    kern_shrink_fista_fbdl<<<grd,blk>>>(nwidget,ncomps ,  d_w[i][j],    d_dw , d_wold[i][j],
					   0.0f , d_notzero, told, t); 
	  } else {

	    // float * d_notzero = NULL;
	    kern_shrink_fista_fbdl<<<grd,blk>>>(nwidget,ncomps ,  d_w[i][j],    d_dw , d_wold[i][j],
					   weight/(*Lipschitz), d_notzero, told, t); 
	  }
	  sparsita += cublasSasum (nwidget*ncomps ,d_notzero , 1)/nwidget/ncomps;
	}	
      }
    }
    
    if(failip) {
      double sum=0.0;
      for(i=0; i<nf0; i++) {
	for(j=0; j<nf1; j++) {
	  
	  int shift0, shift1; 
	  shift0 = i* self->STEPFORPATCHES ; 
	  shift1 = j* self->STEPFORPATCHES ; 
	  int nw0    = ((dim0-shift0)/dim_patches);
	  int nw1    = ((dim1-shift1)/dim_patches);
	  int nwidget =  nw0*nw1  ; 
	  
	  double tmp =  cublasSnrm2( nwidget*ncomps  ,  d_w[i][j]   , 1);
	  sum=sum+tmp*tmp;
	}
      }
      norma = sqrt(sum);
      for(i=0; i<nf0; i++) {
	for(j=0; j<nf1; j++) {
	  int shift0, shift1; 
	  shift0 = i* self->STEPFORPATCHES ; 
	  shift1 = j* self->STEPFORPATCHES ; 
	  int nw0    = ((dim0-shift0)/dim_patches);
	  int nw1    = ((dim1-shift1)/dim_patches);
	  int nwidget =  nw0*nw1  ; 
	  cublasSscal(nwidget*ncomps, 1.0f/norma , d_w[i][j], 1) ;
	}
      }
      printf(" ricerca Lipschitz %e \n", norma);
    } else {
      printf(" ERROR %e FIDELITY %e PENALITY %e SPARSITA %e\n",  fidelity+ penality,   fidelity, penality, sparsita/(nf0*nf1));
    }
  }
  
  if(failip) {
    *Lipschitz = norma*1.1;
  } else {
    memcpy(SLICE, image, dim0*dim1*doppio*sizeof(float));
  }
  
  free(pimg_tmp);
  free(image);
  free(imagesum);
  free(imgresult);
  free(noverlap);
  free(dists);
  free(freqs);
  for(i=0; i<nf0; i++) {
    for(j=0; j<nf1; j++) {
      free(  pimg[i][j]  );
      CUDA_SAFE_CALL(cudaFree(d_wold[i][j]));
      CUDA_SAFE_CALL(cudaFree(d_w[i][j]));
    }
  }
  CUDA_SAFE_CALL(cudaFree(d_dw));
  CUDA_SAFE_CALL(cudaFree(d_R));
  CUDA_SAFE_CALL(cudaFree(d_X));
  CUDA_SAFE_CALL(cudaFree(d_S));
  CUDA_SAFE_CALL(cudaFree(d_notzero));
}


int gpu_main(Gpu_Context * self, float *WORK , float * SLICE, int do_precondition,
	     float DETECTOR_DUTY_RATIO,
	     int DETECTOR_DUTY_OVERSAMPLING,
	     int ANGLEOVERSAMPLING, float * fidelity) {


  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 

  //CUDA_SAFE_CALL(
 

  cudaError_t err = cudaMemcpy(self->dev_rWork,WORK,sizeof(float)*(self->dim_fft*self->num_projections),cudaMemcpyHostToDevice);  
  if(err!=cudaSuccess) {
    printf("Last error-reset: %s \n", cudaGetErrorString(err ));	
    exit(1);	
  }
  if(fidelity) *fidelity=0;
  if(1) {
    if(fidelity && do_precondition==0) {
      *fidelity = cublasSnrm2( self->num_projections*self->dim_fft, self->dev_rWork,1  )  ; 
    } 
  } else {
    // qua per cross validation
    if(fidelity && do_precondition==0) {
      *fidelity = cublasSnrm2( self->dim_fft, self->dev_rWork,1  )  ; 
      cudaMemset(self->dev_rWork,0,sizeof(float)*(self->dim_fft));  
    } 
  }


  if( (self->FBFILTER>=0 && do_precondition) ||  self->FBFILTER ==2 ) {
    int iproj;
    for(iproj=0; iproj<self->num_projections; iproj+=fftbunch) {
      CUDA_SAFE_FFT(cufftExecR2C((cufftHandle) self->planR2C,
				 self->dev_rWork+(iproj*self->dim_fft),
				 (cufftComplex *) self->dev_iWork));
      
      if(do_precondition ){
	dim3 blk,grd;
	int Hdimfft = self->dim_fft/2 +1 ; 
	blk = dim3( blsize_cufft , 1 , 1 );
	grd = dim3( iDivUp((Hdimfft) ,blsize_cufft) , 1, 1 );
	// kern_cufft_filter<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork),self->dev_Filter,self->num_projections, self->dim_fft);
	if(fidelity)  cudaMemcpy(self->dev_iWork_copy,self->dev_iWork,
				 sizeof(cufftComplex)*Hdimfft*fftbunch,
				 cudaMemcpyDeviceToDevice);
	kern_cufft_filter<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork),self->dev_Filter,fftbunch, self->dim_fft);
	if(cudaError_t err=cudaGetLastError()){printf("kern_cufft_filter: %s\n",cudaGetErrorString(err));/*getchar();*/}	
	if(fidelity) {
	  kern_cufft_filter_corrpersymm<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork_copy),fftbunch, self->dim_fft);
	  *fidelity+= cublasSdot( Hdimfft*fftbunch*2,
				  (float*) self->dev_iWork, 1,(float*) self->dev_iWork_copy, 1)  ; 
	}
      }

      if(self->FBFILTER ==2) {
	dim3 blk,grd;
	int Hdimfft = self->dim_fft/2 +1 ; 
	blk = dim3( blsize_cufft , 1 , 1 );
	grd = dim3( iDivUp((Hdimfft) ,blsize_cufft) , 1, 1 );
	kern_cufft_filter_talbot<<<grd,blk>>>(Hdimfft ,(cufftReal*)(self->dev_iWork),fftbunch, self->dim_fft);
	if(cudaError_t err=cudaGetLastError()){printf("kern_cufft_filterr_talbot: %s\n",cudaGetErrorString(err));/*getchar();*/}
      }
      
      CUDA_SAFE_FFT(cufftExecC2R((cufftHandle) self->planC2R,(cufftComplex *)self->dev_iWork,
				 self->dev_rWork+ (iproj*self->dim_fft)   ));
    }
  }

  {
    dim3 blk,grd;
    blk = dim3( blsize_cufft , 1 , 1 );
    grd = dim3( iDivUp(self->num_bins,blsize_cufft) , 1, 1 );
    if(  self->fai360==0) {
      kern_recopy<<<grd,blk>>>(self->num_bins,self->dim_fft,self->num_projections, self->dev_Work_perproje,  self->dev_rWork
			       );
    } else {
      kern_recopy_slope<<<grd,blk>>>(self->num_bins,self->dim_fft,self->num_projections, self->dev_Work_perproje,  self->dev_rWork,
				     self->overlapping, self->pente_zone, self->flat_zone,
				     self->d_axis_s,
				     self->prof_shift  , self->prof_fact
				     );
    }
  }

  
  CUDA_SAFE_CALL( cudaMemcpyToArray((cudaArray*) self->a_Proje_voidptr, 0, 0,self->dev_Work_perproje , 
				    self->num_projections*self->num_bins*sizeof(float) , cudaMemcpyDeviceToDevice) );
  CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,(cudaArray*) self->a_Proje_voidptr) );  //  !! unbind ??
  texProjes.filterMode = cudaFilterModeLinear;
  
  // da fare : testare prima la copia di memoria in blocco 2D ===> OK
  // Allocare con pinned da CCspace.cx
  // async cambiare al volo self->NblocchiPerColonna e incrementare la posizione in y. si usera offset nel kernel e si
  // sciftera il float * self->d_SLICE
  // ASINC ANCORA NON FUNZIONA, PROBLEMI PROBABILMENTE CON LA MEMORIA PINNED
  {
    clock_t t1, t2;
    t1 = clock();

    printf("HELLO ZUBAIR. ( from gpu_main) preparing the grid of blocks for the 16x16 tiles \n");


    dim3 dimGrid ( self->NblocchiPerLinea ,   self->NblocchiPerColonna  );
    dim3      dimBlock         (  16  ,  16   );
    int y = 0; 
    int itime=0;
    if(ANGLEOVERSAMPLING==1) {
      gputomo_kernel<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>> 
	(self->num_projections,self->num_bins, self->axis_position,
	 self->d_SLICE+ self->dimrecx*y,
	 self->gpu_offset_x, self->gpu_offset_y + y,
       self->d_cos_s , self->d_sin_s , self->d_axis_s
	 );  
    } else {
      gputomo_oversampling_kernel<<<dimGrid,dimBlock,256*3*sizeof(float),(((Gpu_Streams*) self->gpu_streams)->stream[itime%2])>>> 
	(self->num_projections,self->num_bins, self->axis_position,
	 self->d_SLICE+ self->dimrecx*y,
	 self->gpu_offset_x, self->gpu_offset_y + y,
	 self->d_cos_s , self->d_sin_s , self->d_axis_s,
	 ANGLEOVERSAMPLING
	 );  
    }



    if(abs(DETECTOR_DUTY_OVERSAMPLING)>1){
      assert( self->num_x==self->num_y  );
      int dimslice = self->num_x ; 
      int NblocchiPerLinea= iDivUp(dimslice, 16 );
      int NblocchiPerColonna =  iDivUp(dimslice, 16 ) ; 
      int dimrecx, dimrecy;
      dimrecx = NblocchiPerLinea  *16 ;
      dimrecy = NblocchiPerColonna*16 ;

      float dangle = acos(self->cos_s[1]*self->cos_s[0]+self->sin_s[1]*self->sin_s[0])*DETECTOR_DUTY_RATIO;
 
      dim3 dimGrid ( NblocchiPerLinea ,   NblocchiPerLinea );
      dim3      dimBlock         (  16  ,  16   );

      cudaArray * a_slice;
      // cudaChannelFormatDesc floatTex = cudaCreateChannelDesc<float>();
      CUDA_SAFE_CALL( cudaMallocArray(&a_slice, &floatTex ,  (dimslice)   ,  (dimslice)) );
      CUDA_SAFE_CALL( cudaMemcpyToArray((cudaArray*) a_slice, 0, 0, self->d_SLICE, 
					dimrecy*dimrecx*sizeof(float) ,
					cudaMemcpyDeviceToDevice) );
      CUDA_SAFE_CALL( cudaBindTextureToArray(texProjes,(cudaArray*) a_slice) );  //  !! unbind ??
      texProjes.filterMode = cudaFilterModeLinear;

      printf("gputomo.cu:  DOING slicerot_kernel with DETECTOR_DUTY_OVERSAMPLING=%d\n",DETECTOR_DUTY_OVERSAMPLING);
      slicerot_kernel<<<dimGrid,dimBlock>>>( dimslice,
					     self->d_SLICE,
					     dangle,
					     abs(DETECTOR_DUTY_OVERSAMPLING) );

      CUDA_SAFE_CALL(cudaFreeArray(a_slice));


    }
    cudaMemcpy2D( 
		 // CUDA_SAFE_CALL(
		 // cudaMemcpy2DAsync( 
		 SLICE+y*self->num_x,self->num_x*sizeof(float),
		 self->d_SLICE +y*self->dimrecx,
		 self->dimrecx*sizeof(float),
		 self->num_x*sizeof(float),
		 self->num_y,
		 cudaMemcpyDeviceToHost
		 // , 
		 //  (((Gpu_Streams*) self->gpu_streams)->stream[itime%2])  
		  ); 
    //  );
    cudaError_t last = cudaGetLastError();
    if(last!=cudaSuccess) {
      printf("memcpy async Last error-reset: %s \n", cudaGetErrorString( last));
      exit(1);
    }
    t2 = clock();
    printf( "GPUKRNEL OK t=%f \n",  ( ((long)t2)-((long)t1)   )*1.0f/CLOCKS_PER_SEC);
  }
  return 1;
}

#undef blsize_cufft
#undef CUDA_SAFE_FFT


void  gpu_pagCtxCreate (Gpu_pag_Context * self) {
  self->gpuctx = (void*)  malloc(sizeof(CUcontext)) ; 
  // cudaSetDeviceFlags(	cudaDeviceMapHost	) ;
  cudaSetDevice(self->MYGPU);

  cuCtxCreate( (CUcontext *) self->gpuctx	,
	       // CU_CTX_SCHED_YIELD*0,
	       CU_CTX_SCHED_SPIN,
	       self->MYGPU	 
	       );
}

void  gpu_pagCtxDestroy(Gpu_pag_Context * self) {
  cuCtxDestroy( *((CUcontext *) (self->gpuctx))	  );
}

void  gpu_pag( Gpu_pag_Context *   self , float * auxbuffer	)   {
  char messaggio[1000];
  int dimbuff = self->size_pa0*self->size_pa1; 
  CUDA_SAFE_CALL(cudaMemcpy(self->d_fftwork,auxbuffer,
			    sizeof(cufftComplex)*dimbuff,
			    cudaMemcpyHostToDevice));
  CUFFT_SAFE_CALL( cufftExecC2C(*((cufftHandle*)self->FFTplan_ptr), 
				(cufftComplex *) self->d_fftwork, 
				(cufftComplex *) self->d_fftwork, 
				CUFFT_FORWARD) , "doing a   cufftExecC2C"  );
  {
    dim3 blk,grd;
    int blksz = 216;
    blk = dim3(blksz  , 1 , 1 );
    int done=0;
    int todo=0;
    while(done<dimbuff) {
      int nb =   min(  iDivUp(dimbuff,blksz ), 0xFFFF )  ; 
      todo = min( nb* blksz , dimbuff-done   ) ;
      grd = dim3( nb , 1, 1 ); 

      kern_mult<<<grd,blk>>>( ((cufftComplex *) self->d_fftwork)+done,
			      ((cufftComplex *) self->d_kernelbuffer)+done,
			      todo );
      done+=todo;
    }
  }
  CUFFT_SAFE_CALL( cufftExecC2C(*((cufftHandle*)self->FFTplan_ptr), 
				(cufftComplex *) self->d_fftwork, 
				(cufftComplex *) self->d_fftwork, 
				CUFFT_INVERSE), " doing CUFFT_INVERSE"  );

  CUDA_SAFE_CALL(cudaMemcpy(auxbuffer,self->d_fftwork,
			    sizeof(cufftComplex)*dimbuff,
			    cudaMemcpyDeviceToHost));
}


void *  AllocPinned(Gpu_Context * self, size_t size ) {
  void *res;
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 
  // CUDA_SAFE_CALL(
  cuMemHostAlloc(&res,size ,CU_MEMHOSTALLOC_WRITECOMBINED || CU_MEMHOSTALLOC_DEVICEMAP ); // CU_MEMHOSTALLOC_PORTABLE    );
  //);

  // CUDA_SAFE_CALL(cudaHostAlloc(&res,size ,cudaHostAllocPortable  ););

  // cudaHostAlloc(&res,size ,cudaHostAllocDefault  );
  printf(" memoria %ld \n", res );
  return res; 
}

void FreePinned(Gpu_Context * self,void *ptr) {
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 
  cudaFreeHost(ptr);
}

void gpu_pagFree( Gpu_pag_Context *   self	)  {
  char messaggio[1000];
  CUDA_SAFE_CALL(cudaFree(self->d_fftwork) );
  CUDA_SAFE_CALL(cudaFree(self->d_kernelbuffer) );
  CUFFT_SAFE_CALL (cufftDestroy(*((cufftHandle*)self->FFTplan_ptr)), "doing cufftDestroy ");
  delete ((cufftHandle*)self->FFTplan_ptr ) ; 
};



void gpu_pagInit( Gpu_pag_Context *   self	)  {
  char messaggio[1000];
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 
  int dimbuff = self->size_pa0*self->size_pa1; 
  self->FFTplan_ptr = (void * )  new cufftHandle;
  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_fftwork),
			    sizeof(cufftComplex)*dimbuff));
  CUFFT_SAFE_CALL( cufftPlan2d( (cufftHandle*)   self->FFTplan_ptr,
				self->size_pa0 ,self->size_pa1  ,
				CUFFT_C2C ), " doing a cufftPlan2d  " );
  CUDA_SAFE_CALL(cudaMalloc((void**)&(self->d_kernelbuffer),
			    sizeof(cufftComplex)*dimbuff));
  CUDA_SAFE_CALL(cudaMemcpy(self->d_kernelbuffer,self->kernelbuffer,
			    sizeof(cufftComplex)*dimbuff,
			    cudaMemcpyHostToDevice));
};


// ============================================================================================================


texture<float,2> Img_texture;
texture<float,2> Der0_texture;
texture<float,2> Der1_texture;

#define blksizeX 16
#define blksizeY 16


__global__ static void     grad_kernel (int dim0, int  dim1, int pitch,  float *deri_0 ,  float *deri_1) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;
  const int J =  bidx*blksizeX +  tidx;  
  const int II =  bidy*blksizeY +  tidy; 


  if(II<dim0 ) { 
    if(J<dim1-1) {
      ((float *) (((char *)deri_1)  + II*pitch ))[ J ]  =tex2D( Img_texture, J+1.5f, II+0.5f)- tex2D( Img_texture, J+0.5f, II+0.5f)  ; 
    } else {
      ((float *) (((char *)deri_1)  + II*pitch ))[ J ]  = 0.0f ; 
    }
  }
  if(J<dim1 ) { 
    if(II<dim0-1) {
      ((float *) (((char *)deri_0)  + II*pitch ))[ J ]  =tex2D( Img_texture, J+0.5f, II+1.5f)- tex2D( Img_texture, J+0.5f, II+0.5f)  ; 
    } else {
      ((float *) (((char *)deri_0)  + II*pitch ))[ J ]  = 0.0f ; 
    }
  }
};

__global__ static void      normgrad_kernel ( int dim0, int  dim1, int pitch, float *normgrad){
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;
  
  const int II =  bidy*blksizeY +  tidy; 
  const int J =  bidx*blksizeX +  tidx; 
  float d0,d1;
  if(II<dim0 ) { 
    if(J<dim1-1) {
      d0=tex2D( Img_texture, J+1.5f, II+0.5f)- tex2D( Img_texture, J+0.5f, II+0.5f)  ; 
    } else {
      d0 = 0.0f ; 
    }
  }
  if(J<dim1 ) { 
    if(II<dim0-1) {
      d1=tex2D( Img_texture, J+0.5f, II+1.5f)- tex2D( Img_texture, J+0.5f, II+0.5f)  ; 
    } else {
      d1= 0.0f ; 
    }
  }
  ((float *) (((char *)normgrad)  + II*pitch ))[ J ]  = sqrt( d0*d0+d1*d1  );
}

__global__ static void     div_kernel (int dim0, int  dim1, int pitch, float *div, float w) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;
  
  const int II =  bidy*blksizeY +  tidy; 
  const int J =  bidx*blksizeX +  tidx; 

  float res=0.0f;

  if(II<dim0  && J<dim1 ) {
    res  = tex2D( Der0_texture, J+0.5f, II+0.5f)+ tex2D( Der1_texture, J+0.5f, II+0.5f)  ;
  }

  if(II>0 ) {
    res -= tex2D( Der0_texture, J+0.5f, II-0.5f) ;
  }
  if(J>0 ) {
    res -= tex2D( Der1_texture, J-0.5f, II+0.5f) ;
  }
  ((float *) (((char *)div)  + II*pitch ))[ J ]  = res*w ; 
};

__global__ static void   projdual_kernel (int dim0, int  dim1, int pitch, 
					  float *grad_tmp0 , float *grad_tmp1 ,
					  float *deri_0, float * deri_1  ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;
  
  const int II =  bidy*blksizeY +  tidy; 
  const int J =  bidx*blksizeX +  tidx; 
  
  float norm=0.0f,d0,d1;
  if(II<dim0  && J<dim1 ) {
    d0=((float *) (((char *)deri_0)  + II*pitch ))[ J ];
    d1=((float *) (((char *)deri_1)  + II*pitch ))[ J ];
    norm = max( sqrt(d0*d0+d1*d1),1.0f);
    ((float *) (((char *)grad_tmp0)  + II*pitch ))[ J ] = d0/norm;
    ((float *) (((char *)grad_tmp1)  + II*pitch ))[ J ] = d1/norm;
  }
}

__global__ static void   linearcomb_kernel (int dim0, int  dim1, int pitch, 
					    float *res , float *va ,float a,
					    float *vb, float b  ) {
  const int tidx = threadIdx.x;
  const int bidx = blockIdx.x;
  const int tidy = threadIdx.y;
  const int bidy = blockIdx.y;
  
  const int II =  bidy*blksizeY +  tidy; 
  const int J =  bidx*blksizeX +  tidx; 
  
  if(II<dim0  && J<dim1 ) {
    ((float *) (((char *)res)  + II*pitch ))[ J ] =   
      ((float *) (((char *)va)  + II*pitch ))[ J ] *a
      +
      ((float *) (((char *)vb)  + II*pitch ))[ J ] *b
      ;
  }
}
 

 
float dual_gap(dim3 &dimGrid, dim3 &dimBlock,
	       int dim0, int dim1, 
	       float  im_norm, 
	       float *new_,
	       float *gap,
	       float weight,
	       float *normsgrad,
	       int pitch_img,
	       cudaChannelFormatDesc &ch_desc) {
  
  CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Img_texture ,new_ , ch_desc, dim0, dim1,pitch_img )) ;  
  normgrad_kernel<<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img, normsgrad  );

  float normgrad =  cublasSasum (  dim0* pitch_img/sizeof(float) ,normsgrad , 1);
  float tv_new = 2 * weight * normgrad ; 
  // float norm2gap =  cublasSdot ( dim0* pitch_img/sizeof(float) ,   gap   , 1 , gap , 1); ; 

  float norm2gap =   cublasSnrm2( dim0* pitch_img/sizeof(float) ,   gap   , 1 ); 
  norm2gap *=norm2gap;

  // float norm2new_ =  cublasSdot ( dim0* pitch_img/sizeof(float) ,   new_   , 1 , new_ , 1); ; 
  float norm2new_ =  cublasSnrm2 ( dim0* pitch_img/sizeof(float) ,   new_   , 1 ); ; 
  norm2new_*=norm2new_;

  float dual_gap =  norm2gap + tv_new - im_norm + norm2new_ ; 
  return 0.5f / im_norm * dual_gap;
}
	  
float probe(float *d, int pdim ) {
  return cublasSdot (pdim,   d   , 1 , d  , 1);
}

// ===========================================================================================================


void put_patches( float *pimg, float *imgA,int dim0, int dim1, int s0, int s1,
		  int end0, int end1, int dim_patches, int direction,
		  float *paverages, float *dists, float *freqs, int vectoriality) {

  int doppio=vectoriality;
  float *img_s[] ={imgA, imgA+ dim0*dim1 , NULL }; 
  int pos=0;
  int iwidget=0;
  if(direction==1) {
    for(int i0=0; i0<end0; i0++) {
      for(int i1=0; i1<end1; i1++) {	

	for(int idop =0; idop<doppio; idop++) {
	  float *img = img_s[idop];
	  paverages[iwidget] = 0;
	  for(int pi0= 0; pi0<dim_patches; pi0++) {
	    for(int pi1= 0; pi1<dim_patches; pi1++) {
	      pimg[pos] = img[ (s0+ i0*dim_patches + pi0 )*dim1  +s1  + i1*dim_patches + pi1   ];
	      paverages[iwidget]+= pimg[pos] ; 
	      pos++;
	    }
	  }
	  paverages[iwidget] /= (dim_patches)*(dim_patches);
	  for(int i=pos-(dim_patches)*(dim_patches); i<pos; i++) pimg[i] -= paverages[iwidget]  ;
	  iwidget++;
	}
      }
    }
  } else {
    float C = 0.5f*(dim_patches-1), d,d0;
    int address ; 
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {

	for(int idop =0; idop<doppio; idop++) {
	  float *img = img_s[idop];
	  for(int pi0= 0; pi0<dim_patches; pi0++) {
	    d0=fabs(pi0-C);
	    for(int pi1= 0; pi1<dim_patches; pi1++) {
	      d = d0 + fabs(pi1-C);
	      address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ; 
	      if(d< dists[ address + idop*dim0*dim1 ] ) {
		img[ address  ]= pimg[pos] + paverages[iwidget]  ;
		dists[ address + idop*dim0*dim1]=d;
		freqs[ address + idop*dim0*dim1]=1;
	      } else if ( d == dists[ address + idop*dim0*dim1 ]  ) {
		img[ address  ]= ((pimg[pos]+paverages[iwidget]) + img[ address]*freqs[ address+ idop*dim0*dim1 ] )/(  freqs[ address+ idop*dim0*dim1 ]+1 ) ;
		freqs[ address+ idop*dim0*dim1 ] +=1 ;
	      }
	      pos++;
	    }
	  }
	  iwidget++;
	}
      }
    }
  }
}






void put_patches( float *pimg, float *img, int dim1, int s0, int s1,
		  int end0, int end1, int dim_patches, int direction,
		  float *paverages, float *dists, float *freqs) {
  int pos=0;
  int iwidget=0;
  if(direction==1) {
    for(int i0=0; i0<end0; i0++) {
      for(int i1=0; i1<end1; i1++) {	
	paverages[iwidget] = 0;
	for(int pi0= 0; pi0<dim_patches; pi0++) {
	  for(int pi1= 0; pi1<dim_patches; pi1++) {
	    pimg[pos] = img[ (s0+ i0*dim_patches + pi0 )*dim1  +s1  + i1*dim_patches + pi1   ];
	    paverages[iwidget]+= pimg[pos] ; 
	    pos++;
	  }
	}
	paverages[iwidget] /= (dim_patches)*(dim_patches);
	for(int i=pos-(dim_patches)*(dim_patches); i<pos; i++) pimg[i] -= paverages[iwidget]  ;
	iwidget++;
      }
    }
  } else {
    float C = 0.5f*(dim_patches-1), d,d0;
    int address ; 
    for(int i0=0 ; i0<end0; i0++) {
      for(int i1=0 ; i1<end1; i1++) {
	for(int pi0= 0; pi0<dim_patches; pi0++) {
	  d0=fabs(pi0-C);
	  for(int pi1= 0; pi1<dim_patches; pi1++) {
	    d = d0 + fabs(pi1-C);
	    address =   ( s0 + i0*dim_patches + pi0 )*dim1  + s1  + i1*dim_patches + pi1 ; 
	    if(d< dists[ address ] ) {
	      img[ address  ]= pimg[pos] + paverages[iwidget]  ;
	      dists[ address ]=d;
	      freqs[ address ]=1;
	    } else if ( d == dists[ address ]  ) {
	      img[ address  ]= ((pimg[pos]+paverages[iwidget]) + img[ address]*freqs[ address ] )/(  freqs[ address ]+1 ) ;
	      freqs[ address ] +=1 ;
	    }
	    pos++;
	  }
	}
	iwidget++;
      }
    }
  }
}



__global__  static  void kern_shrink(int nitems, int ncomps,float *d_w,  float *d_dw,  float  weight, float *d_notzero ){
  int gid;
  float w0,w1,tmp,  notzero;

  gid = threadIdx.x + blockIdx.x*blockDim.x;
  // int iwidget = gid/ncomps  ; 

  if(gid<nitems*ncomps) {

    // ss = d_ss[iwidget];

    w0 = d_w[gid];
    tmp = w0+d_dw[gid];
    // w1 = copysignf( max(fabs(tmp)-ss*weight,0.0f) , tmp) ;
    w1 = copysignf( max(fabs(tmp)-weight,0.0f) , tmp) ;
    d_w [gid] = w1;
    notzero=0.0f;

    if(w1!=0.0f) {
      notzero = 1.0f;
    } 

    d_dw[gid] = w1 - w0;
    d_notzero[gid]=notzero;

  }
}



// _global__  static  void  kern_omp_0( float *d_sc, float *d_cc, int nwidget, int pitchpatch, float *d_w) {
//   int gid;
//   gid = threadIdx.x + blockIdx.x*blockDim.x;
//   if(gid<nwidget) {
//     d_w[0*pitchpatch+gid] =  d_sc[0*pitchpatch+gid]/d_cc[(0*1+0)*pitchpatch+gid] ; 
//   }
// }


template<int n_pursuit>
__global__  static  void  kern_omp( float * d_sc, float *d_cc, int nwidget, int pitchpatch, float *d_w, float *d_ss, float tol) {
  int gid;
  gid = threadIdx.x + blockIdx.x*blockDim.x;
  float v[n_pursuit][n_pursuit], scal, coeffs[n_pursuit];
  int  i,j,k,l;
  v[0][0]=1.0f;
  if(gid<nwidget) {
    for(k=0; k<n_pursuit; k++) {
      coeffs[k]=0.0f;
    }
    for(i=1; i<n_pursuit; i++) {
      if( fabs(d_sc[i*pitchpatch+gid]) > tol* d_ss[gid]) { 
	for(j=0; j<n_pursuit; j++) {
	  v[i][j]=0.0f;
	}
	v[i][i]=1.0;
	for(j=0; j<i; j++) {
	  scal=0.0f;
	  for(k=0; k<=j; k++) {
	    for(l=0; l<=i; l++) {	    
	      scal += v[i][l] *v[j][k] *   d_cc[ (k*n_pursuit+l)*pitchpatch+gid ] ; 
	    }
	  }
	  for(k=0; k<=j; k++) {
	    v[i][k] -=  scal *v[j][k] ; 
	  }
	  scal=0.0f;
	  for(k=0; k<=i; k++) {
	    for(l=0; l<=i; l++) {	    
	      scal += v[i][l] *v[i][k] *   d_cc[ (k*n_pursuit+l)*pitchpatch+gid ] ; 
	    }
	  }
	  scal=1.0/sqrt(scal);
	  for(k=0; k<=i; k++) {
	    v[i][k] =  scal *v[i][k] ; 
	  }
	}
      }
    } 
    for(i=0; i<n_pursuit; i++) {
      if( fabs(d_sc[i*pitchpatch+gid]) > tol* d_ss[gid]) { 
	scal=0.0f;
	for(k=0; k<=i; k++) {
	  scal+= v[i][k] * d_sc[k*pitchpatch+gid]; 
	}
	for(k=0; k<=i; k++) {
	  coeffs[k]+= v[i][k] * scal; 
	}
      }
    }
    for(k=0; k<n_pursuit; k++)  d_w[k*pitchpatch+gid] =  coeffs[k] ; 
  }
}







float  tv_denoising_patches_this_L1(float * d_y,float * d_R, float *d_X,float * d_wold, float *d_w ,float *d_dw ,
				    int nwidget, int n_comps, int sizepatch, float *pimg  , float weight,
				    float Lipschitz, float *d_notzero, float *d_ss, float *ftmps, int N_ITERS_DENOISING
				    ) {


  CUDA_SAFE_CALL(cudaMemcpy ( d_y,  pimg ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  CUDA_SAFE_CALL(cudaMemcpy ( d_R,  d_y  ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToDevice  ));
  CUDA_SAFE_CALL( cudaMemset( d_w,  0    ,  nwidget*n_comps*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMemset( d_wold,  0    ,  nwidget*n_comps*sizeof(float)    ) )	;


  float tol=1.0e-10 ;
  float rtol =  cublasSnrm2(  nwidget*sizepatch  ,  d_y    , 1);
  rtol = rtol*rtol*tol; 
  
  int maxiter=N_ITERS_DENOISING;
  float res=0.0f;

  float t=1.0f; 

  {
    int iw, i;
    float tmp;
    for(iw=0; iw<nwidget; iw++) {
      ftmps[iw]=0.0f;
      for(i=0; i<sizepatch; i++) {
	tmp =  pimg[iw*sizepatch+i] ; 
	ftmps[iw] +=   tmp*tmp ; 
      }
      ftmps[iw]=sqrt(ftmps[iw]);
    }
    CUDA_SAFE_CALL(cudaMemcpy (d_ss, ftmps,  nwidget*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  }



  for(int n_iter=0; n_iter<maxiter; n_iter++) {
    
    float alpha= 1.0f/Lipschitz;
    float beta = 0.0f;
    
    cublasSgemm('T','N', nwidget, n_comps ,  sizepatch ,
		alpha,
		d_R    , sizepatch ,
		d_X    , sizepatch  ,
		beta,
		d_dw  , nwidget );
    
    if(1){
      dim3 blk,grd;
      blk = dim3( 512 , 1 , 1 );
      grd = dim3( iDivUp( nwidget*n_comps  ,512) , 1, 1 );
      // if(n_iter%100 ==0) t=1.0f;
      float told = t;
      t=(1.0f+sqrt(1.0f+4.0f*t*t))/2.0f;
      kern_shrink_fista<<<grd,blk>>>(nwidget,n_comps ,  d_w,    d_dw , d_wold, weight/Lipschitz, d_notzero, told, t); 
    } else {
      dim3 blk,grd;
      blk = dim3( 512 , 1 , 1 );
      grd = dim3( iDivUp( nwidget*n_comps  ,512) , 1, 1 );
      kern_shrink<<<grd,blk>>>(nwidget, n_comps ,  d_w,    d_dw ,  weight/Lipschitz, d_notzero); 
    }
    
    alpha=-1.0f;
    beta = 1.0f;
    
    cublasSgemm('N','T',sizepatch ,nwidget ,n_comps,
		alpha,
		d_X , sizepatch,
		d_dw, nwidget,
		beta,
		d_R  , sizepatch );
    
    
    
    float wmax  = 0.0f;
    float dwmax = 0.0f;
    int imax = cublasIsamax(nwidget*n_comps,d_dw ,1 );
    CUDA_SAFE_CALL(cudaMemcpy  (  &dwmax,  d_dw+imax-1,  1*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
    imax = cublasIsamax(nwidget*n_comps,d_w ,1 );
    CUDA_SAFE_CALL(cudaMemcpy  (  &wmax,  d_w+imax-1,  1*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
    
    dwmax=fabs(dwmax);
    wmax=fabs(wmax);
    
    float notzero = cublasSasum (nwidget*n_comps ,d_notzero , 1);
    if((n_iter+1)%100==0) printf(" SPARSITA %e \n", notzero/(nwidget*n_comps));
    
    res = cublasSasum (nwidget*n_comps ,d_w , 1)*weight; 
    float norma =  cublasSnrm2( nwidget*sizepatch  ,  d_R    , 1);
    res=res+norma*norma/2.0f;
    if((n_iter+1)%100==0) printf("n_iter %d dwmax/wmax, wmax  dwmax %e %e %e  error %e \n", n_iter,  dwmax/wmax, wmax, dwmax, res);
    // if(wmax==0.0f || dwmax/wmax < tol) break;
    if(n_iter==maxiter-1) printf(" attenzione patches denoising esce prima convergenza\n");
    
  }
  float alpha= -1.0f;
  cublasSaxpy ( sizepatch*nwidget ,  alpha, d_R, 1, d_y, 1);
  {
    float alpha= 1.0f;
    float beta = 0.0f;
     cublasSgemm('N','T',sizepatch ,nwidget ,n_comps,
		 alpha,
		 d_X , sizepatch,
		 d_w, nwidget,
		 beta,
		 d_y  , sizepatch );
  }


  {
    for(int i=0; i<nwidget*sizepatch ; i++) pimg[i]=0.0;
  }

  CUDA_SAFE_CALL(cudaMemcpy  ( pimg , d_y ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
  return res;

}

 



float  tv_denoising_patches_L1(Gpu_Context * self,int dim0,int dim1,float  *img,float  *imgresultA , float weight,
			       float *patches, int n_comps, int dim_patches, int N_ITERS_DENOISING,
			       int vectoriality )  {
  int doppio= vectoriality;
   
  int np0    = (dim0/dim_patches);
  int np1    = (dim1/dim_patches);
  // int marge0 = np0*dim_patches;
  // int marge1 = np1*dim_patches;

  float * pimg = (float *) malloc( dim0*dim1*doppio*sizeof(float));
  float * paverages  = (float *) malloc( (np0+1)*(np1+1)*doppio*sizeof(float));
  float *d_y, *d_R, *d_X, *d_tmp, *d_w, *d_dw, *d_ss, *d_notzero;
  float res=0.0f;

  float * dists = (float *) malloc( doppio*dim0*dim1*sizeof(float));
  float * freqs = (float *) malloc( doppio*dim0*dim1*sizeof(float));
  float *ftmps;
  int sizepatch = dim_patches*dim_patches*doppio; 


  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 

  
  float *imgresult;
  if(img!=imgresultA) {
    imgresult=imgresultA ; 
  } else {
    imgresult = (float *) malloc( doppio*dim1*dim0*sizeof(float)   ) ;
  }

  memcpy(imgresult, img, doppio*dim1*dim0*sizeof(float) );

  ftmps = (float *) malloc( doppio*dim_patches*dim_patches*(np0+1)*(np1+1)*sizeof(float) );



  CUDA_SAFE_CALL( cudaMalloc( 	&d_R,  dim0*dim1*doppio*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_y,  dim0*dim1*doppio*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_X,  n_comps*dim_patches*dim_patches*doppio*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_tmp,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )	;

  CUDA_SAFE_CALL( cudaMalloc( 	&d_w,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_dw,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_ss,  (np0+1)*(np1+1)*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_notzero,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )	;

  CUDA_SAFE_CALL(cudaMemcpy (   d_X , patches , n_comps*dim_patches*dim_patches*doppio*sizeof(float)   ,cudaMemcpyHostToDevice  ));

  static float Lipschitz=0.0;

  if( Lipschitz==0.0)
  {
  
    float v0[n_comps];
    for(int i=0; i<n_comps; i++) {
      v0[i]=0.0f;
    }
    v0[0]=1;
    CUDA_SAFE_CALL(cudaMemcpy (   d_w , v0 , n_comps*sizeof(float)   ,cudaMemcpyHostToDevice  ));



    for(int ivolta=0; ivolta<100; ivolta++) {
    
      float alpha= 1.0f;
      float beta = 0.0f;
      
      cublasSgemm('N','N',sizepatch ,1 ,n_comps,
		  alpha,
		  d_X  , sizepatch,
		  d_w, n_comps ,
		  beta,
		  d_dw  , sizepatch );

      cublasSgemm('T','N',n_comps ,1 ,sizepatch,
		  alpha,
		  d_X  , sizepatch,
		  d_dw, sizepatch ,
		  beta,
		  d_w  , n_comps );
      float norma =  cublasSnrm2( n_comps  ,  d_w    , 1);
      printf("ivolta %d  norma %e\n", ivolta, norma);
      cublasSscal(n_comps, 1.0f/norma , d_w, 1) ;
      Lipschitz=1.1*norma;
    }


  }
  {
    memset(freqs, 0,  doppio*dim0*dim1*sizeof(float)  );
    int i;
    for(i=0; i< dim0*dim1*doppio; i++) {
      dists[i]=1.0e10;
    }
    int shift0, shift1; 
    for( shift0=0; shift0<dim_patches ; shift0+= self->STEPFORPATCHES) {
      for( shift1=0; shift1<dim_patches ; shift1+= self->STEPFORPATCHES) {
	int nw0    = ((dim0-shift0)/dim_patches);
	int nw1    = ((dim1-shift1)/dim_patches);

        printf(" QUI tol --   %d %d\n",  self->STEPFORPATCHES,dim_patches );

	put_patches(pimg,img, dim0, dim1, shift0,shift1,nw0,nw1, dim_patches ,1 , paverages, dists, freqs, vectoriality);
	// put_patches(pimg,img, dim1, shift0,shift1,nw0,nw1, dim_patches ,1 , paverages, dists, freqs);

	res+=tv_denoising_patches_this_L1( d_y, d_R, d_X, d_tmp, d_w , d_dw,nw0*nw1 , n_comps,sizepatch , pimg ,
					   weight ,Lipschitz, d_notzero, d_ss, ftmps ,N_ITERS_DENOISING);

	put_patches(pimg,imgresult, dim0, dim1, shift0,shift1,nw0,nw1,  dim_patches ,-1 , paverages, dists, freqs, vectoriality);
	// put_patches(pimg,imgresult,  dim1, shift0,shift1,nw0,nw1,  dim_patches ,-1 , paverages, dists, freqs);
      }
    }

  }

  if(img!=imgresultA) {
  } else {
    memcpy( imgresultA ,imgresult , doppio*dim1*dim0*sizeof(float) );
    free(imgresult);
  }


  free(dists);
  free(freqs);
  free(pimg);
  free(paverages);
  free(ftmps);
  CUDA_SAFE_CALL(cudaFree(d_R  ));
  CUDA_SAFE_CALL(cudaFree(d_y  ));
  CUDA_SAFE_CALL(cudaFree(d_X  ));
  CUDA_SAFE_CALL(cudaFree(d_tmp));
  CUDA_SAFE_CALL(cudaFree(d_w  ));
  CUDA_SAFE_CALL(cudaFree(d_dw ));
  CUDA_SAFE_CALL(cudaFree(d_notzero ));
  CUDA_SAFE_CALL(cudaFree(d_ss ));

  return res; 
}


 
void   tv_denoising_patches_this_OMP(float * d_y,float * d_R, float *d_X, float *comps,float *d_w ,float *d_dw ,int nwidget, int n_comps,int dim_patches,
				 float *pimg  ,
				 float * comp_scalprods, float *cs_scalprods,
				 float **d_vects, float *ftmps, float *ftmps2, int *choosedcomps, int npursuit,
				 float *d_ss,float *d_sc, float *d_cc, int pitchpatch, float *cr_scalprods,
				 float tol) {


  int sizepatch = dim_patches*dim_patches; 

  CUDA_SAFE_CALL(cudaMemcpy ( d_y,  pimg ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  CUDA_SAFE_CALL(cudaMemcpy ( d_R,  d_y  ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToDevice  ));
  CUDA_SAFE_CALL( cudaMemset( d_w,  0    ,  npursuit*pitchpatch*sizeof(float)    ) )	;

  {
    float alpha=1.0, beta=0.0;
    cublasSgemm('T','N',  n_comps , nwidget,  sizepatch ,
		alpha,
		d_X    , sizepatch  ,
		d_R    , sizepatch ,
		beta,
		d_dw  , n_comps );
    CUDA_SAFE_CALL(cudaMemcpy (cs_scalprods , d_dw   ,  nwidget*n_comps*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
  }


  {
    int iw, i;
    float tmp;
    for(iw=0; iw<nwidget; iw++) {
      ftmps[iw]=0.0f;
      for(i=0; i<sizepatch; i++) {
	tmp =  pimg[iw*sizepatch+i] ; 
	ftmps[iw] +=   tmp*tmp ; 
      }
      ftmps[iw]=sqrt(ftmps[iw]);
    }
    CUDA_SAFE_CALL(cudaMemcpy (d_ss, ftmps,  nwidget*sizeof(float)   ,cudaMemcpyHostToDevice  ));
  }
  int iw, ic;
  int  imax;
  float wmax,tmp;
  int i_pursuit;
  
  for(i_pursuit=0;i_pursuit<npursuit; i_pursuit++)   {  
    {
      float alpha=1.0, beta=0.0;
      cublasSgemm('T','N',  n_comps , nwidget,  sizepatch ,
		  alpha,
		  d_X    , sizepatch  ,
		  d_R    , sizepatch ,
		  beta,
		  d_dw  , n_comps );
    }

    CUDA_SAFE_CALL(cudaMemcpy (cr_scalprods , d_dw   ,  nwidget*n_comps*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
  
    for(iw=0; iw<nwidget; iw++) {
      for(ic=0; ic<n_comps; ic++) {
	if(cr_scalprods[iw*n_comps+ic  ]==0.0f ) cr_scalprods[iw*n_comps+ic  ]=1.0e-32; 
      }
    }    

    for(iw=0; iw<nwidget; iw++) {
      // annullo i precedenti
      for(ic=0; ic<i_pursuit; ic++)  cr_scalprods[iw*n_comps+  choosedcomps[ iw*npursuit +ic ] ]=0.0f ; 
    }


    for(iw=0; iw<nwidget; iw++) {
      imax=0;
      wmax =  fabs(cr_scalprods[iw*n_comps+0  ]) ; 
      for(ic=1; ic<n_comps; ic++) {
	tmp = fabs(cr_scalprods[iw*n_comps+ic  ]) ; 
	if(   tmp>wmax )  {
	  wmax=tmp;
	  imax=ic ; 
	}
      }
      ftmps2[iw ] = cs_scalprods[iw*n_comps+imax  ]; 
      choosedcomps[ iw*npursuit +i_pursuit ] = imax ; 
      memcpy(ftmps +  iw*sizepatch, comps +imax* sizepatch   ,  sizepatch*sizeof(float));
      // cudaMemcpy( d_vects[i_pursuit] + iw*sizepatch,  d_X+imax* sizepatch      , sizepatch*sizeof(float) , cudaMemcpyDeviceToDevice ) ; 
    }
    cudaMemcpy( d_vects[i_pursuit] ,  ftmps    ,nwidget* sizepatch*sizeof(float) , cudaMemcpyHostToDevice ) ;


    cudaMemcpy( d_sc + i_pursuit*pitchpatch,   ftmps2    , nwidget*sizeof(float) , cudaMemcpyHostToDevice ) ; 
    {
      int ic1, ic2, ip1, ip2;
      for(ic1=0; ic1<=i_pursuit; ic1++) {
	for(ic2=0; ic2<=i_pursuit; ic2++) {
	  for(iw=0; iw<nwidget; iw++) {
	    ip1 =  choosedcomps[ iw*npursuit +ic1 ]  ;
	    ip2 =  choosedcomps[ iw*npursuit +ic2 ]  ;
	    ftmps[ iw   ]  =   comp_scalprods[ ip1*n_comps+ip2]   ;
	  }
	  cudaMemcpy( d_cc + (ic1*(i_pursuit+1)+ic2)*pitchpatch  , ftmps   ,  nwidget*sizeof(float)  ,  cudaMemcpyHostToDevice) ; 
	}
      }
    }
    
    {
      dim3 blk,grd;
      blk = dim3( 256 , 1 , 1 );
      grd = dim3( iDivUp( nwidget  ,256) , 1, 1 );
      
      printf(" la tolleranza est %e \n", tol);


      if( i_pursuit==0)    kern_omp<1><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==1)    kern_omp<2><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==2)    kern_omp<3><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==3)    kern_omp<4><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==4)    kern_omp<5><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==5)    kern_omp<6><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      if( i_pursuit==6)    kern_omp<7><<<grd,blk>>>( d_sc,d_cc, nwidget, pitchpatch, d_w,d_ss,tol);
      
      if( i_pursuit >6) {
	printf(" Error : aumenta i template per npursuit\n");
	exit(1);
      }
    }
    
    CUDA_SAFE_CALL(cudaMemcpy ( d_R,  d_y  ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToDevice  ));
    for(ic=0; ic <= i_pursuit; ic++) {
      dim3 blk,grd;
      blk = dim3( 256 , 1 , 1 );
      grd = dim3( iDivUp( nwidget*sizepatch ,256) , 1, 1 );

      kern_subtract<<<grd,blk>>>(d_R, d_vects[ic],  d_w+ic*pitchpatch  , nwidget*sizepatch,  sizepatch  );

    }
  }

  {
    float alpha= -1.0f;
    cublasSaxpy ( sizepatch*nwidget ,  alpha, d_R, 1, d_y, 1);
  }
  
  CUDA_SAFE_CALL(cudaMemcpy  ( pimg , d_y ,  nwidget*sizepatch*sizeof(float)   ,cudaMemcpyDeviceToHost  ));  
}



float  tv_denoising_patches_OMP(Gpu_Context * self,int dim0,int dim1,float  *img,float  *imgresult , float weight,
			    float *comps, int n_comps, int dim_patches )  {

  printf("dim_patches %d\n", dim_patches);

  int np0    = (dim0/dim_patches);
  int np1    = (dim1/dim_patches);
  //int marge0 = np0*dim_patches;
  //int marge1 = np1*dim_patches;
  float * pimg = (float *) malloc( dim0*dim1*sizeof(float));

  float * dists = (float *) malloc( dim0*dim1*sizeof(float));
  float * freqs = (float *) malloc( dim0*dim1*sizeof(float));

  float * tmp  = (float *) malloc( dim0*dim1*sizeof(float));
  float * tmp2  = (float *) malloc( (np0+1)*(np1+1)*sizeof(float));
  float * paverages  = (float *) malloc( (np0+1)*(np1+1)*sizeof(float));
  float *d_y, *d_R, *d_X,  *d_w, *d_dw, *d_ss,  *d_sc, *d_cc;
  float res=0.0f;
  float *comp_scalprods, *cs_scalprods, *cr_scalprods;
  int npursuit = (int) weight;
  float tol = weight-npursuit ; 
  printf(" QUI weight  %e \n", weight );

  int   *choosedcomps = (int*)  malloc( npursuit * (np0+1)*(np1+1)*sizeof(int));
  int i;

  int pitchpatch =  iDivUp( (np0+1)*(np1+1)  ,32)*32 ;  
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 

  memcpy(imgresult, img, dim1*dim0*sizeof(float) );

  int sizepatch = dim_patches*dim_patches ; 

  CUDA_SAFE_CALL( cudaMalloc( 	&d_R,    dim0*dim1*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_y,    dim0*dim1*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_X,    n_comps* sizepatch *sizeof(float)    ) )	;
  // CUDA_SAFE_CALL( cudaMalloc( 	&d_tmp,  (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_w,    pitchpatch*npursuit*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_dw,   (np0+1)*(np1+1)*n_comps*sizeof(float)    ) )	;

  CUDA_SAFE_CALL( cudaMalloc( 	&d_ss,  pitchpatch*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_sc,  npursuit*pitchpatch*sizeof(float)    ) )	;
  CUDA_SAFE_CALL( cudaMalloc( 	&d_cc,  npursuit*npursuit*pitchpatch*sizeof(float)    ) )	;

  comp_scalprods = (float*) malloc( n_comps* n_comps  *sizeof(float)  );
  cs_scalprods   = (float*) malloc( (np0+1)*(np1+1)  *n_comps* sizeof(float)  );
  cr_scalprods   = (float*) malloc( (np0+1)*(np1+1)  *n_comps* sizeof(float)  );

  float *d_vects[npursuit];
  for(i=0; i<npursuit; i++) {
    CUDA_SAFE_CALL( cudaMalloc( &(d_vects[i]) , (np0+1)*(np1+1)  * sizepatch*sizeof(float)  ) )	;
    CUDA_SAFE_CALL( cudaMemset( d_vects[i],  0    , (np0+1)*(np1+1)  * sizepatch *sizeof(float)    ) )	;
  }
  
  CUDA_SAFE_CALL(cudaMemcpy (   d_X , comps , n_comps* sizepatch *sizeof(float)   ,cudaMemcpyHostToDevice  ));
  {
    float *d_comp_scalprods;
    CUDA_SAFE_CALL( cudaMalloc(  &d_comp_scalprods,  n_comps*n_comps*sizeof(float)  ));
    
    float alpha= 1.0f;
    float beta = 0.0f;
    
    cublasSgemm('T','N',n_comps , n_comps ,  sizepatch ,
		alpha,
		d_X    , sizepatch ,
		d_X    , sizepatch  ,
		beta,
		d_comp_scalprods  , n_comps );

    CUDA_SAFE_CALL(cudaMemcpy(comp_scalprods , d_comp_scalprods , n_comps*n_comps*sizeof(float)   ,cudaMemcpyDeviceToHost  ));
    CUDA_SAFE_CALL(cudaFree( d_comp_scalprods ));
  }
  {
    memset(freqs, 0,  dim0*dim1*sizeof(float)  );
    for(i=0; i< dim0*dim1; i++) {
      dists[i]=1.0e10;
    }
    int shift0, shift1; 
    for( shift0=0; shift0<dim_patches ; shift0+=self->STEPFORPATCHES ) {
      for( shift1=0; shift1<dim_patches ; shift1+=self->STEPFORPATCHES ) {
	int nw0    = ((dim0-shift0)/dim_patches);
	int nw1    = ((dim1-shift1)/dim_patches);
	
	printf(" QUI tol -- %e %d %d\n", tol, self->STEPFORPATCHES,dim_patches );
	put_patches(pimg,img, dim1, shift0,shift1,nw0,nw1, dim_patches ,1 , paverages, dists, freqs);
	tv_denoising_patches_this_OMP( d_y, d_R, d_X,comps,   d_w , d_dw,  nw0*nw1  , n_comps, dim_patches,
				   pimg , comp_scalprods , cs_scalprods,
				   d_vects ,  tmp,  tmp2, choosedcomps, npursuit, d_ss, d_sc, d_cc, pitchpatch, cr_scalprods,
				   tol);
	
	put_patches(pimg,imgresult, dim1, shift0,shift1,nw0,nw1, dim_patches ,-1 , paverages, dists, freqs);
      }
    }
  }


  free(tmp);
  free(tmp2);

  for(i=0; i<npursuit; i++) {
    CUDA_SAFE_CALL( cudaFree( d_vects[i])  )	;
  }
  
  free(dists);
  free(freqs);
  free(comp_scalprods);
  free(pimg);
  free(paverages);
  free(choosedcomps);
  free(cs_scalprods   );
  free(cr_scalprods   );


  CUDA_SAFE_CALL(cudaFree(d_R  ));
  CUDA_SAFE_CALL(cudaFree(d_y  ));
  CUDA_SAFE_CALL(cudaFree(d_X  ));
  // CUDA_SAFE_CALL(cudaFree(d_tmp));
  CUDA_SAFE_CALL(cudaFree(d_w  ));
  CUDA_SAFE_CALL(cudaFree(d_dw ));
  CUDA_SAFE_CALL(cudaFree(d_ss ));
  CUDA_SAFE_CALL(cudaFree(d_sc ));
  CUDA_SAFE_CALL(cudaFree(d_cc ));

  return res; 
}


// =========================================================================================================
	
float  tv_denoising_fistagpu(Gpu_Context * self,int dim0,int dim1,float  *img,float  *result , float weight,
			     float eps , int n_iter_max,   int check_gap_frequency )  {

  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 

  int nDums = 11;
  float *d_dums[nDums];
  float dgap;
  int i_img      = 0;
  int i_grad_tmp0 = 1;
  int i_grad_tmp1 = 2;
  int i_grad_aux0 = 3;
  int i_grad_aux1 = 4;
  int i_grad_im0  = 5;
  int i_grad_im1  = 6;
  int i_gap       = 7;
  int i_new       = 8;
  int i_normgrad       = 9 ;
  int i_error     = 10;

  size_t pitch_img  ; 
  int Pdim;
  {
    for(int i=0; i<nDums; i++) {
      CUDA_SAFE_CALL( cudaMallocPitch( 	&d_dums[i],  &pitch_img,   dim1*sizeof(float)   , dim0  ) )	;
      CUDA_SAFE_CALL( cudaMemset     ( 	 d_dums[i],  0  ,  pitch_img  * dim0  ) )	;
      assert(     (  pitch_img %sizeof(float))==0);  // ceci devrait etre toujours vrai. 
                                                     // Surtout il le doit : Sinon je ne peux pas utiliser les dsum et scal de cublas
      Pdim  =  pitch_img / sizeof(float)  * dim0; 
    }
    CUDA_SAFE_CALL(cudaMemcpy2D ( d_dums[i_img], pitch_img, img,  dim1*sizeof(float), dim1*sizeof(float), dim0,  cudaMemcpyHostToDevice)) ;
  }

  float im_norm  ; 
  // im_norm = cublasSdot (Pdim,   d_dums[i_img ]    , 1 , d_dums[i_img ]  , 1);
  im_norm =  cublasSnrm2(Pdim,   d_dums[i_img ]    , 1);
  im_norm*=im_norm;



  int Gdim0, Gdim1;
  {
    Gdim0 = iAlignUp(dim0, blksizeY);
    Gdim1 = iAlignUp(dim1, blksizeX);
  }

  int grdsizeY, grdsizeX ;
  {
    grdsizeY = Gdim0 / blksizeY ;
    grdsizeX = Gdim1 / blksizeX ;    
  }
  dim3 dimGrid ( grdsizeX ,  grdsizeY  );
  dim3 dimBlock ( blksizeX , blksizeY    );
  
  cudaChannelFormatDesc ch_desc = cudaCreateChannelDesc<float>();
  Img_texture.filterMode = cudaFilterModePoint; 
  
  Der0_texture.filterMode = cudaFilterModePoint; 
  Der1_texture.filterMode = cudaFilterModePoint; 

  float t = 1.0f;    
  float t_new, t_factor;
  int   i = 0   ;    
  while( i < n_iter_max) {

    // error = weight * div(grad_aux) - im
    //
    CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Der0_texture , d_dums[i_grad_aux0], ch_desc, dim0, dim1,pitch_img )) ;
    CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Der1_texture , d_dums[i_grad_aux1], ch_desc, dim0, dim1,pitch_img )) ;

    div_kernel  <<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img , d_dums[i_error ] ,  weight );
    cublasSaxpy (Pdim, -1.0f ,  d_dums[i_img ]  , 1 ,d_dums[i_error ] , 1 );
    
    // grad_tmp = gradient(error)
    // grad_tmp *= 1./ (8 * weight)
    // grad_aux += grad_tmp

    CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Img_texture , d_dums[i_error], ch_desc, dim0, dim1,pitch_img )) ;

    grad_kernel <<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img,  d_dums[i_grad_tmp0 ],  d_dums[i_grad_tmp1 ] );

    cublasSaxpy (Pdim, 1.0f/ (8.0f * weight) ,   d_dums[i_grad_tmp0 ] , 1 ,d_dums[ i_grad_aux0] , 1 );
    cublasSaxpy (Pdim, 1.0f/ (8.0f * weight) ,   d_dums[i_grad_tmp1 ] , 1 ,d_dums[ i_grad_aux1] , 1 );



    //   grad_tmp = _projector_on_dual(grad_aux)
    projdual_kernel<<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img,
					   d_dums[i_grad_tmp0 ],  d_dums[i_grad_tmp1 ],
					   d_dums[i_grad_aux0 ],  d_dums[i_grad_aux1 ] );
 
 
    // t_new = 1. / 2 * (1 + np.sqrt(1 + 4 * t**2))
    // t_factor = (t - 1) / t_new
    t_new = 1.0f / 2.0f * (1.0f + sqrt(1.0f + 4.0f * t*t ));
    t_factor = (t - 1.0f) / t_new  ; 

    // grad_aux = (1 + t_factor) * grad_tmp - t_factor * grad_im
    // grad_im = grad_tmp
    // t = t_new
    
    linearcomb_kernel <<<dimGrid,dimBlock>>>( dim0,   dim1, pitch_img , 
					      d_dums[i_grad_aux0 ] ,
					      d_dums[i_grad_tmp0 ] ,(1 + t_factor) ,
					      d_dums[i_grad_im0  ] ,-t_factor ) ;
    linearcomb_kernel <<<dimGrid,dimBlock>>>( dim0,   dim1, pitch_img , 
					      d_dums[i_grad_aux1 ] ,
					      d_dums[i_grad_tmp1 ] ,(1 + t_factor) ,
					      d_dums[i_grad_im1  ] ,-t_factor ) ;

    // {
    // 	printf(" PROBE %e \n",  probe( d_dums[ i_grad_aux0] , Pdim  ));
    // 	printf(" %e %e  \n", t_new, t_factor  );
    // 	if(i==1) return;
    // }

    CUDA_SAFE_CALL(cudaMemcpy  (  d_dums[i_grad_im0], d_dums[ i_grad_tmp0 ],  dim0*pitch_img   ,cudaMemcpyDeviceToDevice  ));
    CUDA_SAFE_CALL(cudaMemcpy  (  d_dums[i_grad_im1], d_dums[ i_grad_tmp1 ],  dim0*pitch_img   ,cudaMemcpyDeviceToDevice  ));

    t = t_new;
    
    //
    if ( (i % check_gap_frequency) == 0 ) {

      // gap = weight * div(grad_im)
      // new = im - gap
      CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Der0_texture , d_dums[i_grad_im0], ch_desc, dim0, dim1,pitch_img )) ;
      CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Der1_texture , d_dums[i_grad_im1], ch_desc, dim0, dim1,pitch_img )) ;
      div_kernel  <<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img , d_dums[ i_gap ] ,  weight );
      linearcomb_kernel <<<dimGrid,dimBlock>>>( dim0,   dim1, pitch_img , 
						d_dums[i_new ] ,
						d_dums[i_img ] ,  1.0f ,
						d_dums[i_gap  ], -1.0f  ) ;



      // {
      // 	printf(" PROBE %e \n",  probe( d_dums[i_new ] , Pdim  ));
      // 	//return ; 
      // }

      //  dgap = dual_gap(im, new, gap, weight)
      dgap =  dual_gap(dimGrid, dimBlock,
			     dim0, dim1, 
			     im_norm, 
			     d_dums[i_new ] ,
			     d_dums[i_gap ] ,
			     weight,
			     d_dums[i_normgrad ],
			     pitch_img,
			     ch_desc)  ;

      // 
      if(dgap<eps) break;
    }
    i+=1;
  }
  if(i==n_iter_max) {
    printf("  tv_denoising counter reached allowed  maximum. Now exiting with dgap=%d\n", dgap);
  }
  
  
  CUDA_SAFE_CALL( cudaBindTexture2D( NULL,Img_texture ,d_dums[i_new ], ch_desc, dim0, dim1,pitch_img )) ;  
  normgrad_kernel<<<dimGrid,dimBlock>>> (dim0, dim1,pitch_img, d_dums[i_normgrad ]  );
  
  float normgrad =  cublasSasum (  dim0* pitch_img/sizeof(float) ,d_dums[i_normgrad ], 1);
  
  
  // return new
  {
    CUDA_SAFE_CALL(cudaMemcpy2D  (result , dim1*sizeof(float) , d_dums[ i_new ], pitch_img , dim1*sizeof(float), dim0,  cudaMemcpyDeviceToHost)) ;
  }
  
  //
  {
    for(int i=0; i<nDums; i++) {
      CUDA_SAFE_CALL(cudaFree(d_dums[i]));
    }
  }
  return normgrad;   
}



texture<float, 2, cudaReadModeElementType> texImage;

#define RADIUS   7
#define BRADIUS    5
#define AREA     ( (2 * BRADIUS + 1) * (2 * BRADIUS + 1) )
#define INV_AREA ( 1.0f / (float)AREA )

__global__ void NLM(
		    float *result,
		    int W,
		    int H,
		    float bruit,
		    float mix
		    )
{
  float bruit2=bruit*bruit;
  float Hf= bruit2*0.4f  ;
  const int ix = blockDim.x * blockIdx.x + threadIdx.x;
  const int iy = blockDim.y * blockIdx.y + threadIdx.y;
  const float x = (float)ix + 0.5f;
  const float y = (float)iy + 0.5f;
  int count=0;
  if (ix < W && iy < H)
    {
      float sumW = 0.0f ;
      float res = 0.0f ;
      float maxw=0.0f;
      for (float i = -RADIUS; i <= RADIUS; i++) {
	for (float j = -RADIUS; j <= RADIUS; j++)
	  {
	    if( i!=0 || j!=0  ) {
	      float distance = 0.0f;
	      float diff;
	      for (float n = -BRADIUS; n <= BRADIUS; n++) {
		for (float m = -BRADIUS; m <= BRADIUS; m++) {
		  diff = tex2D(texImage, x + j + m, y + i + n)-tex2D(texImage,     x + m,     y + n);
		  distance += diff*diff ;
		}
	      }
	      distance*= INV_AREA;
	      distance  =   min(max(distance-2*bruit2 , 0.0f  ),100.0f*Hf)  ; 
	      float weight     = __expf(- distance/Hf  );
	      if( weight >1.0e-10) {
		count++;
		float value = tex2D(texImage, x + j, y + i);
		res      += value * weight ;
		sumW  += weight ;
		if(weight>maxw) maxw=weight;
	      }
	    }
	  }
      }
      float orig = tex2D(texImage, x, y);
      if(count) {
	res   = res +maxw*orig;
	sumW += maxw; 
	res /= sumW;
	result[W * iy + ix] =  res*mix+(1.0f-mix)*orig ;
      } else {
	result[W * iy + ix] = orig;
      }
    }
}

void nonlocalmeans(    Gpu_Context * self,
		       float *result,
		       float *image,
		       int H,
		       int W,
		       float bruit,
		       float mix
		       )
{

  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ; 

  cudaArray *a_image;
  cudaMallocArray  (&a_image, &floatTex, W, H);
  cudaMemcpyToArray( a_image, 0, 0, image, H*W* sizeof(float),cudaMemcpyHostToDevice );
  CUDA_SAFE_CALL( cudaBindTextureToArray(texImage,(cudaArray*)a_image ) ); 


  float *d_result;
  cudaMalloc((void **)&d_result, H*W*sizeof(float));

  dim3 dimGrid (iDivUp(W,blksizeX ), iDivUp(H,blksizeY ));
  dim3 dimBlock (blksizeX, blksizeY);

  
  NLM<<<dimGrid,dimBlock >>>(d_result, W, H, bruit, mix);

  cudaMemcpy( result, d_result , H*W*sizeof(float), cudaMemcpyDeviceToHost);    

  CUDA_SAFE_CALL(cudaFree(d_result) );
  CUDA_SAFE_CALL(cudaFreeArray(a_image) );
  CUDA_SAFE_CALL( cudaUnbindTexture 	(   texImage  	 ));  
}






















