// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkSmartPointer.h" #include "vtkActor.h" #include "vtkCamera.h" #include "vtkCellPicker.h" #include "vtkImageActor.h" #include "vtkImageData.h" #include "vtkImageDataOutlineFilter.h" #include "vtkImageMapToColors.h" #include "vtkImageMapper3D.h" #include "vtkImagePlaneWidget.h" #include "vtkLogger.h" #include "vtkLookupTable.h" #include "vtkMath.h" #include "vtkMathUtilities.h" #include "vtkPolyDataMapper.h" #include "vtkProperty.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkRenderer.h" #include "vtkVolume16Reader.h" #include "vtkTestUtilities.h" namespace helper { struct ImageInfo { vtkIdType NbOfPoints; vtkIdType NbOfCells; double Origin[3]; int Dimensions[3]; int Extent[6]; ImageInfo(vtkImageData* image) { this->NbOfPoints = image->GetNumberOfPoints(); this->NbOfCells = image->GetNumberOfCells(); image->GetOrigin(this->Origin); image->GetDimensions(this->Dimensions); image->GetExtent(this->Extent); } }; }; //------------------------------------------------------------------------------ vtkSmartPointer loadImage(int argc, char* argv[]) { char* fname = vtkTestUtilities::ExpandDataFileName(argc, argv, "Data/headsq/quarter"); vtkNew volumeReader; volumeReader->SetDataDimensions(64, 64); volumeReader->SetDataByteOrderToLittleEndian(); volumeReader->SetImageRange(1, 93); volumeReader->SetDataSpacing(3.2, 3.2, 1.5); volumeReader->SetFilePrefix(fname); volumeReader->SetDataMask(0x7fff); volumeReader->Update(); delete[] fname; return volumeReader->GetOutput(); } //------------------------------------------------------------------------------ void addImageOutlineToRenderer(vtkImageData* image, vtkRenderer* renderer, double color[3]) { vtkNew outline; outline->SetInputData(image); vtkNew outlineMapper; outlineMapper->SetInputConnection(outline->GetOutputPort()); vtkNew outlineActor; outlineActor->SetMapper(outlineMapper); outlineActor->GetProperty()->SetColor(color); renderer->AddActor(outlineActor); } //------------------------------------------------------------------------------ void addResliceOutputToRenderer(vtkImagePlaneWidget* planeWidget, vtkRenderer* renderer) { vtkNew colorMap; colorMap->PassAlphaToOutputOff(); colorMap->SetActiveComponent(0); colorMap->SetOutputFormatToLuminance(); colorMap->SetInputData(planeWidget->GetResliceOutput()); colorMap->SetLookupTable(planeWidget->GetLookupTable()); vtkNew imageActor; imageActor->PickableOff(); imageActor->GetMapper()->SetInputConnection(colorMap->GetOutputPort()); renderer->AddActor(imageActor); } //------------------------------------------------------------------------------ void setupPlaneWidget( vtkImagePlaneWidget* planeWidget, vtkRenderWindowInteractor* iren, vtkImageData* volumeInput) { vtkNew picker; picker->SetTolerance(0.005); planeWidget->SetInteractor(iren); planeWidget->SetPicker(picker); planeWidget->SetInputData(volumeInput); planeWidget->SetPlaneOrientationToXAxes(); planeWidget->SetSliceIndex(42); planeWidget->On(); } //------------------------------------------------------------------------------ void setupWindow(vtkRenderWindow* window, vtkRenderer* sceneRenderer, vtkRenderer* straightSliceRenderer, vtkRenderer* orientedSliceRenderer) { window->SetMultiSamples(0); window->SetSize(600, 350); window->AddRenderer(sceneRenderer); window->AddRenderer(straightSliceRenderer); window->AddRenderer(orientedSliceRenderer); sceneRenderer->SetBackground(0.4, 0.4, 0.8); straightSliceRenderer->SetBackground(0.8, 0.4, 0.8); orientedSliceRenderer->SetBackground(0.4, 0.8, 0.8); sceneRenderer->SetViewport(0, 0, 0.5, 1); straightSliceRenderer->SetViewport(0.5, 0, 1, 0.5); orientedSliceRenderer->SetViewport(0.5, 0.5, 1, 1); window->Render(); sceneRenderer->ResetCamera(); straightSliceRenderer->ResetCamera(); sceneRenderer->GetActiveCamera()->Elevation(110); sceneRenderer->GetActiveCamera()->SetViewUp(0, 0, -1); sceneRenderer->GetActiveCamera()->Azimuth(45); sceneRenderer->GetActiveCamera()->Dolly(1.15); sceneRenderer->ResetCameraClippingRange(); } //------------------------------------------------------------------------------ void compareReslicePlane(vtkImageData* straightImage, vtkImageData* orientedImage, vtkImagePlaneWidget* straightWidget, vtkImagePlaneWidget* orientedWidget) { double straightPoint[3]; straightWidget->GetOrigin(straightPoint); double straightIndices[3]; straightImage->TransformPhysicalPointToContinuousIndex(straightPoint, straightIndices); double orientedPoint[3]; orientedWidget->GetOrigin(orientedPoint); double orientedIndices[3]; orientedImage->TransformPhysicalPointToContinuousIndex(orientedPoint, orientedIndices); double indicesDelta = vtkMath::Distance2BetweenPoints(straightIndices, orientedIndices); vtkLogIf(ERROR, indicesDelta > 10e-6, "Reslice planes should have same Origin in image coordinates. Has " << orientedIndices[0] << " " << orientedIndices[1] << " " << orientedIndices[2] << " instead of " << straightIndices[0] << " " << straightIndices[1] << " " << straightIndices[2] << ". Error squared: " << indicesDelta); double straightNormal[3]; straightWidget->GetNormal(straightNormal); double orientedNormal[3]; orientedWidget->GetNormal(orientedNormal); straightImage->TransformPhysicalPointToContinuousIndex(straightNormal, straightIndices); orientedImage->TransformPhysicalPointToContinuousIndex(orientedNormal, orientedIndices); double angle = vtkMath::AngleBetweenVectors(straightIndices, orientedIndices); vtkLogIf(ERROR, !vtkMathUtilities::FuzzyCompare(angle, 0.), "Reslice planes should have same Normal in image coordinates. Has " << orientedIndices[0] << " " << orientedIndices[1] << " " << orientedIndices[2] << " instead of " << straightIndices[0] << " " << straightIndices[1] << " " << straightIndices[2] << ". Angle is: " << angle); } //------------------------------------------------------------------------------ void compareResliceImage(vtkImagePlaneWidget* straightWidget, vtkImagePlaneWidget* orientedWidget) { auto straightSlice = straightWidget->GetResliceOutput(); auto orientedSlice = orientedWidget->GetResliceOutput(); auto straightInfo = helper::ImageInfo(straightSlice); auto orientedInfo = helper::ImageInfo(orientedSlice); vtkLogIf(ERROR, straightInfo.NbOfPoints != orientedInfo.NbOfPoints, "Number of points differs. Has " << orientedInfo.NbOfPoints << " instead of " << straightInfo.NbOfPoints); vtkLogIf(ERROR, straightInfo.NbOfCells != orientedInfo.NbOfCells, "Number of cells differs. Has " << orientedInfo.NbOfCells << " instead of " << straightInfo.NbOfCells); for (int axes = 0; axes < 3; axes++) { if (!vtkMathUtilities::NearlyEqual(straightInfo.Origin[axes], orientedInfo.Origin[axes], 10e-6)) { vtkLog(ERROR, "Origin differs. Has " << orientedInfo.Origin[0] << " " << orientedInfo.Origin[1] << " " << orientedInfo.Origin[2] << " " << "instead of " << straightInfo.Origin[0] << " " << straightInfo.Origin[1] << " " << straightInfo.Origin[2] << "."); break; } } for (int axes = 0; axes < 3; axes++) { if (straightInfo.Dimensions[axes] != orientedInfo.Dimensions[axes]) { vtkLog(ERROR, "Dimensions differs. Has " << orientedInfo.Dimensions[0] << " " << orientedInfo.Dimensions[1] << " " << orientedInfo.Dimensions[2] << " " << "instead of " << straightInfo.Dimensions[0] << " " << straightInfo.Dimensions[1] << " " << straightInfo.Dimensions[2] << "."); break; } } for (int axes = 0; axes < 3; axes++) { if (straightInfo.Extent[2 * axes] != orientedInfo.Extent[2 * axes] || straightInfo.Extent[2 * axes + 1] != orientedInfo.Extent[2 * axes + 1]) { vtkLog(ERROR, "Extent differs. Has " << orientedInfo.Extent[0] << " " << orientedInfo.Extent[1] << " " << orientedInfo.Extent[2] << " " << orientedInfo.Extent[3] << " " << orientedInfo.Extent[4] << " " << orientedInfo.Extent[5] << " " << "instead of " << straightInfo.Extent[0] << " " << straightInfo.Extent[1] << " " << straightInfo.Extent[2] << "." << straightInfo.Extent[3] << " " << straightInfo.Extent[4] << " " << straightInfo.Extent[5] << "."); break; } } vtkLogIf(ERROR, false, "Slices are different"); } //------------------------------------------------------------------------------ int ImagePlaneWidgetOriented(int argc, char* argv[]) { vtkSmartPointer straightData = loadImage(argc, argv); vtkNew sceneRenderer; double color[3] = { 0.5, 0.5, 0.5 }; addImageOutlineToRenderer(straightData, sceneRenderer, color); // rotate original data by Pi/5 vtkNew orientedData; orientedData->DeepCopy(straightData); constexpr double ANGLE = vtkMath::Pi() / 5; const double COS = std::cos(ANGLE); const double SIN = std::sin(ANGLE); orientedData->SetDirectionMatrix(COS, -SIN, 0, SIN, COS, 0, 0, 0, 1); color[0] = 1; color[1] = 1; color[2] = 0; addImageOutlineToRenderer(orientedData, sceneRenderer, color); vtkNew window; vtkNew orientedSliceRenderer; vtkNew straightSliceRenderer; setupWindow(window, sceneRenderer, orientedSliceRenderer, straightSliceRenderer); vtkNew interactor; interactor->SetRenderWindow(window); vtkNew orientedPlaneWidget; setupPlaneWidget(orientedPlaneWidget, interactor, orientedData); addResliceOutputToRenderer(orientedPlaneWidget, orientedSliceRenderer); vtkNew straightPlaneWidget; setupPlaneWidget(straightPlaneWidget, interactor, straightData); addResliceOutputToRenderer(straightPlaneWidget, straightSliceRenderer); window->Render(); sceneRenderer->ResetCamera(); orientedSliceRenderer->ResetCamera(); straightSliceRenderer->ResetCamera(); compareReslicePlane(straightData, orientedData, straightPlaneWidget, orientedPlaneWidget); compareResliceImage(straightPlaneWidget, orientedPlaneWidget); interactor->Initialize(); window->Render(); interactor->Start(); return EXIT_SUCCESS; }