2D Ranged Access¶
eAccessRanged2D
tells us that for every output position, a kernel needs to access a two-dimensional range of positions from the given Image. Similar to 1D Ranged Access, using 2D Ranged Access allows for optimisations that come from knowing the exact range of access that a kernel will need, but applies to both the X and Y axes.
The BoxBlur2DKernel¶
To demonstrate 2D ranged access we can extend the 1D box blur to work in two dimensions.
// Copyright (c) 2024 The Foundry Visionmongers Ltd. All Rights Reserved.
kernel BoxBlur2D : public ImageComputationKernel<eComponentWise>
{
Image<eRead, eAccessRanged2D, eEdgeClamped> src;
Image<eWrite, eAccessPoint> dst;
param:
int xRadius; //The horizontal radius of the box blur
int yRadius; //The vertical radius of the box blur
local:
int filterSize;
void define() {
defineParam(xRadius, "RadiusX", 5, eParamProxyScale);
defineParam(yRadius, "RadiusY", 3, eParamProxyScale);
}
void init() {
// Set the range we need to access from the source
src.setRange(-xRadius, -yRadius, xRadius, yRadius);
filterSize = (2 * xRadius + 1) * (2 * yRadius + 1);
}
void process() {
// Sum all the pixel values within radius
float sum = 0.0f;
for(int j = -yRadius; j <= yRadius; j++) {
for(int i = -xRadius; i <= xRadius; i++) {
sum += src(i, j);
}
}
// Write out the average value
dst() = sum / filterSize;
}
};
Configuring 2D Ranged Image Access¶
Image Specification¶
First, an image specification must use eAccessRanged2D
to allow for two-dimensional access to pixels. The desired behaviour for accessing outside of image edges is also required, either eEdgeClamped
or eEdgeConstant
.
The example BoxBlur2DKernel needs to access a two-dimensional range of pixels from its source image, with clamping edge behaviour, specified as follows:
Image<eRead, eAccessRanged2D, eEdgeClamped> src;
Setting the Range¶
Similar to 1D access, you must specify the size of the desired range. This time, we set the range for both axes at the same time. This must be set up inside the init()
function.
setRange(int xMin, int yMin, int xMax, int yMax)
This function sets the minimum and maximum extent of the range.
setRange(int rangeMin, int rangeMax)
This function sets the minimum and maximum extent of the range for both axes: equivalent to calling
setRange(min, min, max, max)
.
For example, setRange(-2, 2)
would allow accessing the 25-pixel square in the inclusive range [-2, 2] in both axes:
BoxBlur2DKernel example¶
In the box blur kernel, to set up the 2D range, we call setRange()
inside the init()
function. This time, we give it four parameters to specify the horizontal minimum, vertical minimum, horizontal maximum and vertical maximum respectively.
void init() {
// Set the range we need to access from the source
src.setRange(-xRadius, -yRadius, xRadius, yRadius);
Accessing the 2D Range¶
Pixel positions are accessed using relative offsets from the current pixel position. Access to our 2D range must be done from inside the process()
function. This is very similar to accessing the 1D range, except that we now need to specify both the X and Y offsets from the current position. For example, this kernel will access the pixels adjacent to the current pixel position being processed:
kernel RangedAccess2DReadExample : ImageComputationKernel<eComponentWise>
{
Image<eRead, eAccessRanged2D, eEdgeClamped> src;
Image<eWrite> dst;
void init() {
src.setRange(-1, 1);
}
void process() {
float sum = 0;
// Read the row of three adjacent pixels directly above the current position
sum += src(-1, -1);
sum += src( 0, -1);
sum += src( 1, -1);
// Read the row of three adjacent pixels at the current position
sum += src(-1, 0);
sum += src( 0, 0);
sum += src( 1, 0);
// Read the row of three adjacent directly below the current position
sum += src(-1, 1);
sum += src( 0, 1);
sum += src( 1, 1);
// output the average
dst() = sum / 9.0f;
}
};
It is also possible to write to a 2D range of the output image. For example:
kernel RangedAccess2DWriteExample : ImageComputationKernel<eComponentWise>
{
Image<eRead> src;
Image<eWrite, eAccessRanged2D, eEdgeClamped> dst;
void init() {
dst.setRange(-1, 1);
}
void process(int2 pos) {
//Only perform the write if it will be within the image bounds
if (pos.x > 0 && pos.y > 0) {
// Write to the pixel to the left and above the current position
dst(-1, -1) = src();
}
}
};
BoxBlur2DKernel example¶
The process()
function of the BoxBlur2DKernel performs a nested loop, first looping over the vertical range, and for each vertical position, looping over the horizontal range, accumulating contributions to our box blur from each source position in turn. In the final step, the output pixel is set to the average value of all the contributions, using the value of filterSize
we calculated inside the init()
function.
void process() {
// Sum all the pixel values within radius
float sum = 0.0f;
for(int j = -yRadius; j <= yRadius; j++) {
for(int i = -xRadius; i <= xRadius; i++) {
sum += src(i, j);
}
}
// Write out the average value
dst() = sum / filterSize;
}