kde.clΒΆ

///
/// @file kde.cl
///
/// Kernels to do pdf-kernel density estimation
///
/// @author CPS
/// @bug No known bugs
///

///
/// @defgroup kde Kernel density estimation
/// Kernel density estimation
///

// All of the kernel-density filters below trap for bounds exceedance |@p x|>@p w.
// Shouldn't be necessary, but you never know.
// None are normalized, because each discretely sampled kd filter is integrated
//   and normalized.

#ifdef KDF_IS_TOPHAT
///
/// @brief  One of the following kernel-density filter functions: top hat,
/// triangle, Epanechnikov, cosine, Gaussian.
///
/// @param[in] x   (double, RO):
/// @param[in] w   (double, RO):
///
/// @returns double
///
/// @details If @p KDF_IS_TOPHAT is defined:
/// Top-hat (also known as 'boxcar') filter function.
/// Return the value of the filter at the sample point @p x given a bandwidth of @p w.
///
/// @ingroup kde
///
static inline double k_sample(const double x, const double w) {
    return select((double)0.0f, (double)1.0f,
                  (unsigned long)isless(fabs(x),w)); //0.5f/w; hack
}
#endif

#ifdef KDF_IS_TRIANGLE
///
/// @details If @p KDF_IS_TRIANGLE is defined: Triangle kernel filter function.
/// Return the value of the filter at the sample point @p x given a bandwidth of @p w.
///
/// @ingroup kde
///
static inline double k_sample(const double x, const double w) {
    return fmax( 1.0f-fabs(x/w) , (double)0.0f );
}
#endif

#ifdef KDF_IS_EPANECHNIKOV
///
/// @details If @p KDF_IS_EPANECHNIKOV is defined: Epanechnikov kernel filter function.
/// Return the value of the filter at the sample point @p x given a bandwidth of @p w.
/// This best-choice kd-filter is a simple quadratic.
///
/// @ingroup kde
///
static inline double k_sample(const double x, const double w) {
    return fmax( (1.0f-(x/w)*(x/w)), (double)0.0f); //0.75f*
}
#endif

#ifdef KDF_IS_COSINE
///
/// @details If @p KDF_IS_COSINE is defined: Cosine kernel filter function.
/// Return the value of the filter at the sample point @p x given a bandwidth of @p w.
/// Spans the @p w -scaled equivalent of [-pi/2,+pi/2].
///
/// @ingroup kde
///
static inline double k_sample(const double x, const double w) {
    return fmax( cos((M_PI_2*x)), (double)0.0f); //M_PI_4*
}
#endif

#ifdef KDF_IS_GAUSSIAN
///
/// @details If @p KDF_IS_GAUSSIAN is defined: Clipped Gaussian kernel filter function.
/// Return the value of the filter at the sample point @p x given a bandwidth of @p w.
/// Currently a bit of a hack - not normalized because pdf is normalized later anyway.
///
/// @ingroup kde
///
// This is a hack: ought to be rescaled in w and renormalized given the clipping
static inline double k_sample(const double x, const double w) {
    return exp(-0.5f*(x/(w*0.4f))*(x/(w*0.4f))); //M_SQRT1_2*
}
#endif


#ifdef KERNEL_HISTOGRAM_BIVARIATE
///
/// Compute the 2d raw (unnormalized) histogram of an @p sl_array data-pair vector.
///
/// The two columns of the data vector @p sl_array span [@p LOGX_MIN,@p LOGX_MAX]
///   and  [@p LOGY_MIN,@p LOGY_MAX] with ranges @p LOGX_RANGE and  @p LOGY_RANGE respectively.
///   Each kernel instance maps one element pair of this data
///   vector onto its matching @p histogram_array bin element through an @p atomic_inc
///   operation on that element.
///
/// Compiled if @p KERNEL_HISTOGRAM_BIVARIATE is defined.
///
/// @ingroup kde
///
__kernel void histogram_bivariate(
        __global const    float2 *sl_array,
        __global          uint   *histogram_array
   )
{
    // For every ROI pixel...

    const uint global_id = get_global_id(0u)+get_global_id(1u)*get_global_size(0u);
    const float bin_x = (sl_array[global_id][0]-LOGX_MIN)/LOGX_RANGE;
    const float bin_y = (sl_array[global_id][1]-LOGY_MIN)/LOGY_RANGE;
    const uint idx_x = min(max(0u,(uint)(bin_x*(float)(N_HIST_BINS-1u))),N_HIST_BINS-1u);
    const uint idx_y = min(max(0u,(uint)(bin_y*(float)(N_HIST_BINS-1u))),N_HIST_BINS-1u);
    atomic_inc(&histogram_array[idx_y+N_HIST_BINS*idx_x]);
    return;
}
#endif


#ifdef KERNEL_PDF_BIVARIATE_ROWS
///
/// Kernel-density filter and reduce a 2d @p histogram_array
///
///
///
/// Compiled if @p KERNEL_PDF_BIVARIATE_ROWS is defined.
///
/// @param[in]     histogram_array        (uint *,   RO):
/// @param[in]     hi_idx                 (uint,     RO):
/// @param[in]     bin_dx                 (double,   RO):
/// @param[in]     k_idx                  (uint,     RO):
/// @param[in]     k_width                (double,   RO):
/// @param[in,out] pdf_bin_accumulator    (double *, RW):
/// @param[in,out] k_weight_accumulator   (double *, RW):
///
/// @returns void
///
/// @ingroup kde
///
static inline void filter_along_rows(
        __global const uint *histogram_array,
        const uint hi_idx, const double bin_dx, const uint k_idx, const double k_width,
        double *pdf_bin_accumulator, double *k_weight_accumulator)
{
    // Figure out where we are on the coarse 2d pdf array (at the kdf center)
    const int hi_col = hi_idx % N_HIST_BINS;
    const int hi_row = hi_idx / N_HIST_BINS;
    // Figure out where the left & right kdf bins are on the coarse 2d pdf array
    // First right kdf-sampled pdf bin index...
    const int hi_col_rght = hi_col+(int)k_idx;
    // ...then left kdf-sampled pdf bin index
    // NB: add one here because histogram left-indexing starts at the right edge
    //     of the bin, aka the edge of the bin to the right ie with bin index plus one
    const int hi_col_left = hi_col-(int)k_idx+1;

    // Get the kdf weight
    const double k_value = k_sample((double)k_idx, k_width);

    const uint is_ok_right = isless(hi_col_rght,(int)N_HIST_BINS);
    const uint is_ok_left  = isnotequal(k_idx,0u) & isgreaterequal(hi_col_left,0);
    int hist_rght = hi_row*N_HIST_BINS+hi_col_rght;
    int hist_left = hi_row*N_HIST_BINS+hi_col_left;
    hist_rght = select(0u, (uint)hist_rght, is_ok_right);
    hist_left = select(0u, (uint)hist_left, is_ok_left);
    // Get histogram right & left counts for this hist bin
    //   and add to the pdf bin accumulator
    *pdf_bin_accumulator
        += select((double)0.0f, k_value*(double)histogram_array[hist_rght],
                  (unsigned long)is_ok_right);
    *pdf_bin_accumulator
        += select((double)0.0f, k_value*(double)histogram_array[hist_left],
                  (unsigned long)is_ok_left);
    // Add the kdf weight to the kdf-integral accumulator
    *k_weight_accumulator
        += select((double)0.0f, (double)k_value, (unsigned long)is_ok_right);
    *k_weight_accumulator
        += select((double)0.0f, (double)k_value, (unsigned long)is_ok_left );
    return;
}
#endif

#ifdef KERNEL_PDF_BIVARIATE_ROWS
///
/// Use kernel-density estimation to map a 2d histogram into a smooth 2d pdf: rows only.
///
/// Compiled if @p KERNEL_PDF_BIVARIATE is defined.
///
/// @param[in]     histogram_array   (uint *,  RO):
/// @param[in,out] partial_pdf_array (float *, RW):
///
/// @returns void
///
/// @ingroup kde
///
__kernel void pdf_bivariate_rows(
        __global const  uint  *histogram_array,
        __global        float *partial_pdf_array )
{
    // For every histogram bin...

    const uint global_id = get_global_id(0u)+get_global_id(1u)*get_global_size(0u);

    // Create a buffer variable for the pdf bin accumulator
    __private double pdf_bin_accumulator=0.0f;
    // Create a buffer variable for the kdf-integral accumulator
    __private double k_weight_accumulator=1.0f;
    __private uint k_idx;

    // Step through half kdf taking advantage of symmetry
    //   e.g., 0...2  for 5-point kdf
    // Smooth along rows (C-order, thus row-major, last (y) index changes faster)
    for (k_idx=0;k_idx<=N_KDF_PART_POINTS_Y;k_idx++) {
        filter_along_rows(histogram_array, global_id, BIN_DY,
                          k_idx, (double)(N_KDF_PART_POINTS_Y)+0.5f,
                          &pdf_bin_accumulator, &k_weight_accumulator);
    }
    // Normalize the pdf point accumulation by dividing by the kdf weight accumulation
    //   and write to the return pdf_array element for this CL-kernel instance
    partial_pdf_array[global_id] = (float)(pdf_bin_accumulator/k_weight_accumulator);
    return;
}
#endif


#ifdef KERNEL_PDF_BIVARIATE_COLS
///
/// Kernel-density filter and reduce a 2d @p histogram_array
///
///
///
/// Compiled if @p KERNEL_PDF_BIVARIATE_COLS is defined.
///
/// @param[in]     partial_pdf_array      (float *,  RO):
/// @param[in]     pdf_idx                (uint,     RO):
/// @param[in]     pdf_kdx                (double,   RO):
/// @param[in]     bin_dx                 (double,   RO):
/// @param[in]     k_idx                  (uint,     RO):
/// @param[in]     k_width                (double,   RO):
/// @param[in,out] pdf_bin_accumulator    (double *, RW):
/// @param[in,out] k_weight_accumulator   (double *, RW):
///
/// @returns void
///
/// @ingroup kde
///
static inline void filter_along_cols(
        __global const float *partial_pdf_array,
        const uint pdf_idx, const double bin_dx,
        const double pdf_kdx, const uint k_idx, const double k_width,
        double *pdf_bin_accumulator, double *k_weight_accumulator)
{
    __private double k_value, k_x;
    __private int hi_col, hi_row, hi_row_up, hi_row_dn;
    __private uint is_ok_down, is_ok_up;

    // Set the number of histogram bins per pdf bin
    //   e.g., 1000 hist bins, 100 pdf point bins, 10 hist bins per pdf point bin
    const uint n_bins_per_point = N_HIST_BINS/N_PDF_POINTS;

    // Figure out where we are on the coarse 2d pdf array (at the kdf center)
    const int pdf_col = pdf_idx % N_PDF_POINTS;
    const int pdf_row = pdf_idx / N_PDF_POINTS;

    // Figure out where the left & right kdf bins are on the coarse 2d pdf array
    // First right kdf-sampled pdf bin index...
    const int pdf_row_dn = pdf_row+(int)k_idx;
    // ...then left kdf-sampled pdf bin index
    const int pdf_row_up = pdf_row-(int)k_idx;

    // Figure out where we are on the fine 2d histogram array
    // First right kdf-sampled pdf bin index...
    const int hi_row_dn_offset \
        = (uint)(pdf_col+pdf_row_dn*N_HIST_BINS)*n_bins_per_point;
    // ...then left kdf-sampled pdf bin index
    const int hi_row_up_offset \
        = (uint)(pdf_col+pdf_row_up*N_HIST_BINS)*n_bins_per_point;

    // Step columnwise through all the histogram bins in this pdf bin
    // Apply this weight repeatedly across all the rows in this hist bin
    for (hi_row=0u;hi_row<(int)n_bins_per_point;hi_row++) {
        // Sample point on kernel-density filter
        k_x = (double)hi_row+((double)k_idx)*n_bins_per_point;
        // Get the filter weight at this point
        k_value = k_sample(k_x, k_width);
        // Apply this weight repeatedly across all the rows in this hist bin
        for (hi_col=0u;hi_col<(int)n_bins_per_point;hi_col++) {
            hi_row_dn = hi_row_dn_offset+hi_row*N_HIST_BINS+hi_col;
            hi_row_up = hi_row_up_offset+(n_bins_per_point-1-hi_row)*N_HIST_BINS+hi_col;
            is_ok_down = isless(hi_row_dn,(int)(N_HIST_BINS*N_HIST_BINS));
            is_ok_up   = isnotequal(k_idx,0) & isgreaterequal(hi_row_up,0);
            hi_row_dn = select(0u, (uint)hi_row_dn, is_ok_down);
            hi_row_up = select(0u, (uint)hi_row_up, is_ok_up);
            // Get histogram right & left counts for this hist bin
            //   and add to the pdf bin accumulator
            *pdf_bin_accumulator
                += select((double)0.0f, k_value*(double)partial_pdf_array[hi_row_dn],
                          (unsigned long)is_ok_down);
            *pdf_bin_accumulator
                += select((double)0.0f, k_value*(double)partial_pdf_array[hi_row_up],
                          (unsigned long)is_ok_up);
            // Add the kdf weight to the kdf-integral accumulator
            *k_weight_accumulator
                += select((double)0.0f, (double)k_value, (unsigned long)is_ok_down);
            *k_weight_accumulator
                += select((double)0.0f, (double)k_value, (unsigned long)is_ok_up);
        }
    }
    return;
}

#endif

#ifdef KERNEL_PDF_BIVARIATE_COLS
///
/// Use kernel-density estimation to map a 2d histogram into a smooth bivariate pdf.
///
/// Compiled if @p KERNEL_PDF_BIVARIATE is defined.
///
/// @param[in]     partial_pdf_array (float *, RO):
/// @param[in,out] pdf_array         (float *, RW):
///
/// @returns void
///
/// @ingroup kde
///
__kernel void pdf_bivariate_cols(
        __global const  float *partial_pdf_array,
        __global        float *pdf_array )
{
    // For every pdf point...

    const uint global_id = get_global_id(0u)+get_global_id(1u)*get_global_size(0u);

    // Get this pdf point's index
    const uint pdf_idx = global_id;
    // Create a buffer variable for the pdf bin accumulator
    __private double pdf_bin_accumulator=0.0f;
    // Create a buffer variable for the kdf-integral accumulator
    __private double k_weight_accumulator=1.0f;
    __private uint k_idx;

    // Step through half kdf taking advantage of symmetry
    //   e.g., 0...2  for 5-point kdf
    // Smooth along columns (C-order, thus row-major, first (x) index changes slower)
    for (k_idx=0;k_idx<=N_KDF_PART_POINTS_X;k_idx++) {
        filter_along_cols(partial_pdf_array, pdf_idx, PDF_DX, BIN_DX,
                          k_idx, (double)(N_KDF_PART_POINTS_X)+0.5f,
                          &pdf_bin_accumulator, &k_weight_accumulator);
    }
    // Normalize the pdf point accumulation by dividing by the kdf weight accumulation
    //   and write to the return pdf_array element for this CL-kernel instance
    pdf_array[pdf_idx] = (float)(pdf_bin_accumulator/k_weight_accumulator);
    return;
}
#endif


#ifdef KERNEL_HISTOGRAM_UNIVARIATE
///
/// Compute the 1d raw (unnormalized) histogram of an @p sl_array data vector.
///
/// The single-column data vector @p sl_array spans [@p LOGX_MIN,@p LOGX_MAX] with
///   range @p LOGX_RANGE.  Each kernel instance maps one element of this data
///   vector onto its matching @p histogram_array bin element through an @p atomic_inc
///   operation on that element.
///
/// Compiled if @p KERNEL_HISTOGRAM_UNIVARIATE is defined.
///
/// @param[in]     sl_array          (float *, RO):
/// @param[in,out] histogram_array   (uint *,  RW):
///
/// @returns void
///
/// @ingroup kde
///
__kernel void histogram_univariate(
        __global const    float  *sl_array,
        __global          uint   *histogram_array )
{
    // For every ROI pixel...

    const uint global_id = get_global_id(0u)+get_global_id(1u)*get_global_size(0u);
    const float bin_x = (sl_array[global_id]-LOGX_MIN)/LOGX_RANGE;
    const uint idx = min(max(0u,(uint)(bin_x*(float)N_HIST_BINS)),N_HIST_BINS-1u);
    atomic_inc(&histogram_array[idx]);
    return;
}
#endif

#ifdef KERNEL_PDF_UNIVARIATE
///
/// Filter and reduce a kd filter-span of a 1d @p histogram_array.
///
/// Stepping through the set of @p histogram_array bin elements encompassed
///   by the kernel filter span @p KDF_WIDTH_X at the kernel pdf element @p pdf_idx,
///   the kernel filter weight is obtained from  @p k_sample(), recorded cumulatively
///   in @p *k_weight_accumulator, applied to the @p histogram_array element value,
///   and recorded cumulatively in @p *pdf_bin_accumulator.
/// The accumulated filter weight in @p *k_weight_accumulator is used to normalize
///   the @p *pdf_bin_accumulator value: this is needed where the filter is clipped
///   at the data limits.
/// The kernel density filter function @p k_sample() is chosen and defined
///   through a compiler -D macro flag.
///
///
/// Compiled if @p KERNEL_PDF_UNIVARIATE is defined.
///
/// @param[in]     histogram_array        (uint *,   RO):
/// @param[in]     pdf_idx                (uint,     RO):
/// @param[in]     k_idx                  (uint,     RO):
/// @param[in]     k_points               (uint,     RO):
/// @param[in,out] pdf_bin_accumulator    (double *, RW):
/// @param[in,out] k_weight_accumulator   (double *, RW):
///
/// @returns void
///
/// @ingroup kde
///
static inline void filter(
        __global const uint *histogram_array, const uint pdf_idx,
        const uint k_idx, const uint k_points,
        double *pdf_bin_accumulator, double *k_weight_accumulator)
{
    __private double k_bin_point, k_bin_edge, k_value, x;
    __private int pdf_col_left, pdf_col_rght;
    __private uint h_col, h_col_left, h_col_rght;
    __private uint is_ok_right, is_ok_left;

    // Set the number of histogram bins per pdf bin
    //   e.g., 2000 hist bins, 200 pdf point bins, 10 hist bins per pdf point bin
    const uint n_bins_per_point = N_HIST_BINS/N_PDF_POINTS;

    // Center of kdf bin
    k_bin_point = ((double)k_idx)*PDF_DX;
    // Left edge of kdf bin
    k_bin_edge = k_bin_point-PDF_DX/2.0f;
    // First right kdf-sampled pdf bin index...
    pdf_col_rght = pdf_idx+k_idx;
    // ...then left kdf-sampled pdf bin index
    // NB: add one here because histogram left-indexing starts at the right edge
    //     of the bin, aka the edge of the bin to the right ie with bin index plus one
    pdf_col_left = pdf_idx-k_idx+1;
//    printf("pdf_col_left=%d, pdf_col_rght=%d, k_idx=%d, k_bin_point=%g\n",
//            pdf_col_left,pdf_col_rght,k_idx,k_bin_point);
    // Step through span of histogram bins for this pdf bin
    for (h_col=0u;h_col<n_bins_per_point;h_col++) {
        // Center of histogram bin
        x = k_bin_edge+BIN_DX*((double)h_col+0.5f);
        // Calculate the kdf (kernel density filter) weight for this pdf point
        k_value = k_sample(x,KDF_WIDTH_X);
        // Get histogram count for this hist bin on both sides of the symm filter
        // First check if the filter spills off the right or left array limits
        is_ok_right = isless(pdf_col_rght,(int)N_PDF_POINTS);
        // When checking left also suppress accumulation if at filter center
        //   (don't count twice at the x=0 pdf bin)
        is_ok_left  = isnotequal(k_idx,0) & isgreater(pdf_col_left,0);
        h_col_rght = (uint)(pdf_col_rght*n_bins_per_point+h_col);
        h_col_left = (uint)(pdf_col_left*n_bins_per_point-h_col-1u);
        // Map R&L col indexes into dummy value zero index if off grid
        h_col_rght = select(0u, h_col_rght, is_ok_right);
        h_col_left = select(0u, h_col_left, is_ok_left);
        // Add to the pdf bin accumulator
        //   - but don't bother if the array index is off grid
        *pdf_bin_accumulator
            += select((double)0.0f, (double)histogram_array[h_col_rght]*k_value,
                      (unsigned long)is_ok_right);
        *pdf_bin_accumulator
            += select((double)0.0f, (double)histogram_array[h_col_left]*k_value,
                      (unsigned long)is_ok_left);
        // Add the kdf weight to the kdf-integral accumulator
        *k_weight_accumulator
            += select((double)0.0f, (double)k_value, (unsigned long)is_ok_right);
        *k_weight_accumulator
            += select((double)0.0f, (double)k_value, (unsigned long)is_ok_left);
    }
    return;
}
#endif

#ifdef KERNEL_PDF_UNIVARIATE
///
/// Use kernel-density estimation to map a 1d histogram into a smooth univariate pdf.
///
/// Compiled if @p KERNEL_PDF_UNIVARIATE is defined.
///
/// @param[in]     histogram_array (uint *,  RO):
/// @param[in,out] pdf_array       (float *, RW):
///
/// @returns void
///
/// @ingroup kde
///
__kernel void pdf_univariate(
        __global const  uint  *histogram_array,
        __global        float *pdf_array )
{
    // For every pdf point...

    const uint global_id = get_global_id(0u)+get_global_id(1u)*get_global_size(0u);

    // Get this pdf point's index
    const uint pdf_idx = global_id;
    // Create a buffer variable for the pdf bin accumulator
    __private double pdf_bin_accumulator=0.0f;
    // Create a buffer variable for the kdf-integral accumulator
    __private double k_weight_accumulator=0.0f;
    __private uint k_idx;

    // Step through half kdf taking advantage of symmetry
    //   e.g., 0...2  for 5-point kdf
    for (k_idx=0;k_idx<=N_KDF_PART_POINTS_X;k_idx++) {
        filter(histogram_array, pdf_idx, k_idx, N_KDF_PART_POINTS_X*2u+1u,
               &pdf_bin_accumulator, &k_weight_accumulator);
    }
    // Normalize the pdf point accumulation by dividing by the kdf weight accumulation
    //   and write to the return pdf_array element for this CL-kernel instance
    pdf_array[pdf_idx] = (float)pdf_bin_accumulator/k_weight_accumulator;
    return;
}
#endif