Blink API

ImageReductionKernels

Example of a Reduction Kernel

In this example, we use an ImageReductionKernel kernel to find the mean value for each component in an image.

An ImageReductionKernel takes an image, or images, and reduces it down to a value, or values, which describe the image(s) in some way. For example, we use an ImageReductionKernel here to find the mean value for each component in an image, but we could also calculate other statistics such as the variance, or the minimum and maximum values, or perhaps to count the number of non-zero values in the image.

An ImageReductionKernel needs a data structure to reduce into, with some functions defined on it which specify how the reduction is to be performed. In the example below, we use the "addSample" function to update the reduction data from the kernel's "process" call, but you could define your own function for the updates inside the data structure, with any name and signature you like. However, your data structure must implement the "join" and "finish" functions.

After the reduction kernel has been run, and all its process calls completed, the "join" function will be called in order to combine results from different parts of the reduction, which might have been performed in parallel. "join" takes a similar reduction data object to combine with as a parameter. Optionally, it can also take a component index, which must then be passed in before the reduction data object.

The "finish" function is the final step, and is called on a single thread after all the results have been joined together. "finish" doesn't have to do anything but, for the moment, it must always be defined. "finish" takes two integer arguments. The first is the area of the iteration space the kernel was run over, so for an iteration space the same size as the input image, this would be (image width) x (image height). The second is the number of components the kernel was run over (for a pixel-wise kernel, this will always be 1). The "finish" function is only called once, so if you need to do something once for each component, you should loop over the components inside your finish function, as in the example below.

// First we define a data structure to reduce into.
struct MeanReductionData
{
  MeanReductionData() { mean = 0; }

  //Our data structure has a single variable, "mean", which has four floating-point values,
  // one for each image component.
  float4 mean;

  // The addSample function here defines how each sample will be added to the reduction data. In order to 
  // calculate the mean, we simply want to add the sample's value to our "mean" variable. We maintain a
  // mean for each of four image components, so the "addSample" function takes a component to add
  // to as well as the value to add.
  void addSample(int component, float value) {
    // Add the value to the mean for this component:
    mean[component] += value;
  }
    
  // Data reductions are performed in parallel initially. After the parallel part of the reduction, the 
  // "join" function is called to combine the data from all the parallel computations.
  void join(MeanReductionData other) {
    // For our mean calculation, we add the mean value we calculated to the mean value calculated by 
    // another part of the parallel computation, in order to obtain the combined result.
    mean += other.mean;
  }
    
  // The finish function is called after all the results for the parallel parts of the reduction have been combined.
  // This is called once on a single thread and is the place to do any extra calculations needed to produce the final
  // result.
  void finish(int nValues, int nComponents) {
    // Here, for each component, we divide the "mean" value we have obtained so far by the number of values that
    // were combined (added, in this case) to produce that value. This will give us our final result: the mean 
    // value of each component.
    for(int c = 0; c < 4; ++c)
      mean[c] /= nValues;
  }
};// struct MeanReductionData


// The MeanKernel inherits from ImageReductionKernel. The calculations to find the mean of each component are independent
// of one another, so the first template argument to the ImageReductionKernel tells us that this kernel will be run
// component-wise. The second template argument to ImageReductionKernel is the data structure to reduce into, in this 
// case the MeanReductionData structure we defined above. 
kernel MeanKernel : ImageReductionKernel<eComponentWise, MeanReductionData>
{
  // The MeanKernel has a single input image, src, and no output. It only needs to read from the src at the current
  // position in the iteration space, so can use point access. We're only interested in the mean of the values inside
  // the src image, so no edge method is supplied here.
  Image<eRead, eAccessPoint> src;
    
  // The "process" function will be called at every point in the iteration space. This is where the parallel part of the 
  // reduction will happen, as we accumulate samples here using the "addSample" function on the MeanReductionData.
  void process(int c) {
    
    // First we read the value from the src.
    float s = src();
    
    // Now we call "addSample" on the MeanReductionData to add this value into our calculation for the mean of the
    // current component, c.
    addSample(c, s);
  }
};// kernel MeanKernel
 All Classes Namespaces Files Functions Variables


©2013 The Foundry Visionmongers, Ltd. All Rights Reserved.
www.thefoundry.co.uk