1D Ranged Access

eAccessRanged1D specifies that for every output position, a kernel needs access to a one-dimensional range of positions from a given image. Specifying the range over which an image will be accessed allows for optimisations that come from knowing the exact range of access that a kernel will need, by allowing for faster access over spaces where the pixel position and the specified range offset remain within the bounds of the image.

For example, by specifying that an output pixel will only need access to the input image from the adjacent neighbouring pixel positions, for most output pixels we do not have to worry about accessing outside of the image and can access those pixels fast, while only boundary pixels would require safe access with checks.

../../_images/safe1d.png

Safe and fast access regions for a horizontal 1D ranged access.

The BoxBlur1DKernel

This one-dimensional box blur demonstrates 1D ranged access.

// Copyright (c) 2024 The Foundry Visionmongers Ltd.  All Rights Reserved.
kernel BoxBlur1D : public ImageComputationKernel<eComponentWise>
{
  Image<eRead, eAccessRanged1D, eEdgeClamped> src;
  Image<eWrite, eAccessPoint> dst;

param:
  int radius;   //The radius of the box blur

local:
  int filterWidth;

  void define() {
    defineParam(radius, "Radius", 5, eParamProxyScale);
  }

  void init() {
    // Set the range we need to access from the source 
    src.setRange(-radius, radius);

    // Set the axis for the 1D-range to be horizontal
    src.setAxis(eX);

    filterWidth = 2 * radius + 1;
  }

  void process() {
    // Sum all the pixel values within radius
    float sum = 0.0f;
    for(int i = -radius; i <= radius; ++i) {
      sum += src(i);
    }

    // Write out the average value
    dst() = sum / filterWidth;
  }
};

Configuring 1D ranged image access

Image Specification

First, an image specification must use eAccessRanged1D to allow for access to a one-dimensional range of pixels. The desired behaviour for accessing outside of image edges is also required, either eEdgeClamped or eEdgeConstant.

The example BoxBlur1DKernel requires one-dimensional access to its source image, specified as follows:

Image<eRead, eAccessRanged1D, eEdgeClamped> src;

Setting the Range

In order to use eRanged1D, you must first define the size of the desired range of access, and whether the desired range is vertical or horizontal. These must be set up inside the init() function using the following methods.

setAxis(Axis axis)

axis parameter is either eX (horizontal) or eY (vertical). Defines the ranged access axis for 1D ranged images.

setRange(int rangeMin, int rangeMax)

This function sets the minimum and maximum extent of the range. For example, setRange(-2, 2) would allow accessing the 5 pixels in the inclusive range.

../../_images/1d-access.png

An example of 1D Ranged Access using setRange(-2, 2) and setAxis(eX).

BoxBlur1DKernel example

In the BoxBlur1DKernel example, the range depends on the value of the radius parameter, which is the blur size. The range passed to setRange(...) is taken to be inclusive on both sides.

A call to setAxis(...) sets the desired axis to access. In this case the blur is horizontal, so the axis is set to eX. This will allow the kernel to access positions from (x - radius, y) to (x + radius, y) inclusive.

void init() {
  // Set the range we need to access from the source 
  src.setRange(-radius, radius);

  // Set the axis for the 1D-range to be horizontal
  src.setAxis(eX);

Accessing the 1D Range

Pixel positions are accessed using relative offsets from the current pixel position. For example, this kernel will allow access the pixels adjacent to the current pixel position being processed:

kernel RangedAccess1DReadExample : ImageComputationKernel<eComponentWise>
{
  Image<eRead, eAccessRanged1D, eEdgeClamped> src;
  Image<eWrite> dst;

  void init() {
    src.setRange(-1, 1);
    src.setAxis(eX);
  }

  void process() {
    float sum = 0;
    // Read the left adjacent input pixel
    sum += src(-1);
    // Read the current position input pixel
    sum += src(0);
    // Read the right adjacent input pixel
    sum += src(1);
    // output the average
    dst() = sum / 3.0f;
  }
};

It is also possible to write to a 1D range of the output image. For example:

kernel RangedAccess1DWriteExample : ImageComputationKernel<eComponentWise>
{
  Image<eRead> src;
  Image<eWrite, eAccessRanged1D, eEdgeClamped> dst;

  void init() {
    dst.setRange(-1, 1);
    dst.setAxis(eX);
  }

  void process(int2 pos) {
    // Only perform the write if it will be within the image bounds
    if (pos.x > 0) {
      // Write to the pixel to the left of the current position
      dst(-1) = src();
    }
  }
};

BoxBlur1DKernel example

The process function of the BoxBlur1DKernel loops over our horizontal range from left to right, accumulating contributions to the box blur, before dividing by the total number of contributions using the value of filterWidth that was calculated in the init() function.

void process() {
  // Sum all the pixel values within radius
  float sum = 0.0f;
  for(int i = -radius; i <= radius; ++i) {
    sum += src(i);
  }

  // Write out the average value
  dst() = sum / filterWidth;
}