123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- // David Eberly, Geometric Tools, Redmond WA 98052
- // Copyright (c) 1998-2020
- // 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 WwiseGTE
- {
- 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];
- }
- }
- }
- }
- };
- }
|