You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
562 lines
20 KiB
562 lines
20 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2019.08.13
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Image3.h>
|
|
#include <functional>
|
|
|
|
// Image utilities for Image3<int> objects. TODO: Extend this to a template
|
|
// class to allow the pixel type to be int*_t and uint*_t for * in
|
|
// {8,16,32,64}.
|
|
//
|
|
// All but the Draw* functions are operations on binary images. Let the image
|
|
// have d0 columns, d1 rows and d2 slices. The input image must have zeros on
|
|
// its boundaries x = 0, x = d0-1, y = 0, y = d1-1, z = 0 and z = d2-1. The
|
|
// 0-valued voxels are considered to be background. The 1-valued voxels are
|
|
// considered to be foreground. In some of the operations, to save memory and
|
|
// time the input image is modified by the algorithms. If you need to
|
|
// preserve the input image, make a copy of it before calling these
|
|
// functions.
|
|
|
|
namespace gte
|
|
{
|
|
class ImageUtility3
|
|
{
|
|
public:
|
|
// Compute the 6-connected components of a binary image. The input
|
|
// image is modified to avoid the cost of making a copy. On output,
|
|
// the image values are the labels for the components. The array
|
|
// components[k], k >= 1, contains the indices for the k-th component.
|
|
static void GetComponents6(Image3<int>& image,
|
|
std::vector<std::vector<size_t>>& components)
|
|
{
|
|
std::array<int, 6> neighbors;
|
|
image.GetNeighborhood(neighbors);
|
|
GetComponents(6, &neighbors[0], image, components);
|
|
}
|
|
|
|
// Compute the 18-connected components of a binary image. The input
|
|
// image is modified to avoid the cost of making a copy. On output,
|
|
// the image values are the labels for the components. The array
|
|
// components[k], k >= 1, contains the indices for the k-th component.
|
|
static void GetComponents18(Image3<int>& image,
|
|
std::vector<std::vector<size_t>>& components)
|
|
{
|
|
std::array<int, 18> neighbors;
|
|
image.GetNeighborhood(neighbors);
|
|
GetComponents(18, &neighbors[0], image, components);
|
|
}
|
|
|
|
// Compute the 26-connected components of a binary image. The input
|
|
// image is modified to avoid the cost of making a copy. On output,
|
|
// the image values are the labels for the components. The array
|
|
// components[k], k >= 1, contains the indices for the k-th component.
|
|
static void GetComponents26(Image3<int>& image,
|
|
std::vector<std::vector<size_t>>& components)
|
|
{
|
|
std::array<int, 26> neighbors;
|
|
image.GetNeighborhood(neighbors);
|
|
GetComponents(26, &neighbors[0], image, components);
|
|
}
|
|
|
|
// Dilate the image using a structuring element that contains the
|
|
// 6-connected neighbors.
|
|
static void Dilate6(Image3<int> const& inImage, Image3<int>& outImage)
|
|
{
|
|
std::array<std::array<int, 3>, 6> neighbors;
|
|
inImage.GetNeighborhood(neighbors);
|
|
Dilate(6, &neighbors[0], inImage, outImage);
|
|
}
|
|
|
|
// Dilate the image using a structuring element that contains the
|
|
// 18-connected neighbors.
|
|
static void Dilate18(Image3<int> const& inImage, Image3<int>& outImage)
|
|
{
|
|
std::array<std::array<int, 3>, 18> neighbors;
|
|
inImage.GetNeighborhood(neighbors);
|
|
Dilate(18, &neighbors[0], inImage, outImage);
|
|
}
|
|
|
|
// Dilate the image using a structuring element that contains the
|
|
// 26-connected neighbors.
|
|
static void Dilate26(Image3<int> const& inImage, Image3<int>& outImage)
|
|
{
|
|
std::array<std::array<int, 3>, 26> neighbors;
|
|
inImage.GetNeighborhood(neighbors);
|
|
Dilate(26, &neighbors[0], inImage, outImage);
|
|
}
|
|
|
|
// Compute coordinate-directional convex set. For a given coordinate
|
|
// direction (x, y, or z), identify the first and last 1-valued voxels
|
|
// on a segment of voxels in that direction. All voxels from first to
|
|
// last are set to 1. This is done for all segments in each of the
|
|
// coordinate directions.
|
|
static void ComputeCDConvex(Image3<int>& image)
|
|
{
|
|
int const dim0 = image.GetDimension(0);
|
|
int const dim1 = image.GetDimension(1);
|
|
int const dim2 = image.GetDimension(2);
|
|
|
|
Image3<int> temp = image;
|
|
int i0, i1, i2;
|
|
for (i1 = 0; i1 < dim1; ++i1)
|
|
{
|
|
for (i0 = 0; i0 < dim0; ++i0)
|
|
{
|
|
int i2min;
|
|
for (i2min = 0; i2min < dim2; ++i2min)
|
|
{
|
|
if ((temp(i0, i1, i2min) & 1) == 0)
|
|
{
|
|
temp(i0, i1, i2min) |= 2;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (i2min < dim2)
|
|
{
|
|
int i2max;
|
|
for (i2max = dim2 - 1; i2max >= i2min; --i2max)
|
|
{
|
|
if ((temp(i0, i1, i2max) & 1) == 0)
|
|
{
|
|
temp(i0, i1, i2max) |= 2;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i2 = 0; i2 < dim2; ++i2)
|
|
{
|
|
for (i0 = 0; i0 < dim0; ++i0)
|
|
{
|
|
int i1min;
|
|
for (i1min = 0; i1min < dim1; ++i1min)
|
|
{
|
|
if ((temp(i0, i1min, i2) & 1) == 0)
|
|
{
|
|
temp(i0, i1min, i2) |= 2;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (i1min < dim1)
|
|
{
|
|
int i1max;
|
|
for (i1max = dim1 - 1; i1max >= i1min; --i1max)
|
|
{
|
|
if ((temp(i0, i1max, i2) & 1) == 0)
|
|
{
|
|
temp(i0, i1max, i2) |= 2;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i2 = 0; i2 < dim2; ++i2)
|
|
{
|
|
for (i1 = 0; i1 < dim1; ++i1)
|
|
{
|
|
int i0min;
|
|
for (i0min = 0; i0min < dim0; ++i0min)
|
|
{
|
|
if ((temp(i0min, i1, i2) & 1) == 0)
|
|
{
|
|
temp(i0min, i1, i2) |= 2;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (i0min < dim0)
|
|
{
|
|
int i0max;
|
|
for (i0max = dim0 - 1; i0max >= i0min; --i0max)
|
|
{
|
|
if ((temp(i0max, i1, i2) & 1) == 0)
|
|
{
|
|
temp(i0max, i1, i2) |= 2;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < image.GetNumPixels(); ++i)
|
|
{
|
|
image[i] = (temp[i] & 2 ? 0 : 1);
|
|
}
|
|
}
|
|
|
|
// Use a depth-first search for filling a 6-connected region. This is
|
|
// nonrecursive, simulated by using a heap-allocated "stack". The input
|
|
// (x,y,z) is the seed point that starts the fill.
|
|
template <typename PixelType>
|
|
static void FloodFill6(Image3<PixelType> & image, int x, int y, int z,
|
|
PixelType foreColor, PixelType backColor)
|
|
{
|
|
// Test for a valid seed.
|
|
int const dim0 = image.GetDimension(0);
|
|
int const dim1 = image.GetDimension(1);
|
|
int const dim2 = image.GetDimension(2);
|
|
if (x < 0 || x >= dim0 || y < 0 || y >= dim1 || z < 0 || z >= dim2)
|
|
{
|
|
// The seed point is outside the image domain, so there is
|
|
// nothing to fill.
|
|
return;
|
|
}
|
|
|
|
// Allocate the maximum amount of space needed for the stack. An
|
|
// empty stack has top == -1.
|
|
size_t const numVoxels = image.GetNumPixels();
|
|
std::vector<int> xStack(numVoxels), yStack(numVoxels), zStack(numVoxels);
|
|
|
|
// Push seed point onto stack if it has the background color. All
|
|
// points pushed onto stack have background color backColor.
|
|
int top = 0;
|
|
xStack[top] = x;
|
|
yStack[top] = y;
|
|
zStack[top] = z;
|
|
|
|
while (top >= 0) // stack is not empty
|
|
{
|
|
// Read top of stack. Do not pop since we need to return to
|
|
// this top value later to restart the fill in a different
|
|
// direction.
|
|
x = xStack[top];
|
|
y = yStack[top];
|
|
z = zStack[top];
|
|
|
|
// Fill the pixel.
|
|
image(x, y, z) = foreColor;
|
|
|
|
int xp1 = x + 1;
|
|
if (xp1 < dim0 && image(xp1, y, z) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = xp1;
|
|
yStack[top] = y;
|
|
zStack[top] = z;
|
|
continue;
|
|
}
|
|
|
|
int xm1 = x - 1;
|
|
if (0 <= xm1 && image(xm1, y, z) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = xm1;
|
|
yStack[top] = y;
|
|
zStack[top] = z;
|
|
continue;
|
|
}
|
|
|
|
int yp1 = y + 1;
|
|
if (yp1 < dim1 && image(x, yp1, z) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = x;
|
|
yStack[top] = yp1;
|
|
zStack[top] = z;
|
|
continue;
|
|
}
|
|
|
|
int ym1 = y - 1;
|
|
if (0 <= ym1 && image(x, ym1, z) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = x;
|
|
yStack[top] = ym1;
|
|
zStack[top] = z;
|
|
continue;
|
|
}
|
|
|
|
int zp1 = z + 1;
|
|
if (zp1 < dim2 && image(x, y, zp1) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = x;
|
|
yStack[top] = y;
|
|
zStack[top] = zp1;
|
|
continue;
|
|
}
|
|
|
|
int zm1 = z - 1;
|
|
if (0 <= zm1 && image(x, y, zm1) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = x;
|
|
yStack[top] = y;
|
|
zStack[top] = zm1;
|
|
continue;
|
|
}
|
|
|
|
// Done in all directions, pop and return to search other
|
|
// directions for the predecessor.
|
|
--top;
|
|
}
|
|
}
|
|
|
|
// Visit pixels using Bresenham's line drawing algorithm. The callback
|
|
// represents the action you want applied to each voxel as it is visited.
|
|
static void DrawLine(int x0, int y0, int z0, int x1, int y1, int z1,
|
|
std::function<void(int, int, int)> const& callback)
|
|
{
|
|
// Starting point of line.
|
|
int x = x0, y = y0, z = z0;
|
|
|
|
// Direction of line.
|
|
int dx = x1 - x0, dy = y1 - y0, dz = z1 - z0;
|
|
|
|
// Increment or decrement depending on direction of line.
|
|
int sx = (dx > 0 ? 1 : (dx < 0 ? -1 : 0));
|
|
int sy = (dy > 0 ? 1 : (dy < 0 ? -1 : 0));
|
|
int sz = (dz > 0 ? 1 : (dz < 0 ? -1 : 0));
|
|
|
|
// Decision parameters for voxel selection.
|
|
if (dx < 0)
|
|
{
|
|
dx = -dx;
|
|
}
|
|
if (dy < 0)
|
|
{
|
|
dy = -dy;
|
|
}
|
|
if (dz < 0)
|
|
{
|
|
dz = -dz;
|
|
}
|
|
int ax = 2 * dx, ay = 2 * dy, az = 2 * dz;
|
|
int decX, decY, decZ;
|
|
|
|
// Determine largest direction component, single-step related
|
|
// variable.
|
|
int maxValue = dx, var = 0;
|
|
if (dy > maxValue)
|
|
{
|
|
maxValue = dy;
|
|
var = 1;
|
|
}
|
|
if (dz > maxValue)
|
|
{
|
|
var = 2;
|
|
}
|
|
|
|
// Traverse Bresenham line.
|
|
switch (var)
|
|
{
|
|
case 0: // Single-step in x-direction.
|
|
decY = ay - dx;
|
|
decZ = az - dx;
|
|
for (/**/; /**/; x += sx, decY += ay, decZ += az)
|
|
{
|
|
// Process voxel.
|
|
callback(x, y, z);
|
|
|
|
// Take Bresenham step.
|
|
if (x == x1)
|
|
{
|
|
break;
|
|
}
|
|
if (decY >= 0)
|
|
{
|
|
decY -= ax;
|
|
y += sy;
|
|
}
|
|
if (decZ >= 0)
|
|
{
|
|
decZ -= ax;
|
|
z += sz;
|
|
}
|
|
}
|
|
break;
|
|
case 1: // Single-step in y-direction.
|
|
decX = ax - dy;
|
|
decZ = az - dy;
|
|
for (/**/; /**/; y += sy, decX += ax, decZ += az)
|
|
{
|
|
// Process voxel.
|
|
callback(x, y, z);
|
|
|
|
// Take Bresenham step.
|
|
if (y == y1)
|
|
{
|
|
break;
|
|
}
|
|
if (decX >= 0)
|
|
{
|
|
decX -= ay;
|
|
x += sx;
|
|
}
|
|
if (decZ >= 0)
|
|
{
|
|
decZ -= ay;
|
|
z += sz;
|
|
}
|
|
}
|
|
break;
|
|
case 2: // Single-step in z-direction.
|
|
decX = ax - dz;
|
|
decY = ay - dz;
|
|
for (/**/; /**/; z += sz, decX += ax, decY += ay)
|
|
{
|
|
// Process voxel.
|
|
callback(x, y, z);
|
|
|
|
// Take Bresenham step.
|
|
if (z == z1)
|
|
{
|
|
break;
|
|
}
|
|
if (decX >= 0)
|
|
{
|
|
decX -= az;
|
|
x += sx;
|
|
}
|
|
if (decY >= 0)
|
|
{
|
|
decY -= az;
|
|
y += sy;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Dilation using the specified structuring element.
|
|
static void Dilate(int numNeighbors, std::array<int, 3> const* delta,
|
|
Image3<int> const& inImage, Image3<int> & outImage)
|
|
{
|
|
int const bound0M1 = inImage.GetDimension(0) - 1;
|
|
int const bound1M1 = inImage.GetDimension(1) - 1;
|
|
int const bound2M1 = inImage.GetDimension(2) - 1;
|
|
for (int i2 = 1; i2 < bound2M1; ++i2)
|
|
{
|
|
for (int i1 = 1; i1 < bound1M1; ++i1)
|
|
{
|
|
for (int i0 = 1; i0 < bound0M1; ++i0)
|
|
{
|
|
if (inImage(i0, i1, i2) == 0)
|
|
{
|
|
for (int n = 0; n < numNeighbors; ++n)
|
|
{
|
|
int d0 = delta[n][0];
|
|
int d1 = delta[n][1];
|
|
int d2 = delta[n][2];
|
|
if (inImage(i0 + d0, i1 + d1, i2 + d2) == 1)
|
|
{
|
|
outImage(i0, i1, i2) = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outImage(i0, i1, i2) = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Connected component labeling using depth-first search.
|
|
static void GetComponents(int numNeighbors, int const* delta,
|
|
Image3<int> & image, std::vector<std::vector<size_t>> & components)
|
|
{
|
|
size_t const numVoxels = image.GetNumPixels();
|
|
std::vector<int> numElements(numVoxels);
|
|
std::vector<size_t> vstack(numVoxels);
|
|
size_t i, numComponents = 0;
|
|
int label = 2;
|
|
for (i = 0; i < numVoxels; ++i)
|
|
{
|
|
if (image[i] == 1)
|
|
{
|
|
int top = -1;
|
|
vstack[++top] = i;
|
|
|
|
int& count = numElements[numComponents + 1];
|
|
count = 0;
|
|
while (top >= 0)
|
|
{
|
|
size_t v = vstack[top];
|
|
image[v] = -1;
|
|
int j;
|
|
for (j = 0; j < numNeighbors; ++j)
|
|
{
|
|
size_t adj = v + delta[j];
|
|
if (image[adj] == 1)
|
|
{
|
|
vstack[++top] = adj;
|
|
break;
|
|
}
|
|
}
|
|
if (j == numNeighbors)
|
|
{
|
|
image[v] = label;
|
|
++count;
|
|
--top;
|
|
}
|
|
}
|
|
|
|
++numComponents;
|
|
++label;
|
|
}
|
|
}
|
|
|
|
if (numComponents > 0)
|
|
{
|
|
components.resize(numComponents + 1);
|
|
for (i = 1; i <= numComponents; ++i)
|
|
{
|
|
components[i].resize(numElements[i]);
|
|
numElements[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < numVoxels; ++i)
|
|
{
|
|
int value = image[i];
|
|
if (value != 0)
|
|
{
|
|
// Labels started at 2 to support the depth-first
|
|
// search, so they need to be decremented for the
|
|
// correct labels.
|
|
image[i] = --value;
|
|
components[value][numElements[value]] = i;
|
|
++numElements[value];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|