Skip to content

Commit

Permalink
BUG: WIP: Issue Slicer#7179 Curved Planar Reformation
Browse files Browse the repository at this point in the history
Miscellanous minor bugs
  • Loading branch information
Leengit committed Jan 16, 2025
1 parent 52a91fb commit 1c25f2b
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Base/QTApp/qSlicerApplicationHelper.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ void qSlicerApplicationHelper::setupModuleFactoryManager(qSlicerModuleFactoryMan
void qSlicerApplicationHelper::registerVolumeResamplers(qSlicerApplication& app)
{
#ifdef Slicer_BUILD_CLI_SUPPORT
app.applicationLogic()->RegisterVolumeResampler("ScalarVectorDWIVolumeResampler", vtkNew<vtkMRMLScalarVectorDWIVolumeResampler>().GetPointer());
app.applicationLogic()->RegisterVolumeResampler("ResampleScalarVectorDWIVolume", vtkNew<vtkMRMLScalarVectorDWIVolumeResampler>().GetPointer());
#else
Q_UNUSED(app);
#endif
Expand Down
59 changes: 30 additions & 29 deletions Base/QTCLI/vtkMRMLScalarVectorDWIVolumeResampler.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,32 @@ void vtkMRMLScalarVectorDWIVolumeResampler::PrintSelf(ostream& os, vtkIndent ind

//----------------------------------------------------------------------------
bool vtkMRMLScalarVectorDWIVolumeResampler::Resample(vtkMRMLVolumeNode* inputVolume,
vtkMRMLVolumeNode* outputVolume,
vtkMRMLTransformNode* resamplingTransform,
vtkMRMLVolumeNode* referenceVolume,
int interpolationType,
int windowedSincFunction,
const ResamplingParameters& vtkNotUsed(resamplingParameter))
vtkMRMLVolumeNode* outputVolume,
vtkMRMLTransformNode* resamplingTransform,
vtkMRMLVolumeNode* referenceVolume,
int interpolationType,
int windowedSincFunction,
const ResamplingParameters& vtkNotUsed(resamplingParameter))
{
// We need to cliLogic->GetMRMLScene()->RemoveNode(cmdNode) no matter how we exit this function, so we build a garbage
// collection structure.
struct vtkMRMLCommandLineModuleNodeGC
{
public:
vtkMRMLCommandLineModuleNodeGC(vtkSlicerCLIModuleLogic* cliLogicIn)
{
this->cliLogic = cliLogicIn;
this->cmdNode = this->cliLogic->CreateNodeInScene();
}
~vtkMRMLCommandLineModuleNodeGC() { this->cliLogic->GetMRMLScene()->RemoveNode(this->cmdNode); }
operator vtkMRMLCommandLineModuleNode*() { return this->cmdNode; }
vtkMRMLCommandLineModuleNode* operator->() { return this->cmdNode; }

private:
vtkSlicerCLIModuleLogic* cliLogic;
vtkMRMLCommandLineModuleNode* cmdNode;
};

qSlicerCoreApplication* app = qSlicerCoreApplication::application();
if (!app
|| !app->moduleManager()
Expand All @@ -74,39 +93,21 @@ bool vtkMRMLScalarVectorDWIVolumeResampler::Resample(vtkMRMLVolumeNode* inputVol
qSlicerCLIModule* cliModule =
dynamic_cast<qSlicerCLIModule*>(app->moduleManager()->module("ResampleScalarVectorDWIVolume"));

vtkSlicerCLIModuleLogic* cliLogic = vtkSlicerCLIModuleLogic::SafeDownCast(cliModule->logic());
vtkSlicerCLIModuleLogic* cliLogic = vtkSlicerCLIModuleLogic::SafeDownCast(cliModule->cliModuleLogic());
if (cliLogic == nullptr)
{
vtkErrorMacro("Resample: ResampleScalarVectorDWIVolume module logic is not available");
return false;
}

// PERF: Revisit if creating and removing the node impacts performances
vtkMRMLCommandLineModuleNode* cmdNodeTry = cliLogic->CreateNodeInScene();
if (cmdNodeTry == nullptr)
// PERF: Revisit if creating and removing the node impacts performances.
vtkMRMLCommandLineModuleNodeGC cmdNode(cliLogic);
if (cmdNode == nullptr)
{
vtkErrorMacro("Resample: Failed to create ResampleScalarVectorDWIVolume node");
return false;
}

// We need to cliLogic->GetMRMLScene()->RemoveNode(cmdNode) no matter how we exit this function
struct vtkMRMLCommandLineModuleNodeGC
{
public:
vtkMRMLCommandLineModuleNodeGC(vtkSlicerCLIModuleLogic* cliLogicIn, vtkMRMLCommandLineModuleNode* cmdNodeIn)
{
this->cliLogic = cliLogicIn;
this->cmdNode = cmdNodeIn;
}
~vtkMRMLCommandLineModuleNodeGC() { this->cliLogic->GetMRMLScene()->RemoveNode(this->cmdNode); }
operator vtkMRMLCommandLineModuleNode*() { return this->cmdNode; }
vtkMRMLCommandLineModuleNode* operator->() { return this->cmdNode; }

private:
vtkSlicerCLIModuleLogic* cliLogic;
vtkMRMLCommandLineModuleNode* cmdNode;
};
vtkMRMLCommandLineModuleNodeGC cmdNode(cliLogic, cmdNodeTry);

if (inputVolume == nullptr)
{
vtkErrorMacro("Resample: Input volume node is not set");
Expand Down
119 changes: 90 additions & 29 deletions Libs/MRML/Logic/vtkMRMLSliceLogic.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1041,10 +1041,29 @@ void vtkMRMLSliceLogic::CurvedPlanarReformationInit()
}

//----------------------------------------------------------------------------
void vtkMRMLSliceLogic::CurvedPlanarReformationGetPointsProjectedToPlane(vtkPoints* pointsArrayIn,
bool vtkMRMLSliceLogic::CurvedPlanarReformationGetPointsProjectedToPlane(vtkPoints* pointsArrayIn,
vtkMatrix4x4* transformWorldToPlane,
vtkPoints* pointsArrayOut)
{
if (pointsArrayIn == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationGetPointsProjectedToPlane failed: "
"pointsArrayIn not supplied");
return false;
}
if (transformWorldToPlane == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationGetPointsProjectedToPlane failed: "
"transformWorldToPlane not supplied");
return false;
}
if (pointsArrayOut == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationGetPointsProjectedToPlane failed: "
"pointsArrayOut not supplied");
return false;
}

// Returns points projected to the plane coordinate system (plane normal = plane Z axis).

// Compute the inverse transformation
Expand All @@ -1060,7 +1079,7 @@ void vtkMRMLSliceLogic::CurvedPlanarReformationGetPointsProjectedToPlane(vtkPoin
for (vtkIdType i = 0; i < numPoints; ++i)
{
// Note: uses only the first three elements of pIn
pointsArrayIn->GetPoint(i, static_cast<double*>(pIn)); // requires double[3]
pointsArrayIn->GetPoint(i, static_cast<double*>(pIn));
// Point positions in the plane coordinate system:
transformWorldToPlane->MultiplyPoint(pIn, pMiddle);
// Projected point positions in the plane coordinate system:
Expand All @@ -1069,6 +1088,7 @@ void vtkMRMLSliceLogic::CurvedPlanarReformationGetPointsProjectedToPlane(vtkPoin
transformPlaneToWorld->MultiplyPoint(pMiddle, pOut);
pointsArrayOut->SetPoint(i, pOut[0], pOut[1], pOut[2]);
}
return true;
}

//----------------------------------------------------------------------------
Expand All @@ -1081,6 +1101,19 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
double rotationDeg,
vtkMRMLModelNode* reslicingPlanesModelNode)
{
if (transformToStraightenedNode == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform failed: "
"transformToStraightenedNode not supplied");
return false;
}
if (curveNode == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform failed: "
"curveNode not supplied");
return false;
}

/*
Compute straightened volume (useful for example for visualization of curved vessels)
stretching: if True then stretching transform will be computed, otherwise straightening
Expand Down Expand Up @@ -1141,7 +1174,7 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
{
for (int i = 0; i < 3; ++i)
{
transformGridAxisZ[i] = orthogonalizedTransformGridAxisZ[1];
transformGridAxisZ[i] = orthogonalizedTransformGridAxisZ[i];
}
}
else
Expand Down Expand Up @@ -1238,7 +1271,7 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(

// After projection, resampling is needed to get uniform distances
originalCurvePoints = resampledCurveNode->GetCurvePointsWorld();
sampledPoints->Reset();
sampledPoints = vtkSmartPointer<vtkPoints>::New();
if (!vtkMRMLMarkupsCurveNode::ResamplePoints(originalCurvePoints, sampledPoints, resamplingCurveSpacing, false))
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform failed: "
Expand Down Expand Up @@ -1274,13 +1307,13 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
const int numberOfSlices = resampledCurveNode->GetNumberOfControlPoints();
const int gridDimensions[3] = { 2, 2, numberOfSlices };
const double gridSpacing[3] = { sliceSizeMm[0], sliceSizeMm[1], resamplingCurveSpacing };
vtkSmartPointer<vtkMatrix4x4> gridDirectionMatrixArray = vtkSmartPointer<vtkMatrix4x4>::New();
gridDirectionMatrixArray->Identity();
gridDirectionMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
gridDirectionMatrix->Identity();
for (int i = 0; i < 3; ++i)
{
gridDirectionMatrixArray->SetElement(i, 0, transformGridAxisX[i]);
gridDirectionMatrixArray->SetElement(i, 1, transformGridAxisY[i]);
gridDirectionMatrixArray->SetElement(i, 2, transformGridAxisZ[i]);
gridDirectionMatrix->SetElement(i, 0, transformGridAxisX[i]);
gridDirectionMatrix->SetElement(i, 1, transformGridAxisY[i]);
gridDirectionMatrix->SetElement(i, 2, transformGridAxisZ[i]);
}

vtkSmartPointer<vtkImageData> gridImage = vtkSmartPointer<vtkImageData>::New();
Expand All @@ -1290,10 +1323,14 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
gridImage->AllocateScalars(VTK_DOUBLE, 3);
vtkSmartPointer<vtkOrientedGridTransform> transform = vtkSmartPointer<vtkOrientedGridTransform>::New();
transform->SetDisplacementGridData(gridImage);
transform->SetGridDirectionMatrix(gridDirectionMatrixArray);
transform->SetGridDirectionMatrix(gridDirectionMatrix);
transformToStraightenedNode->SetAndObserveTransformFromParent(transform);

vtkSmartPointer<vtkAppendPolyData> appender = vtkSmartPointer<vtkAppendPolyData>::New();
vtkSmartPointer<vtkAppendPolyData> appender;
if (reslicingPlanesModelNode != nullptr)
{
appender = vtkSmartPointer<vtkAppendPolyData>::New();
}

// Currently there is no API to set PreferredInitialNormalVector in the curve
// coordinate system, therefore a new coordinate system generator must be set up:
Expand All @@ -1312,9 +1349,10 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
// pointData->GetAbstractArray(curveCoordinateSystemGeneratorWorld->GetTangentsArrayName()));

// Compute displacements
vtkSmartPointer<vtkDoubleArray> transformDisplacements_RAS = vtkSmartPointer<vtkDoubleArray>::New();
transformDisplacements_RAS->SetNumberOfComponents(3);
transformDisplacements_RAS->SetNumberOfTuples(gridDimensions[2] * gridDimensions[1] * gridDimensions[0]);
vtkGridTransform* transformGrid = vtkGridTransform::SafeDownCast(transformToStraightenedNode->GetTransformFromParent());
vtkImageData* displacementGrid = transformGrid->GetDisplacementGrid();
vtkDataArray* transformDisplacements_RAS = displacementGrid->GetPointData()->GetScalars();

for (int gridK = 0; gridK < gridDimensions[2]; ++gridK)
{
// The curve's built-in coordinate system generator could be used like this
Expand All @@ -1336,7 +1374,7 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
const double* curveAxisY_RASVec = binormals->GetTuple3(curvePointIndex);
const double* curvePoint_RAS = curvePoly->GetPoint(curvePointIndex);

vtkPlaneSource* plane = vtkPlaneSource::SafeDownCast(this->SliceModelNode->GetPolyDataConnection()->GetProducer());
vtkSmartPointer<vtkPlaneSource> plane;
for (int gridJ = 0; gridJ < gridDimensions[1]; ++gridJ)
{
for (int gridI = 0; gridI < gridDimensions[0]; ++gridI)
Expand All @@ -1355,6 +1393,7 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
{
if (gridI == 0 && gridJ == 0)
{
plane = vtkSmartPointer<vtkPlaneSource>::New();
plane->SetOrigin(inputVolume_RAS);
}
else if (gridI == 1 && gridJ == 0)
Expand All @@ -1366,12 +1405,10 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
plane->SetPoint2(inputVolume_RAS);
}
}
double difference_RAS[3];
for (int i = 0; i < 3; ++i)
{
difference_RAS[i] = inputVolume_RAS[i] - straightenedVolume_RAS[i];
}
const int index = gridK * gridDimensions[1] * gridDimensions[0] + gridJ * gridDimensions[0] + gridI;
const int index = (gridK * gridDimensions[1] + gridJ) * gridDimensions[0] + gridI;
const double difference_RAS[3] = { inputVolume_RAS[0] - straightenedVolume_RAS[0],
inputVolume_RAS[1] - straightenedVolume_RAS[1],
inputVolume_RAS[2] - straightenedVolume_RAS[2] };
transformDisplacements_RAS->SetTuple(index, difference_RAS);
}
}
Expand All @@ -1382,9 +1419,8 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(
}
}

vtkGridTransform* transformGrid =
vtkGridTransform::SafeDownCast(transformToStraightenedNode->GetTransformFromParent());
vtkImageData* displacementGrid = transformGrid->GetDisplacementGrid();
transformGrid = vtkGridTransform::SafeDownCast(transformToStraightenedNode->GetTransformFromParent());
displacementGrid = transformGrid->GetDisplacementGrid();
displacementGrid->GetPointData()->GetScalars()->Modified();
displacementGrid->Modified();

Expand All @@ -1393,7 +1429,6 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationComputeStraighteningTransform(

if (reslicingPlanesModelNode)
{
vtkSmartPointer<vtkAppendPolyData> appender = vtkSmartPointer<vtkAppendPolyData>::New();
appender->Update();
if (!reslicingPlanesModelNode->GetPolyData())
{
Expand All @@ -1413,9 +1448,22 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationStraightenVolume(vtkMRMLScalarVol
{
// Compute straightened volume (useful for example for visualization of curved vessels)

if (!outputStraightenedVolume || !volumeNode || !straighteningTransformNode)
if (outputStraightenedVolume == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationStraightenVolume failed: "
"outputStraightenedVolume not supplied");
return false;
}
if (volumeNode == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationStraightenVolume failed: "
"volumeNode not supplied");
return false;
}
if (straighteningTransformNode == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationStraightenVolume failed: invalid input parameters");
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationStraightenVolume failed: "
"straighteningTransformNode not supplied");
return false;
}

Expand Down Expand Up @@ -1526,9 +1574,22 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationProjectVolume(vtkMRMLScalarVolume
{
// Create panoramic volume by mean intensity projection along an axis of the straightened volume

if (outputProjectedVolume == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationProjectVolume failed: "
"outputProjectedVolume not supplied");
return false;
}
if (inputStraightenedVolume == nullptr)
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationProjectVolume failed: "
"inputStraightenedVolume not supplied");
return false;
}
if ((projectionAxisIndex < 0) || (projectionAxisIndex >= 3))
{
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationProjectVolume failed: invalid input parameters");
vtkErrorMacro("vtkMRMLSliceLogic::CurvedPlanarReformationProjectVolume failed: "
"projectionAxisIndex is out of range");
return false;
}

Expand Down Expand Up @@ -1631,7 +1692,7 @@ bool vtkMRMLSliceLogic::CurvedPlanarReformationProjectVolume(vtkMRMLScalarVolume
double curvePointToWorldArray[4][4] = { 0.0 };
for (int i = 0; i < 4; ++i)
{
for (int j = 0; i < 4; ++j)
for (int j = 0; j < 4; ++j)
{
curvePointToWorldArray[i][j] = ijkToRas->GetElement(i, j);
}
Expand Down
2 changes: 1 addition & 1 deletion Libs/MRML/Logic/vtkMRMLSliceLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ class VTK_MRML_LOGIC_EXPORT vtkMRMLSliceLogic : public vtkMRMLAbstractLogic
void CurvedPlanarReformationInit();

/// GetPointsProjectedToPlane for curved planar reformation
void CurvedPlanarReformationGetPointsProjectedToPlane(vtkPoints * pointsArrayIn,
bool CurvedPlanarReformationGetPointsProjectedToPlane(vtkPoints * pointsArrayIn,
vtkMatrix4x4 * transformWorldToPlane,
vtkPoints * pointsArrayOut);

Expand Down

0 comments on commit 1c25f2b

Please sign in to comment.