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.
 
 
 
 
 
 

609 lines
23 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/FastMarch.h>
#include <Mathematics/Math.h>
// The topic of fast marching methods are discussed in the book
// Level Set Methods and Fast Marching Methods:
// Evolving Interfaces in Computational Geometry, Fluid Mechanics,
// Computer Vision, and Materials Science
// J.A. Sethian,
// Cambridge University Press, 1999
namespace gte
{
template <typename Real>
class FastMarch3 : public FastMarch<Real>
{
public:
// Construction and destruction.
FastMarch3(size_t xBound, size_t yBound, size_t zBound,
Real xSpacing, Real ySpacing, Real zSpacing,
std::vector<size_t> const& seeds, std::vector<Real> const& speeds)
:
FastMarch<Real>(xBound * yBound * zBound, seeds, speeds)
{
Initialize(xBound, yBound, zBound, xSpacing, ySpacing, zSpacing);
}
FastMarch3(size_t xBound, size_t yBound, size_t zBound,
Real xSpacing, Real ySpacing, Real zSpacing,
std::vector<size_t> const& seeds, Real speed)
:
FastMarch<Real>(xBound * yBound * zBound, seeds, speed)
{
Initialize(xBound, yBound, zBound, xSpacing, ySpacing, zSpacing);
}
virtual ~FastMarch3()
{
}
// Member access.
inline size_t GetXBound() const
{
return mXBound;
}
inline size_t GetYBound() const
{
return mYBound;
}
inline size_t GetZBound() const
{
return mZBound;
}
inline Real GetXSpacing() const
{
return mXSpacing;
}
inline Real GetYSpacing() const
{
return mYSpacing;
}
inline Real GetZSpacing() const
{
return mZSpacing;
}
inline size_t Index(size_t x, size_t y, size_t z) const
{
return x + mXBound * (y + mYBound * z);
}
// Voxel classification.
virtual void GetBoundary(std::vector<size_t>& boundary) const override
{
for (size_t i = 0; i < this->mQuantity; ++i)
{
if (this->IsValid(i) && !this->IsTrial(i))
{
if (this->IsTrial(i - 1)
|| this->IsTrial(i + 1)
|| this->IsTrial(i - mXBound)
|| this->IsTrial(i + mXBound)
|| this->IsTrial(i - mXYBound)
|| this->IsTrial(i + mXYBound))
{
boundary.push_back(i);
}
}
}
}
virtual bool IsBoundary(size_t i) const override
{
if (this->IsValid(i) && !this->IsTrial(i))
{
if (this->IsTrial(i - 1)
|| this->IsTrial(i + 1)
|| this->IsTrial(i - mXBound)
|| this->IsTrial(i + mXBound)
|| this->IsTrial(i - mXYBound)
|| this->IsTrial(i + mXYBound))
{
return true;
}
}
return false;
}
// Run one step of the fast marching algorithm.
virtual void Iterate() override
{
// Remove the minimum trial value from the heap.
size_t i;
Real value;
this->mHeap.Remove(i, value);
// Promote the trial value to a known value. The value was
// negative but is now nonnegative (the heap stores only
// nonnegative numbers).
this->mTrials[i] = nullptr;
// All trial pixels must be updated. All far neighbors must
// become trial pixels.
size_t iM1 = i - 1;
if (this->IsTrial(iM1))
{
ComputeTime(iM1);
this->mHeap.Update(this->mTrials[iM1], this->mTimes[iM1]);
}
else if (this->IsFar(iM1))
{
ComputeTime(iM1);
this->mTrials[iM1] = this->mHeap.Insert(iM1, this->mTimes[iM1]);
}
size_t iP1 = i + 1;
if (this->IsTrial(iP1))
{
ComputeTime(iP1);
this->mHeap.Update(this->mTrials[iP1], this->mTimes[iP1]);
}
else if (this->IsFar(iP1))
{
ComputeTime(iP1);
this->mTrials[iP1] = this->mHeap.Insert(iP1, this->mTimes[iP1]);
}
size_t iMXB = i - mXBound;
if (this->IsTrial(iMXB))
{
ComputeTime(iMXB);
this->mHeap.Update(this->mTrials[iMXB], this->mTimes[iMXB]);
}
else if (this->IsFar(iMXB))
{
ComputeTime(iMXB);
this->mTrials[iMXB] = this->mHeap.Insert(iMXB, this->mTimes[iMXB]);
}
size_t iPXB = i + mXBound;
if (this->IsTrial(iPXB))
{
ComputeTime(iPXB);
this->mHeap.Update(this->mTrials[iPXB], this->mTimes[iPXB]);
}
else if (this->IsFar(iPXB))
{
ComputeTime(iPXB);
this->mTrials[iPXB] = this->mHeap.Insert(iPXB, this->mTimes[iPXB]);
}
size_t iMXYB = i - mXYBound;
if (this->IsTrial(iMXYB))
{
ComputeTime(iMXYB);
this->mHeap.Update(this->mTrials[iMXYB], this->mTimes[iMXYB]);
}
else if (this->IsFar(iMXYB))
{
ComputeTime(iMXYB);
this->mTrials[iMXYB] = this->mHeap.Insert(iMXYB, this->mTimes[iMXYB]);
}
size_t iPXYB = i + mXYBound;
if (this->IsTrial(iPXYB))
{
ComputeTime(iPXYB);
this->mHeap.Update(this->mTrials[iPXYB], this->mTimes[iPXYB]);
}
else if (this->IsFar(iPXYB))
{
ComputeTime(iPXYB);
this->mTrials[iPXYB] = this->mHeap.Insert(iPXYB, this->mTimes[iPXYB]);
}
}
protected:
// Called by the constructors.
void Initialize(size_t xBound, size_t yBound, size_t zBound,
Real xSpacing, Real ySpacing, Real zSpacing)
{
mXBound = xBound;
mYBound = yBound;
mZBound = zBound;
mXYBound = xBound * yBound;
mXBoundM1 = mXBound - 1;
mYBoundM1 = mYBound - 1;
mZBoundM1 = mZBound - 1;
mXSpacing = xSpacing;
mYSpacing = ySpacing;
mZSpacing = zSpacing;
mInvXSpacing = (Real)1 / xSpacing;
mInvYSpacing = (Real)1 / ySpacing;
mInvZSpacing = (Real)1 / zSpacing;
// Boundary pixels are marked as zero speed to allow us to avoid
// having to process the boundary pixels separately during the
// iteration.
size_t x, y, z, i;
// vertex (0,0,0)
i = Index(0, 0, 0);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
// vertex (xmax,0,0)
i = Index(mXBoundM1, 0, 0);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
// vertex (0,ymax,0)
i = Index(0, mYBoundM1, 0);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
// vertex (xmax,ymax,0)
i = Index(mXBoundM1, mYBoundM1, 0);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
// vertex (0,0,zmax)
i = Index(0, 0, mZBoundM1);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
// vertex (xmax,0,zmax)
i = Index(mXBoundM1, 0, mZBoundM1);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
// vertex (0,ymax,zmax)
i = Index(0, mYBoundM1, mZBoundM1);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
// vertex (xmax,ymax,zmax)
i = Index(mXBoundM1, mYBoundM1, mZBoundM1);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
// edges (x,0,0) and (x,ymax,0)
for (x = 0; x < mXBound; ++x)
{
i = Index(x, 0, 0);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
i = Index(x, mYBoundM1, 0);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
}
// edges (0,y,0) and (xmax,y,0)
for (y = 0; y < mYBound; ++y)
{
i = Index(0, y, 0);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
i = Index(mXBoundM1, y, 0);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
}
// edges (x,0,zmax) and (x,ymax,zmax)
for (x = 0; x < mXBound; ++x)
{
i = Index(x, 0, mZBoundM1);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
i = Index(x, mYBoundM1, mZBoundM1);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
}
// edges (0,y,zmax) and (xmax,y,zmax)
for (y = 0; y < mYBound; ++y)
{
i = Index(0, y, mZBoundM1);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
i = Index(mXBoundM1, y, mZBoundM1);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
}
// edges (0,0,z) and (xmax,0,z)
for (z = 0; z < mZBound; ++z)
{
i = Index(0, 0, z);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
i = Index(mXBoundM1, 0, z);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
}
// edges (0,ymax,z) and (xmax,ymax,z)
for (z = 0; z < mZBound; ++z)
{
i = Index(0, mYBoundM1, z);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
i = Index(mXBoundM1, mYBoundM1, z);
this->mInvSpeeds[i] = std::numeric_limits<Real>::max();
this->mTimes[i] = -std::numeric_limits<Real>::max();
}
// Compute the first batch of trial pixels. These are pixels a grid
// distance of one away from the seed pixels.
for (z = 1; z < mZBoundM1; ++z)
{
for (y = 1; y < mYBoundM1; ++y)
{
for (x = 1; x < mXBoundM1; ++x)
{
i = Index(x, y, z);
if (this->IsFar(i))
{
if ((this->IsValid(i - 1) && !this->IsTrial(i - 1))
|| (this->IsValid(i + 1) && !this->IsTrial(i + 1))
|| (this->IsValid(i - mXBound) && !this->IsTrial(i - mXBound))
|| (this->IsValid(i + mXBound) && !this->IsTrial(i + mXBound))
|| (this->IsValid(i - mXYBound) && !this->IsTrial(i - mXYBound))
|| (this->IsValid(i + mXYBound) && !this->IsTrial(i + mXYBound)))
{
ComputeTime(i);
this->mTrials[i] = this->mHeap.Insert(i, this->mTimes[i]);
}
}
}
}
}
}
// Called by Iterate().
void ComputeTime(size_t i)
{
bool hasXTerm;
Real xConst;
if (this->IsValid(i - 1))
{
hasXTerm = true;
xConst = this->mTimes[i - 1];
if (this->IsValid(i + 1))
{
if (this->mTimes[i + 1] < xConst)
{
xConst = this->mTimes[i + 1];
}
}
}
else if (this->IsValid(i + 1))
{
hasXTerm = true;
xConst = this->mTimes[i + 1];
}
else
{
hasXTerm = false;
xConst = (Real)0;
}
bool hasYTerm;
Real yConst;
if (this->IsValid(i - mXBound))
{
hasYTerm = true;
yConst = this->mTimes[i - mXBound];
if (this->IsValid(i + mXBound))
{
if (this->mTimes[i + mXBound] < yConst)
{
yConst = this->mTimes[i + mXBound];
}
}
}
else if (this->IsValid(i + mXBound))
{
hasYTerm = true;
yConst = this->mTimes[i + mXBound];
}
else
{
hasYTerm = false;
yConst = (Real)0;
}
bool hasZTerm;
Real zConst;
if (this->IsValid(i - mXYBound))
{
hasZTerm = true;
zConst = this->mTimes[i - mXYBound];
if (this->IsValid(i + mXYBound))
{
if (this->mTimes[i + mXYBound] < zConst)
{
zConst = this->mTimes[i + mXYBound];
}
}
}
else if (this->IsValid(i + mXYBound))
{
hasZTerm = true;
zConst = this->mTimes[i + mXYBound];
}
else
{
hasZTerm = false;
zConst = (Real)0;
}
Real sum, diff, discr;
if (hasXTerm)
{
if (hasYTerm)
{
if (hasZTerm)
{
// xyz
sum = xConst + yConst + zConst;
discr = (Real)3 * this->mInvSpeeds[i] * this->mInvSpeeds[i];
diff = xConst - yConst;
discr -= diff * diff;
diff = xConst - zConst;
discr -= diff * diff;
diff = yConst - zConst;
discr -= diff * diff;
if (discr >= (Real)0)
{
// The quadratic equation has a real-valued
// solution. Choose the largest positive root for
// the crossing time.
this->mTimes[i] = (sum + std::sqrt(discr)) / (Real)3;
}
else
{
// The quadratic equation does not have a
// real-valued solution. This can happen when the
// speed is so large that the time gradient has
// very small length, which means that the time
// has not changed significantly from the
// neighbors to the current pixel. Just choose
// the maximum time of the neighbors. (Is there a
// better choice?)
this->mTimes[i] = xConst;
if (yConst > this->mTimes[i])
{
this->mTimes[i] = yConst;
}
if (zConst > this->mTimes[i])
{
this->mTimes[i] = zConst;
}
}
}
else
{
// xy
sum = xConst + yConst;
diff = xConst - yConst;
discr = (Real)2 * this->mInvSpeeds[i] * this->mInvSpeeds[i] - diff * diff;
if (discr >= (Real)0)
{
// The quadratic equation has a real-valued
// solution. Choose the largest positive root for
// the crossing time.
this->mTimes[i] = (Real)0.5 * (sum + std::sqrt(discr));
}
else
{
// The quadratic equation does not have a
// real-valued solution. This can happen when the
// speed is so large that the time gradient has
// very small length, which means that the time
// has not changed significantly from the
// neighbors to the current pixel. Just choose
// the maximum time of the neighbors. (Is there a
// better choice?)
this->mTimes[i] = (diff >= (Real)0 ? xConst : yConst);
}
}
}
else
{
if (hasZTerm)
{
// xz
sum = xConst + zConst;
diff = xConst - zConst;
discr = (Real)2 * this->mInvSpeeds[i] * this->mInvSpeeds[i] - diff * diff;
if (discr >= (Real)0)
{
// The quadratic equation has a real-valued
// solution. Choose the largest positive root for
// the crossing time.
this->mTimes[i] = (Real)0.5 * (sum + std::sqrt(discr));
}
else
{
// The quadratic equation does not have a
// real-valued solution. This can happen when the
// speed is so large that the time gradient has
// very small length, which means that the time
// has not changed significantly from the
// neighbors to the current pixel. Just choose
// the maximum time of the neighbors. (Is there a
// better choice?)
this->mTimes[i] = (diff >= (Real)0 ? xConst : zConst);
}
}
else
{
// x
this->mTimes[i] = this->mInvSpeeds[i] + xConst;
}
}
}
else
{
if (hasYTerm)
{
if (hasZTerm)
{
// yz
sum = yConst + zConst;
diff = yConst - zConst;
discr = (Real)2 * this->mInvSpeeds[i] * this->mInvSpeeds[i] - diff * diff;
if (discr >= (Real)0)
{
// The quadratic equation has a real-valued
// solution. Choose the largest positive root for
// the crossing time.
this->mTimes[i] = (Real)0.5 * (sum + std::sqrt(discr));
}
else
{
// The quadratic equation does not have a
// real-valued solution. This can happen when the
// speed is so large that the time gradient has
// very small length, which means that the time
// has not changed significantly from the
// neighbors to the current pixel. Just choose
// the maximum time of the neighbors. (Is there a
// better choice?)
this->mTimes[i] = (diff >= (Real)0 ? yConst : zConst);
}
}
else
{
// y
this->mTimes[i] = this->mInvSpeeds[i] + yConst;
}
}
else
{
if (hasZTerm)
{
// z
this->mTimes[i] = this->mInvSpeeds[i] + zConst;
}
else
{
// Assert: The pixel must have at least one valid
// neighbor.
}
}
}
}
size_t mXBound, mYBound, mZBound, mXYBound;
size_t mXBoundM1, mYBoundM1, mZBoundM1;
Real mXSpacing, mYSpacing, mZSpacing;
Real mInvXSpacing, mInvYSpacing, mInvZSpacing;
};
}