1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
|
// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkAlignImageDataSetFilter.h"
#include "vtkBoundingBox.h"
#include "vtkCompositeDataSet.h"
#include "vtkDummyController.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkLogger.h"
#include "vtkMultiProcessController.h"
#include "vtkObjectFactory.h"
#include "vtkSmartPointer.h"
#include "vtkVector.h"
namespace
{
vtkVector3d AllGather(const vtkVector3d& spacing, vtkMultiProcessController* controller)
{
vtkVector3d in(spacing);
vtkVector3d out = in;
controller->AllReduce(in.GetData(), out.GetData(), 3, vtkCommunicator::MAX_OP);
for (int cc = 0; cc < 3; ++cc)
{
in[cc] = (in[cc] == 0 || in[cc] == out[cc]) ? out[cc] : VTK_DOUBLE_MAX;
}
controller->AllReduce(in.GetData(), out.GetData(), 3, vtkCommunicator::MAX_OP);
return out;
}
bool IsSpacingValid(const vtkVector3d& spacing)
{
return spacing[0] < VTK_DOUBLE_MAX && spacing[1] < VTK_DOUBLE_MAX && spacing[2] < VTK_DOUBLE_MAX;
}
bool ComputeGlobalOrigin(vtkVector3d& origin, const std::vector<vtkImageData*>& images,
vtkMultiProcessController* controller, const int minimumExtent[3])
{
// First, confirm that spacing is compatible. All images must have the same
// spacing otherwise we cannot pick a valid global origin/extent.
vtkVector3d spacing =
images.empty() ? vtkVector3d(0.0) : vtkVector3d(images.front()->GetSpacing());
for (auto* image : images)
{
if (image->GetNumberOfPoints() > 0)
{
const vtkVector3d imgSpacing(image->GetSpacing());
if (spacing != imgSpacing)
{
spacing = vtkVector3d(VTK_DOUBLE_MAX);
}
}
}
vtkVector3d globalSpacing = AllGather(spacing, controller);
if (!IsSpacingValid(globalSpacing))
{
vtkLogF(ERROR, "Cannot determine acceptable global spacing.");
return false;
}
// Compute global bounds to determine the global image origin.
vtkBoundingBox bbox;
for (auto* image : images)
{
bbox.AddBounds(image->GetBounds());
}
vtkBoundingBox globalBounds;
controller->AllReduce(bbox, globalBounds);
globalBounds.GetMinPoint(origin.GetData());
// globalOrigin is based on the extent set at (0, 0, 0) right now. we need to adjust
// globalOrigin based on minimumExtent though.
for (int cc = 0; cc < 3; ++cc)
{
origin[cc] -= minimumExtent[cc] * spacing[cc];
}
return true;
}
}
VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkAlignImageDataSetFilter);
vtkCxxSetObjectMacro(vtkAlignImageDataSetFilter, Controller, vtkMultiProcessController);
//-----------------------------------------------------------------------------
vtkAlignImageDataSetFilter::vtkAlignImageDataSetFilter()
: Controller(nullptr)
, MinimumExtent{ 0, 0, 0 }
{
this->SetController(vtkMultiProcessController::GetGlobalController());
}
//-----------------------------------------------------------------------------
vtkAlignImageDataSetFilter::~vtkAlignImageDataSetFilter()
{
this->SetController(nullptr);
}
//-----------------------------------------------------------------------------
int vtkAlignImageDataSetFilter::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info)
{
info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPartitionedDataSetCollection");
info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPartitionedDataSet");
info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkMultiBlockDataSet");
return 1;
}
//------------------------------------------------------------------------------
int vtkAlignImageDataSetFilter::RequestData(
vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
auto outputCD = vtkCompositeDataSet::GetData(outputVector, 0);
outputCD->ShallowCopy(vtkDataObject::GetData(inputVector[0], 0));
auto images = vtkCompositeDataSet::GetDataSets<vtkImageData>(outputCD);
auto controller = this->GetController()
? vtk::MakeSmartPointer(this->GetController())
: vtk::TakeSmartPointer<vtkMultiProcessController>(vtkDummyController::New());
int countLocal = static_cast<int>(images.size());
int countGlobal = countLocal;
controller->AllReduce(&countLocal, &countGlobal, 1, vtkCommunicator::SUM_OP);
if (countGlobal == 0)
{
// no images present. nothing to do.
return 1;
}
// the origin that all output image datas will have
vtkVector3d globalOrigin(VTK_DOUBLE_MAX);
if (!::ComputeGlobalOrigin(globalOrigin, images, controller, this->MinimumExtent))
{
vtkErrorMacro("Failed to compute global origin.");
return 0;
}
int illalignedOrigins = 0;
// adjust image extents
for (auto* image : images)
{
if (image->GetNumberOfPoints() == 0)
{
continue;
}
const vtkVector3d imgOrigin(image->GetOrigin());
const vtkVector3d imgSpacing(image->GetSpacing());
// we know the relationship is:
// inputOrigin+inputExtent[any]*spacing=globalOrigin+outputExtent[any]*spacing
// remember that spacing is the same for input and output imagedatas
int outputExtent[6], inputExtent[6], dims[3];
image->GetExtent(inputExtent);
image->GetDimensions(dims);
for (int cc = 0; cc < 3; ++cc)
{
outputExtent[2 * cc] = static_cast<int>(
(imgOrigin[cc] + inputExtent[2 * cc] * imgSpacing[cc]) - globalOrigin[cc] / imgSpacing[cc]);
// we compute the max extent based on the imagedata extent to reduce
// the chance of round-off error that may change the number of
// points and cells in the imagedata
outputExtent[2 * cc + 1] = outputExtent[2 * cc] + dims[cc] - 1;
}
const vtkVector3d pt0(image->GetPoint(0));
image->SetOrigin(globalOrigin.GetData());
image->SetExtent(outputExtent);
const vtkVector3d newPt0(image->GetPoint(0));
if ((newPt0 - pt0).Norm() > 1e-10)
{
vtkLogF(ERROR,
"Global spacing (%f, %f, %f)/origin (%f, %f, %f) incompatible for image with first point "
"at (%f, %f, %f) by amount %f",
imgSpacing[0], imgSpacing[1], imgSpacing[2], globalOrigin[0], globalOrigin[1],
globalOrigin[2], pt0[0], pt0[1], pt0[2], (newPt0 - pt0).Norm());
illalignedOrigins = 1;
}
}
int illalignedOriginsGlobal = illalignedOrigins;
controller->AllReduce(&illalignedOrigins, &illalignedOriginsGlobal, 1, vtkCommunicator::MAX_OP);
return (illalignedOriginsGlobal == 1) ? 0 : 1;
}
//-----------------------------------------------------------------------------
void vtkAlignImageDataSetFilter::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os, indent);
os << indent << "Controller: " << this->Controller << endl;
os << indent << "MinimumExtent: " << this->MinimumExtent[0] << ", " << this->MinimumExtent[1]
<< ", " << this->MinimumExtent[2] << endl;
}
VTK_ABI_NAMESPACE_END
|