Libs/Optimize/Domain/ImageDomain.h
Namespaces
| Name |
|---|
| shapeworks User usage reporting (telemetry) |
Classes
| Name | |
|---|---|
| class | shapeworks::ImageDomain |
Source code
```cpp
pragma once
include
include
include
include
include
include
include
include
include "ParticleRegionDomain.h"
// we have to undef foreach here because both Qt and OpenVDB define foreach
undef foreach
ifndef Q_MOC_RUN
include
include
include
include
include
include
endif
namespace shapeworks {
template
using ImageType = itk::Image
using PointType = ParticleRegionDomain::PointType;
void SetImage(ImageType* I, double narrow_band) { this->m_FixedDomain = false; // this->Modified();
openvdb::initialize(); // It is safe to initialize multiple times.
// Set a large background value, so that we quickly catch particles outside or on the edge the narrow band.
// (Downside: its more difficult to display the correct location of the point of failure.)
m_VDBImage = openvdb::FloatGrid::create(1e8);
m_VDBImage->setGridClass(openvdb::GRID_LEVEL_SET);
auto vdbAccessor = m_VDBImage->getAccessor();
// Save properties of the Image needed for the optimizer
m_Size = I->GetRequestedRegion().GetSize();
m_Spacing = I->GetSpacing();
m_Origin = I->GetOrigin();
m_Index = I->GetRequestedRegion().GetIndex();
// Transformation from index space to world space
openvdb::math::Mat4f mat;
mat.setIdentity();
mat.postScale(openvdb::Vec3f(m_Spacing[0], m_Spacing[1], m_Spacing[2]));
mat.postTranslate(openvdb::Vec3f(m_Origin[0], m_Origin[1], m_Origin[2]));
const auto xform = openvdb::math::Transform::createLinearTransform(mat);
m_VDBImage->setTransform(xform);
itk::ImageRegionIterator<ImageType> it(I, I->GetRequestedRegion());
it.GoToBegin();
while (!it.IsAtEnd()) {
const auto idx = it.GetIndex();
const auto pixel = it.Get();
if (abs(pixel) > narrow_band) {
++it;
continue;
}
const auto coord = openvdb::Coord(idx[0], idx[1], idx[2]);
vdbAccessor.setValue(coord, pixel);
++it;
}
typename ImageType::PointType l0;
I->TransformIndexToPhysicalPoint(m_Index, l0);
for (unsigned int i = 0; i < DIMENSION; i++) m_Index[i] += m_Size[i] - 1;
typename ImageType::PointType u0;
I->TransformIndexToPhysicalPoint(m_Index, u0);
// Cast points to higher precision if needed. Parent class uses doubles
// because they are compared directly with points in the particle system,
// which are always double precision.
PointType l;
PointType u;
for (unsigned int i = 0; i < DIMENSION; i++) {
l[i] = static_cast<double>(l0[i]);
u[i] = static_cast<double>(u0[i]);
}
this->SetLowerBound(l);
this->SetUpperBound(u);
// Precompute and save values that are used in parts of the optimizer
this->SetupImageForCrossingPointUpdate(I); // this->UpdateZeroCrossingPoint(I);
this->UpdateSurfaceArea(I);
}
double GetSurfaceArea() const override { return m_SurfaceArea; }
inline PointType GetOrigin() const { return m_Origin; }
inline typename ImageType::SizeType GetSize() const { return m_Size; }
inline typename ImageType::SpacingType GetSpacing() const { return m_Spacing; }
inline typename ImageType::RegionType::IndexType GetIndex() const { return m_Index; }
inline PointType GetValidLocationNear(PointType p) const override { // todo why is this function ignoring the argument? Also see Optimize::AddSinglePoint return m_ZeroCrossingPoint; }
inline T Sample(const PointType& p) const { if (this->IsInsideBuffer(p)) { const auto coord = this->ToVDBCoord(p); return openvdb::tools::BoxSampler::sample(m_VDBImage->tree(), coord); } else { std::ostringstream message; message << "Domain " << m_DomainID << ": " << m_DomainName << " : Distance transform queried for a Point, " << p << ", outside the given image domain. Consider increasing the padding in grooming or the narrow band " "optimization parameter"; throw std::runtime_error(message.str()); } }
inline double GetMaxDiameter() const override { double bestRadius = 0; double maxdim = 0; for (unsigned int i = 0; i < ImageType::ImageDimension; i++) { if (GetSize()[i] > maxdim) { maxdim = GetSize()[i]; bestRadius = maxdim * GetSpacing()[i]; } } return bestRadius; }
void DeleteImages() override { m_VDBImage = 0; }
// Updates zero crossing points. Raster scans candidate zero crossing points, and finds one that does not violate any // constraints. void UpdateZeroCrossingPoint() override { for (size_t i = 0; i < m_possible_zero_crossings.size(); i++) { this->m_ZeroCrossingPoint = m_possible_zero_crossings[i]; if (!this->GetConstraints()->isAnyViolated(this->m_ZeroCrossingPoint)) { // std::cout << "Chosen initial point " << this->m_ZeroCrossingPoint << std::endl; break; } }
if (this->GetConstraints()->isAnyViolated(this->m_ZeroCrossingPoint)) {
std::cerr << "A particle initialization violates at least one constraint. Make sure at least one point satisfies "
"all constraints"
<< std::endl;
}
}
protected: openvdb::FloatGrid::Ptr GetVDBImage() const { return m_VDBImage; }
ImageDomain() {} virtual ~ImageDomain() {};
void PrintSelf(std::ostream& os, itk::Indent indent) const { ParticleRegionDomain::PrintSelf(os, indent); os << indent << "VDB Active Voxels = " << m_VDBImage->activeVoxelCount() << std::endl; }
inline openvdb::math::Transform::Ptr transform() const { return this->m_VDBImage->transformPtr(); }
// Converts a coordinate from an ITK Image point in world space to the corresponding // coordinate in OpenVDB Index space. Raises an exception if the narrow band is not // sufficiently large to sample the point. inline openvdb::Vec3R ToVDBCoord(const PointType& p) const { const auto worldCoord = openvdb::Vec3R(p[0], p[1], p[2]); const auto idxCoord = this->transform()->worldToIndex(worldCoord);
// Make sure the coordinate is part of the narrow band
if (m_VDBImage->tree().isValueOff(
openvdb::Coord::round(idxCoord))) { // `isValueOff` requires an integer coordinate
// If multiple threads crash here at the same time, the error message displayed is just "terminate called
// recursively", which isn't helpful. So we std::cerr the error to make sure its printed to the console.
std::cerr << "Sampled point outside the narrow band: " << p << std::endl;
std::ostringstream message;
message << "Attempt to sample at a point outside the narrow band: " << p
<< ". Consider increasing the narrow band";
throw std::runtime_error(message.str());
}
return idxCoord;
}
private:
openvdb::FloatGrid::Ptr m_VDBImage;
typename ImageType::SizeType m_Size;
typename ImageType::SpacingType m_Spacing;
PointType m_Origin;
PointType m_ZeroCrossingPoint;
typename ImageType::RegionType::IndexType m_Index; // Index defining the corner of the region
double m_SurfaceArea;
std::vector
// Computes possible zero crossing points. Later on, one can find the ones that do not violate constraints.
void SetupImageForCrossingPointUpdate(ImageType* I) {
typename itk::ZeroCrossingImageFilter
for (zcIt.GoToReverseBegin(); !zcIt.IsAtReverseEnd(); --zcIt) {
if (zcIt.Get() == 1.0) {
PointType pos;
I->TransformIndexToPhysicalPoint(zcIt.GetIndex(), pos);
this->m_ZeroCrossingPoint = pos;
m_possible_zero_crossings.push_back(pos);
}
}
}
void UpdateSurfaceArea(ImageType* I) { Image image(I); m_SurfaceArea = image.toMesh(0).getSurfaceArea(); } };
} // end namespace shapeworks ```
Updated on 2026-03-31 at 16:02:11 +0000