Skip to content

Commit 44ea9d7

Browse files
authored
Merge pull request #2424 from SCIInstitute/2423-groom-oblilque
Fix #2423 by resampling oblique images first.
2 parents dfc8147 + 6e32ea3 commit 44ea9d7

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

Libs/Groom/Groom.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ bool Groom::image_pipeline(std::shared_ptr<Subject> subject, size_t domain) {
216216

217217
//---------------------------------------------------------------------------
218218
bool Groom::run_image_pipeline(Image& image, GroomParameters params) {
219+
220+
// ensure axis aligned
221+
image.toAxisAligned();
222+
219223
// isolate
220224
if (params.get_isolate_tool()) {
221225
image.isolate();

Libs/Image/Image.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,90 @@ Image& Image::resize(Dims dims, Image::InterpolationType interp) {
431431
return resample(IdentityTransform::New(), origin(), dims, spacing, coordsys(), interp);
432432
}
433433

434+
435+
Image& Image::toAxisAligned(InterpolationType interp) {
436+
// Check if image is already axis-aligned
437+
auto direction = itk_image_->GetDirection();
438+
bool is_oblique = false;
439+
for (unsigned int i = 0; i < 3; i++) {
440+
for (unsigned int j = 0; j < 3; j++) {
441+
double expected = (i == j) ? 1.0 : 0.0;
442+
if (std::abs(direction(i, j) - expected) > 1e-6) {
443+
is_oblique = true;
444+
break;
445+
}
446+
}
447+
}
448+
449+
// If already axis-aligned, return immediately
450+
if (!is_oblique) {
451+
return *this;
452+
}
453+
454+
// The key insight: we need to create a transform that represents
455+
// the DIFFERENCE between the oblique and axis-aligned coordinate systems
456+
457+
using TransformType = itk::AffineTransform<double, 3>;
458+
auto transform = TransformType::New();
459+
460+
// The direction matrix rotates from index space to physical space
461+
// To go from new axis-aligned indices to old oblique physical space,
462+
// we need: newPhysical = identity * newIndex
463+
// oldIndex = direction^-1 * oldPhysical
464+
// So: oldIndex = direction^-1 * identity * newIndex = direction^-1 * newIndex
465+
466+
TransformType::MatrixType matrix;
467+
for (int i = 0; i < 3; i++) {
468+
for (int j = 0; j < 3; j++) {
469+
matrix(i, j) = direction(i, j);
470+
}
471+
}
472+
transform->SetMatrix(matrix);
473+
474+
// Get bounding box
475+
auto size = itk_image_->GetLargestPossibleRegion().GetSize();
476+
auto spacing = itk_image_->GetSpacing();
477+
auto origin = itk_image_->GetOrigin();
478+
479+
Point3 minPt, maxPt;
480+
for (int i = 0; i < 3; i++) {
481+
minPt[i] = std::numeric_limits<double>::max();
482+
maxPt[i] = std::numeric_limits<double>::lowest();
483+
}
484+
485+
for (int corner = 0; corner < 8; corner++) {
486+
Coord index;
487+
index[0] = (corner & 1) ? size[0] - 1 : 0;
488+
index[1] = (corner & 2) ? size[1] - 1 : 0;
489+
index[2] = (corner & 4) ? size[2] - 1 : 0;
490+
491+
Point3 point;
492+
itk_image_->TransformIndexToPhysicalPoint(index, point);
493+
494+
for (int dim = 0; dim < 3; dim++) {
495+
minPt[dim] = std::min(minPt[dim], point[dim]);
496+
maxPt[dim] = std::max(maxPt[dim], point[dim]);
497+
}
498+
}
499+
500+
ImageType::DirectionType identityDir;
501+
identityDir.SetIdentity();
502+
503+
ImageType::SizeType outputSize;
504+
for (int i = 0; i < 3; i++) {
505+
outputSize[i] = static_cast<size_t>(std::ceil((maxPt[i] - minPt[i]) / spacing[i])) + 1;
506+
}
507+
508+
Vector3 spacingVec;
509+
Dims dims;
510+
for (int i = 0; i < 3; i++) {
511+
spacingVec[i] = spacing[i];
512+
dims[i] = outputSize[i];
513+
}
514+
515+
return resample(transform, minPt, dims, spacingVec, identityDir, interp);
516+
}
517+
434518
bool Image::compare(const Image& other, bool verifyall, double tolerance, double precision) const {
435519
if (tolerance > 1 || tolerance < 0) {
436520
throw std::invalid_argument("tolerance value must be between 0 and 1 (inclusive)");

Libs/Image/Image.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ class Image {
105105
/// resamples image using isotropic physical spacing
106106
Image& resample(double isoSpacing = 1.0, InterpolationType interp = Linear);
107107

108+
/// resamples image to be axis-aligned (i.e., identity direction matrix) if needed
109+
Image& toAxisAligned(InterpolationType interp = NearestNeighbor);
110+
108111
/// changes logical image size, computing new physical spacing based on this size (i.e., physical image size remains
109112
/// the same)
110113
Image& resize(Dims logicalDims, InterpolationType interp = Linear);

0 commit comments

Comments
 (0)