///----------------------------------------------------------------------------
///------------------------- SIRT-FBP filter ----------------------------------
///----------------------------------------------------------------------------

/**

  This file is not used anymore. The SIRT-Filtering process is done in the Python
  level.

*/



#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>
#include <cublas.h>
#include <cuComplex.h>
#include <string.h>
#include <hdf5.h>
#include "h5tools.c"

#define SF_VERBOSE 1
#define SF_DEBUG 1


/// ****************************************************************************
/// ************************* Code re-use  *************************************
/// ****************************************************************************


#define FROMCU
extern "C" {
#include<CCspace.h>
}
#  define CUDACHECK \
{ cudaThreadSynchronize(); \
  cudaError_t last = cudaGetLastError();\
  if(last!=cudaSuccess) {\
  printf("ERRORX: %s  %s  %i \n", cudaGetErrorString( last),    __FILE__, __LINE__    );    \
  exit(1);\
  }\
}
#define fftbunch 128
#define blsize_cufft 32

#  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);                                            \


#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);                                                    \
  } }


typedef struct ParamsForTomo {
  Gpu_Context *ctxstruct;
  float DETECTOR_DUTY_RATIO;
  int DETECTOR_DUTY_OVERSAMPLING;
} ParamsForTomo ;

int iDivUp(int a, int b);
int w_ipow2(int a);
int nextpow2_cp(int v);







/// ****************************************************************************
/// ******************** CUDA Kernels and calls ********************************
/// ****************************************************************************




__global__ void sf_kern_shift_reverse(float* xs_p, cufftComplex* xs_p_shift, int npx, int filter_size, int nproj) {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  if (gidy >= nproj) return;
  // Half right (+1) goes to the left : res[:, 0:npx/2 + 1] = xs_p[:, npx/2:npx]
  if (gidx < npx/2 +1) {
    xs_p_shift[gidy*filter_size + gidx].x = xs_p[gidy*npx + gidx + npx/2];
    //This new left part is reversed and goes to the right : res[:, Nres - npx/2:] = res[:, 1:npx/2 + 1][:, ::-1]
    if (gidx > 0) {
      xs_p_shift[gidy*filter_size + filter_size - npx/2 + npx/2-gidx].x = xs_p_shift[gidy*filter_size + gidx].x;
    }
  }
}


///
/// from the input "xs_p", performs the following :
///   out = zeros(num_projs, 2*nexpow)
///   out[:, 0:npx/2 + 1] = xs_p[:, npx/2:npx]
///   out[:, filter_size - npx/2:] = out[:, 1:npx/2 + 1][:, ::-1]
///  and cast the result to cufftcomplex type, to make a C2C FFT.
///
cufftComplex* sf_shift_reverse(float* xs_p, int npx, int filter_size, int num_projs) {
  cufftComplex* xs_p_shift;
  cudaMalloc(&xs_p_shift, filter_size*num_projs*sizeof(cufftComplex));
  cudaMemset(xs_p_shift, 0, filter_size*num_projs*sizeof(cufftComplex));

  dim3 n_blocks = dim3(iDivUp(filter_size, 8), iDivUp(num_projs, 8), 1);
  dim3 n_threads_per_block = dim3(8, 8, 1);
  sf_kern_shift_reverse<<<n_blocks, n_threads_per_block>>>(xs_p, xs_p_shift, npx, filter_size, num_projs);

  return xs_p_shift;
}


__global__ void sf_kern_discard_imag(cufftComplex* arr, float* out, int nrows, int ncols) {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  if (gidy < nrows && gidx < ncols) {
    out[gidy*ncols + gidx] = arr[gidy*ncols + gidx].x;
  }
}


float* sf_discard_imag(cufftComplex* arr, int ncols, int nrows) {
  float* out;
  cudaMalloc(&out, ncols*nrows*sizeof(float));
  dim3 n_blocks = dim3(iDivUp(ncols, 8), iDivUp(nrows, 8), 1);
  dim3 n_threads_per_block = dim3(8, 8, 1);
  sf_kern_discard_imag<<<n_blocks, n_threads_per_block>>>(arr, out, nrows, ncols);
  return out;
}


__global__ void sf_kern_cmul(cufftComplex* arr1, float* arr2, int nrows, int ncols) {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  if (gidy < nrows && gidx < ncols) {
    float tmp = arr2[gidy*ncols + gidx];
    arr1[gidy*ncols + gidx].x *= tmp;
    arr1[gidy*ncols + gidx].y *= tmp;
  }
}

int sf_call_crmul_kernel(cufftComplex* arr1, float* arr2, int ncols, int nrows) {
  dim3 n_blocks = dim3(iDivUp(ncols, 8), iDivUp(nrows, 8), 1);
  dim3 n_threads_per_block = dim3(8, 8, 1);
  sf_kern_cmul<<<n_blocks, n_threads_per_block>>>(arr1, arr2, nrows, ncols);
  return 0;
}

__global__ void sf_kern_realtocomplex(cufftComplex* arr_complex, float* arr, int nrows, int ncols) {
  int gidx = threadIdx.x + blockIdx.x*blockDim.x;
  int gidy = threadIdx.y + blockIdx.y*blockDim.y;
  if (gidy < nrows && gidx < ncols) {
    arr_complex[gidy*ncols + gidx].x = arr[gidy*ncols + gidx];
    arr_complex[gidy*ncols + gidx].y = 0.0f;
  }
}

cufftComplex* sf_realtocomplex(float* arr, int ncols, int nrows) {
  cufftComplex* arr_complex;
  cudaMalloc(&arr_complex, nrows*ncols*sizeof(cufftComplex));
  cudaMemset(arr_complex, 0, nrows*ncols*sizeof(cufftComplex));
  dim3 n_blocks = dim3(iDivUp(ncols, 8), iDivUp(nrows, 8), 1);
  dim3 n_threads_per_block = dim3(8, 8, 1);
  sf_kern_realtocomplex<<<n_blocks, n_threads_per_block>>>(arr_complex, arr, nrows, ncols);
  return arr_complex;
}





/// ****************************************************************************
/// ******************** Functions & wrappers **********************************
/// ****************************************************************************


/// Simple Backprojection (no filtering), with on-the-fly modification of num_bins
void sf_backproj_wrapper(ParamsForTomo p4t, float* d_sino, float* d_image, int n_x, int n_y, int num_bins) {

  /*
  // -------
  // Modify GPU_context for backprojection of 513*513 slice instead of 512*512
  int num_bins_old = p4t.ctxstruct->num_bins;
  int num_x_old = p4t.ctxstruct->num_x;
  int num_y_old = p4t.ctxstruct->num_y;
  cudaArray* a_Proje_voidptr_old = p4t.ctxstruct->a_Proje_voidptr;

   CUDA_SAFE_CALL(cudaMallocArray((cudaArray**) &(self->a_Proje_voidptr), &floatTex, self->num_bins, self->nprojs_span )); // <-- problem ! floatTex scope is gputomo.cu
   CUDA_SAFE_CALL(cudaMalloc((void **)&(self->dev_Work_perproje), sizeof(float)*self->num_bins*self->nprojs_span));

   self->NblocchiPerLinea32   = (int) (self->num_x *1.0/32  +0.9999999999 );
   self->NblocchiPerColonna32 = (int) (self->num_y *1.0/32  +0.9999999999 );

   self->NblocchiPerLinea   =  2* self->NblocchiPerLinea32;
   self->NblocchiPerColonna =  2* self->NblocchiPerColonna32;

   self->dimrecx = self->NblocchiPerLinea32*32;
   self->dimrecy = self->NblocchiPerColonna32*32;

   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));

  // ------
   */


  int num_bins_tmp = 0, n_x_tmp = 0, n_y_tmp = 0;
  num_bins_tmp = p4t.ctxstruct->num_bins;
  n_x_tmp = p4t.ctxstruct->num_x;
  n_y_tmp = p4t.ctxstruct->num_y;
  p4t.ctxstruct->num_bins = num_bins;
  p4t.ctxstruct->num_x = n_x;
  p4t.ctxstruct->num_x = n_y;
  p4t.ctxstruct->gpu_backproj(p4t.ctxstruct,
                              d_sino,
                              d_image,
                              0,
                              p4t.DETECTOR_DUTY_RATIO,
                              p4t.DETECTOR_DUTY_OVERSAMPLING,
                              1,
                              0);
  p4t.ctxstruct->num_bins = num_bins_tmp;
  p4t.ctxstruct->num_x = n_x_tmp;
  p4t.ctxstruct->num_x = n_y_tmp;
}

/*
/// Projection, with on-the-fly modification of num_bins
void sf_proj_wrapper(ParamsForTomo p4t, float* d_sino, float* d_image, int n_x, int num_bins) {
    int memisonhost=0;
    // The forward projector (C_HST_PROJECT_1OVER_GPU in c_hst_project_1over.cu) works only for square slices...
    // If the filter is computed outside PyHST, this should not be a problem.
    p4t.ctxstruct->gpu_project(p4t.ctxstruct->gpuctx,
                               num_bins, //TODO : check is this works. Otherwise, do like sf_backproj_wrapper;
                               p4t.ctxstruct->nprojs_span,
                               p4t.ctxstruct->angles_per_proj,
                               p4t.ctxstruct->axis_position  ,
                               d_sino    ,
                               d_image   ,
                               n_x,
                               p4t.ctxstruct->axis_corrections,
                               p4t.ctxstruct->gpu_offset_x ,
                               p4t.ctxstruct->gpu_offset_y ,
                               p4t.ctxstruct->JOSEPHNOCLIP,
                               p4t.DETECTOR_DUTY_RATIO,
                               p4t.DETECTOR_DUTY_OVERSAMPLING,
                               memisonhost, p4t.ctxstruct->FAN_FACTOR,
                               p4t.ctxstruct->SOURCE_X
                               );

}
*/



/*
float* sf_create_dirac(int npx) {
  if ((npx & 1) == 0) { // take care to the parenthesis...
    printf("ERROR : sf_create_dirac(): the dimension has to be odd (got %d)\n", npx);
    exit(-1);
  }
  float* h_tmp = (float*) calloc(npx*npx, sizeof(float));
  h_tmp[(npx/2) * npx + npx/2] = 1;
  float* d_dirac;
  cudaMalloc(&d_dirac, npx*npx*sizeof(float));
  cudaMemcpy(d_dirac, h_tmp, npx*npx*sizeof(float), cudaMemcpyHostToDevice);
  free(h_tmp);
  return d_dirac;
}

float* sf_compute_filter_operator(ParamsForTomo p4t, float* d_dirac, int npx, int nproj, float alpha, int n_it) {
  float* d_xs;
  cudaMalloc(&d_xs, npx*npx*sizeof(float));
  cudaMemset(d_xs, 0, npx*npx*sizeof(float));

  float* d_tmp;
  cudaMalloc(&d_tmp, npx*npx*sizeof(float));
  float* d_sino_tmp;
  cudaMalloc(&d_sino_tmp, npx*nproj*sizeof(float));

  float* d_x = d_dirac;
  for (int k=0; k < n_it; k++) {
    // xs += x
    cublasSaxpy(npx*npx, 1.0, d_x, 1, d_xs, 1);
    // x -= alpha*PT(P(x))
    sf_proj_wrapper(p4t, d_sino_tmp, d_x, npx, npx);
    sf_backproj_wrapper(p4t, d_sino_tmp, d_tmp, npx, npx);
    cublasSaxpy(npx*npx, -alpha, d_tmp, 1, d_x, 1);
    if (SF_DEBUG) printf("[%d] xs = %e \t x = %e\n", k, cublasSasum(npx*npx, d_xs, 1), cublasSasum(npx*npx, d_x, 1));
  }
  cudaFree(d_tmp);
  cudaFree(d_sino_tmp);

  float* tmp = (float*) calloc(npx*npx, sizeof(float));
  cudaMemcpy(tmp, d_xs, npx*npx*sizeof(float), cudaMemcpyDeviceToHost);
  FILE* fid = fopen("tmpfilter.dat", "wb");
  fwrite(tmp, sizeof(float), npx*npx, fid);
  fclose(fid);




  return d_xs;
}
*/


//float* sf_fftshift(float* xs_p, int npx, int npx_nextpow, int nproj) {
//  float* res;
//  cudaMalloc(&res, 2*npx_nextpow*nproj*sizeof(float));
//  cudaMemset(res, 0, 2*npx_nextpow*nproj*sizeof(float));
//  // Half right (+1) goes to the left : res[:, 0:npx/2 + 1] = xs_p[:, npx/2:npx]
//  cudaMemcpy2D(res, 2*npx_nextpow * sizeof(float), xs_p + npx/2, npx * sizeof(float), npx/2 *sizeof(float), nproj, cudaMemcpyDeviceToDevice);
//  // Half left goes to the right : f[:, f.shape[1] - int(npix / 2):f.shape[1]] = f[:, 1:int(npix / 2) + 1][:, ::-1]


//  return res;
//}


/*
float* sf_compute_filter(ParamsForTomo p4t, int num_bins, int num_projs, int n_it) {

  ///
  /// FIXME : To compute the filter, gpu_context has to be heavily modified
  ///  in order to use gpu_fbdl with different num_bins and num_x.
  ///  This modification can only be done in gputomo.cu since some pre-allocated arrays
  ///  are bound to a texture whose scope is gputomo.cu.
  ///

  puts("ERROR : the filter computation is not implemented in PyHST Yet.");
  puts("Please use a python script to compute this filter (in HDF5 format).");
  exit(-1);


  int npx = num_bins;
  float alpha = 1.0/(num_projs * num_bins); // TODO : check if Proj() is scaled... if so, adapt the wrapper !
  // Use an odd dimension so that the center is well-defined
  if ((npx & 1) == 0) npx += 1; // take care to the parenthesis...

  // ----
  float* xs_proj;
  cudaMalloc(&xs_proj, npx*num_projs*sizeof(float));
  cufftComplex* xs_p_shift_fourier_c;
  cufftHandle plan_forward = (cufftHandle) p4t.ctxstruct->precond_params_dl.planRamp_forward;
  // ----

  // Compute the operator sum( (I - alpha PT P)^k )
  float* d_dirac = sf_create_dirac(npx);
  float* xs = sf_compute_filter_operator(p4t, d_dirac, npx, num_projs, alpha, n_it);
  // Forward project and scale the operator
  sf_proj_wrapper(p4t, xs_proj, xs, npx, npx);
  cublasSscal(num_projs*npx, alpha, xs_proj, 1);
  //The convolution theorem states that the size of the FFT should be at least 2*N-1  where N is the original size.
  //  Here we make both slice (horizontal size N in real domain) and filter (possibly N+1 in real domain)
  //  have a size nextpow2(N)*2 in Fourier domain, which should provide good performances for FFT.
  int filter_size = 2*nextpow2_cp(num_bins);
  cudaMalloc(&xs_p_shift_fourier_c, filter_size*num_projs*sizeof(cufftComplex));

  // fftshift with zeros in the middle
 cufftComplex* xs_p_shift = sf_shift_reverse(xs_proj, npx, filter_size, num_projs);

  // Fourier transform it. The input is real and symmetric, so is the output.
 CUDA_SAFE_FFT(cufftExecC2C(plan_forward, (cufftComplex *) xs_p_shift, (cufftComplex *) xs_p_shift_fourier_c, CUFFT_FORWARD));
 float* xs_p_shift_fourier = sf_discard_imag(xs_p_shift_fourier_c, filter_size, num_projs);

 // Clean-up
 cudaFree(d_dirac);
 cudaFree(xs);
 cudaFree(xs_proj);
 cudaFree(xs_p_shift);
 cudaFree(xs_p_shift_fourier_c);

 return xs_p_shift_fourier;


}
*/

// CUDA_SAFE_CALL(cudaMalloc(&self->precond_params_dl.d_r_sino_error, fftbunch*nextpow2_cp(num_bins)*sizeof(cufftReal)));
//   CUDA_SAFE_CALL(cudaMalloc(&self->precond_params_dl.d_i_sino_error, fftbunch*nextpow2_cp(num_bins)*sizeof(cufftComplex)));

//   CUDA_SAFE_FFT(cufftPlan1d((cufftHandle *) &self->precond_params_dl.planRamp_forward, nextpow2_cp(num_bins),CUFFT_R2C,fftbunch));
//   CUDA_SAFE_FFT(cufftPlan1d((cufftHandle *) &self->precond_params_dl.planRamp_backward,nextpow2_cp(num_bins),CUFFT_C2R,fftbunch));
//   cufftComplex* d_i_discrete_ramp = w_compute_discretized_ramp_filter(nextpow2_cp(num_bins), self->precond_params_dl.d_r_sino_error, self->precond_params_dl.d_i_sino_error, self->precond_params_dl.planRamp_forward);
//   self->precond_params_dl.filter_coeffs = d_i_discrete_ramp; //size : nextpow2(num_bins)/2+1
//   if (W_VERBOSE) puts("Parameters successfully imported");


// CUDA_SAFE_FFT(cufftExecR2C(planRamp_forward,(cufftReal *) d_r_sino_error,(cufftComplex *) d_i_sino_error));
//     w_kern_fourier_filter<<<grd,blk>>>(d_i_sino_error, p4t.ctxstruct->precond_params_dl.filter_coeffs, dim_fft_ramp, fftbunch);
//     CUDA_SAFE_FFT(cufftExecC2R(planRamp_backward,(cufftComplex *) d_i_sino_error,(cufftReal *) d_r_sino_error));




char* sf_get_filter_name(int n_it, int num_projs, int n_x, int n_y, char* filter_folder) {
  char* fname = (char*) calloc(512, sizeof(char));
  fname[0] = '\0';
  strcat(fname, filter_folder);
  int L = strlen(filter_folder);
  if (fname[L-1] != '/') strcat(fname, "/");
  strcat(fname, "sirtfilter_");

  char n_x_s[6];
  char n_y_s[6];
  char num_projs_s[6];
  char n_it_s[6];

  snprintf(n_x_s, 6, "%d", n_x);
  snprintf(n_y_s, 6, "%d", n_y);
  snprintf(num_projs_s, 6, "%d", num_projs);
  snprintf(n_it_s, 6, "%d", n_it);

  strcat(fname, n_x_s);
  strcat(fname, "_");
  strcat(fname, n_y_s);
  strcat(fname, "_");
  strcat(fname, num_projs_s);
  strcat(fname, "_");
  strcat(fname, n_it_s);
  strcat(fname, ".h5");

  return fname;
}

float* sf_load_filter(char* filter_name, int num_projs, int num_bins, int n_x, int n_y, int n_it, float lambda) {
  hid_t fid = h5_fopen(filter_name, "r");
  if (fid < 0) return NULL;

  hid_t did = h5_get_dataset(fid, "sirtfilter");
  if (did < 0) {
    H5Fclose(fid);
    return NULL;
  }

  int read_num_projs = -1, read_n_x = -1, read_n_y = -1, read_n_it = -1, read_ndet = -1;
  float read_lambda = 0.0f;
  h5_get_attribute_scalar(did, "nproj", "int", &read_num_projs);
  h5_get_attribute_scalar(did, "nx", "int", &read_n_x);
  h5_get_attribute_scalar(did, "ny", "int", &read_n_y);
  h5_get_attribute_scalar(did, "iterations", "int", &read_n_it);
  h5_get_attribute_scalar(did, "ndet", "int", &read_ndet); // num_bins
  h5_get_attribute_scalar(did, "Lambda", "float32", &read_lambda);

  if (read_num_projs != num_projs || read_ndet != num_bins || read_n_it != n_it || (read_n_x - n_x) > 1 || (read_n_y - n_y) > 1) {
    printf("Warning (sirt_filter): Filter %s has (n_x, n_y) = (%d, %d), num_projs = %d, ndet = %d, iterations=%d, Lambda=%.2f\n", filter_name, read_n_x, read_n_y, read_ndet, read_n_it, read_lambda);
    printf("Requestred filter has (n_x, n_y) = (%d, %d), num_projs = %d, ndet = %d, iterations=%d, Lambda = %.2f\n", n_x, n_y, num_projs, num_bins, n_it, lambda);
    puts("This filter will not be used !");
    return NULL;
  }
  int filter_size = nextpow2_cp(read_ndet)*2;
  float* res = (float*) calloc(read_num_projs * filter_size, sizeof(float));
  h5_get_dataset_value(did, "float32", res);
  if (SF_VERBOSE) printf("Loaded filter %s\n", filter_name);
  float* d_res;
  cudaMalloc(&d_res, read_num_projs * filter_size*sizeof(float));
  cudaMemcpy(d_res, res, read_num_projs * filter_size*sizeof(float), cudaMemcpyHostToDevice);
  free(res);
  H5Dclose(did);
  H5Fclose(fid);
  return d_res;
}

/*
int sf_save_filter(char* filter_name, float* thefilter, int num_projs, int num_bins, int n_it) {

  hid_t fid = h5_create_file(filter_name);
  if (fid < 0) {
    printf("Warning (sirt_filter) : unable to write in %s\n", filter_name);
    puts("Filter will not be saved.");
    return -1;
  }

  int filter_size = 2*nextpow2_cp(num_bins);
  hsize_t size[] = {num_projs, filter_size};
  hid_t did = h5_create_dataset(fid, "sirtfilter", 2, size, "float");
  if (did < 0) {
    printf("Warning (sirt_filter) : something went wrong while saving the filter in %s : could not create dataset\n", filter_name);
    puts("Filter will not be saved.");
    H5Fclose(fid);
    return -2;
  }

  float* h_thefilter = (float*) calloc(size[0]*size[1], sizeof(float));
  cudaMemcpy(h_thefilter, thefilter, size[0]*size[1]*sizeof(float), cudaMemcpyDeviceToHost);
  h5_write_dataset(did, h_thefilter, "float");

  h5_write_attribute_scalar(did, "nproj", "int", &num_projs);
  h5_write_attribute_scalar(did, "npx", "int", &num_bins);
  h5_write_attribute_scalar(did, "iterations", "int", &n_it);

  free(h_thefilter);
  H5Dclose(did);
  H5Fclose(fid);
  return 0;
}
*/


int sf_compute_filter_python(int n_x, int n_y, int num_bins, int num_projs, float* projection_angles, float axis_pos, int n_it, char* filter_folder, float lambda) {

  float angle_offset = projection_angles[0];
  float angle_delta = projection_angles[1] - projection_angles[0];

  char command[512];
  snprintf(command, 511, "python /home/paleo/Projets/public/sirtfilter/compute_filter.py %d %d %d %d %f %f %f %d %f %s",
           n_x, n_y, num_bins, num_projs, angle_delta, angle_offset, axis_pos, n_it, lambda, filter_folder);

  if (SF_VERBOSE) printf("%s\n", command);
  return system(command);

}





/// ****************************************************************************
/// *************************  Main driver  ************************************
/// ****************************************************************************

extern "C" {
  int sf_driver(Gpu_Context* self, float* data, float* SLICE, float DETECTOR_DUTY_RATIO, int DETECTOR_DUTY_OVERSAMPLING);
}

int sf_driver(Gpu_Context* self, float* data, float* SLICE, float DETECTOR_DUTY_RATIO, int DETECTOR_DUTY_OVERSAMPLING) {

  if (SF_VERBOSE) {
    puts("------------------------------------------------------------------------------");
    puts("------------------ Entering SIRT-FBP driver ----------------------------------");
    puts("------------------------------------------------------------------------------");
  }
  cuCtxSetCurrent ( *((CUcontext *) self->gpuctx  ))  ;

  //Import parameters from self
  int n_x = self->num_x;
  int n_y = self->num_y;
  int num_bins = self->num_bins;
  int num_projs = self->nprojs_span;
  int n_it = self->SF_ITERATIONS;
  float axis_pos = self->axis_position;
  float lambda = self->SF_LAMBDA;
  char* filter_folder = self->SF_SAVEDIR;
  int filter_size = nextpow2_cp(num_bins)*2;
  float* projection_angles = self->angles_per_proj; //angle =  self->params.ANGLE_OFFSET  +  projection * ( self->params.ANGLE_BETWEEN_PROJECTIONS )  ;
  ParamsForTomo p4t  =  (ParamsForTomo)  { (Gpu_Context*) self, DETECTOR_DUTY_RATIO, DETECTOR_DUTY_OVERSAMPLING } ;
  float*d_slice; //*d_data;
//  cudaMalloc(&d_data, num_projs*num_bins * sizeof(float));
  cudaMalloc(&d_slice, n_x*n_y*sizeof(float));
//  cudaMemcpy(d_data, data, num_projs*num_bins*sizeof(float), cudaMemcpyHostToDevice);
  cudaMemcpy(d_slice, SLICE, n_x*n_y*sizeof(float), cudaMemcpyHostToDevice);


  // Prepare FFT plans
  cufftHandle plan_forward, plan_backward;
  int sz[1] = {filter_size};
  int ignored = 1;

  static int plans_are_computed = 0;
  if(!plans_are_computed) {
    plans_are_computed = 1;
    CUDA_SAFE_FFT(cufftPlanMany(&plan_forward, 1, sz, NULL, ignored, ignored, NULL, ignored, ignored, CUFFT_C2C, num_projs));
    CUDA_SAFE_FFT(cufftPlanMany(&plan_backward, 1, sz, NULL, ignored, ignored, NULL, ignored, ignored, CUFFT_C2C, num_projs));
  }
  if (SF_VERBOSE) {
    printf("Requestred %d iterations of SIRT, geometry = (%d, %d)\n", n_it, num_projs, num_bins);
  }

  char* filter_name = sf_get_filter_name(n_it, num_projs, n_x, n_y, filter_folder);
  float* thefilter = sf_load_filter(filter_name, num_projs, num_bins, n_x, n_y, n_it, lambda);
  if (thefilter == NULL) {
    printf("Filter %s not found. Computing the filter.\n", filter_name);
//    thefilter = sf_compute_filter(p4t, num_bins, num_projs, n_it);
    int res = sf_compute_filter_python(n_x, n_y, num_bins, num_projs, projection_angles, axis_pos, n_it, filter_folder, lambda);
    if (res != 0) {
      puts("ERROR: cound not compute the filter for some reason. Now exiting");
      exit(1);
    }
    if (SF_VERBOSE) puts("Filter computed");
//    sf_save_filter(filter_name, thefilter, num_projs, num_bins, n_it);
    thefilter = sf_load_filter(filter_name, num_projs, num_bins, n_x, n_y, n_it, lambda);
    if (thefilter == NULL) {
      printf("ERROR : could not load %s after computation. Now exiting\n", filter_name);
      exit(1);
    }
  }

  // Convolve sinogram with this filter
  float* data_padded;
  cudaMalloc(&data_padded, num_projs*filter_size*sizeof(float));
  cudaMemset(data_padded, 0, num_projs*filter_size*sizeof(float));
  CUDA_SAFE_CALL(cudaMemcpy2D(data_padded, filter_size*sizeof(float), data, num_bins*sizeof(float), num_bins*sizeof(float), num_projs, cudaMemcpyHostToDevice));
  cufftComplex* data_complex = sf_realtocomplex(data_padded, filter_size, num_projs);

  cufftComplex* data_fourier;
  cudaMalloc(&data_fourier, num_projs*filter_size*sizeof(cufftComplex));
  CUDA_SAFE_FFT(cufftExecC2C(plan_forward, (cufftComplex *) data_complex, (cufftComplex *) data_fourier, CUFFT_FORWARD));

//  printf("before fft : %f\n", cublasScasum(filter_size*num_projs, data_complex, 1));
//  printf("fft sum : %f\n", cublasScasum(filter_size*num_projs, data_fourier, 1));
// printf("filter sum : %f\n", cublasSasum(filter_size*num_projs, thefilter, 1));

  sf_call_crmul_kernel(data_fourier, thefilter, filter_size, num_projs);
  CUDA_SAFE_FFT(cufftExecC2C(plan_backward, (cufftComplex *) data_fourier, (cufftComplex *) data_complex, CUFFT_INVERSE));
  float* data_real = sf_discard_imag(data_complex, filter_size, num_projs);



//  FILE* fid = fopen("tmpfilter.dat", "wb");
//  float* tmp = (float*) calloc(filter_size*num_projs, sizeof(float));
//  cudaMemcpy(tmp, data_real, num_projs*filter_size*sizeof(float), cudaMemcpyDeviceToHost);
//  fwrite(tmp, sizeof(float), filter_size*num_projs, fid);
//  fclose(fid);

  //
  float* data_trunc;
  cudaMalloc(&data_trunc, num_bins*num_projs* sizeof(float));
  CUDA_SAFE_CALL(cudaMemcpy2D(data_trunc, num_bins*sizeof(float), data_real, filter_size*sizeof(float), num_bins*sizeof(float), num_projs, cudaMemcpyDeviceToDevice));
  //

  // Back-project
  sf_backproj_wrapper(p4t, data_trunc, d_slice, n_x, n_y, num_bins);


  cudaMemcpy(SLICE, d_slice, n_x*n_y*sizeof(float), cudaMemcpyDeviceToHost);
  cudaFree(data_padded);
  cudaFree(d_slice);
  cudaFree(data_real);
  cudaFree(data_trunc);
  cudaFree(data_complex);
  cudaFree(data_fourier);
  cudaFree(thefilter);

  return 0;
}













