/*========================================================================= Program: Visualization Toolkit Module: ImagePlaneWidget.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkRegressionTestImage.h" #include "vtkSmartPointer.h" #include "vtkActor.h" #include "vtkBiDimensionalWidget.h" #include "vtkCamera.h" #include "vtkCellPicker.h" #include "vtkCommand.h" #include "vtkDICOMImageReader.h" #include "vtkImageActor.h" #include "vtkImageData.h" #include "vtkImageMapToColors.h" #include "vtkImagePlaneWidget.h" #include "vtkImageReader.h" #include "vtkImageReslice.h" #include "vtkInteractorEventRecorder.h" #include "vtkInteractorStyleImage.h" #include "vtkLookupTable.h" #include "vtkOutlineFilter.h" #include "vtkPlane.h" #include "vtkPlaneSource.h" #include "vtkPointData.h" #include "vtkPolyDataMapper.h" #include "vtkProperty.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkRenderer.h" #include "vtkResliceCursor.h" #include "vtkResliceCursorActor.h" #include "vtkResliceCursorLineRepresentation.h" #include "vtkResliceCursorPolyDataAlgorithm.h" #include "vtkResliceCursorThickLineRepresentation.h" #include "vtkResliceCursorWidget.h" #include "vtkVolume16Reader.h" #include "vtkTestUtilities.h" //------------------------------------------------------------------------------ class vtkResliceCursorCallback3 : public vtkCommand { public: static vtkResliceCursorCallback3* New() { return new vtkResliceCursorCallback3; } void Execute(vtkObject* caller, unsigned long /*ev*/, void* callData) override { vtkImagePlaneWidget* ipw = dynamic_cast(caller); if (ipw) { double* wl = static_cast(callData); if (ipw == this->IPW[0]) { this->IPW[1]->SetWindowLevel(wl[0], wl[1], 1); this->IPW[2]->SetWindowLevel(wl[0], wl[1], 1); } else if (ipw == this->IPW[1]) { this->IPW[0]->SetWindowLevel(wl[0], wl[1], 1); this->IPW[2]->SetWindowLevel(wl[0], wl[1], 1); } else if (ipw == this->IPW[2]) { this->IPW[0]->SetWindowLevel(wl[0], wl[1], 1); this->IPW[1]->SetWindowLevel(wl[0], wl[1], 1); } } vtkResliceCursorWidget* rcw = dynamic_cast(caller); if (rcw) { vtkResliceCursorLineRepresentation* rep = dynamic_cast(rcw->GetRepresentation()); vtkResliceCursor* rc = rep->GetResliceCursorActor()->GetCursorAlgorithm()->GetResliceCursor(); for (int i = 0; i < 3; i++) { vtkPlaneSource* ps = static_cast(this->IPW[i]->GetPolyDataAlgorithm()); ps->SetNormal(rc->GetPlane(i)->GetNormal()); ps->SetCenter(rc->GetPlane(i)->GetOrigin()); // If the reslice plane has modified, update it on the 3D widget this->IPW[i]->UpdatePlacement(); // std::cout << "Updating placement of plane: " << i << " " << // rc->GetPlane(i)->GetNormal()[0] << " " << // rc->GetPlane(i)->GetNormal()[1] << " " << // rc->GetPlane(i)->GetNormal()[2] << std::endl; // this->IPW[i]->GetReslice()->Print(cout); // rep->GetReslice()->Print(cout); // std::cout << "---------------------" << std::endl; } } // Render everything this->RCW[0]->Render(); } vtkResliceCursorCallback3() = default; vtkImagePlaneWidget* IPW[3]; vtkResliceCursorWidget* RCW[3]; }; static const char* TestResliceCursorWidget3Events = "# StreamVersion 1.1\n" // Reslice cursor thickness (Right click on axis) "RightButtonPressEvent 201 152 0 0 0 0\n" "MouseMoveEvent 201 152 0 0 0 0\n" "MouseMoveEvent 201 168 0 0 0 0\n" "MouseMoveEvent 205 187 0 0 0 0\n" "MouseMoveEvent 219 210 0 0 0 0\n" "MouseMoveEvent 232 233 0 0 0 0\n" "RightButtonReleaseEvent 232 233 0 0 0 0\n" // Camera spin (Ctrl + Left button click outside image) "LeftButtonPressEvent 273 86 2 0 0 Control_L\n" "MouseMoveEvent 273 86 2 0 0 Control_L\n" "MouseMoveEvent 271 81 2 0 0 Control_L\n" "MouseMoveEvent 268 68 2 0 0 Control_L\n" "MouseMoveEvent 264 55 2 0 0 Control_L\n" "MouseMoveEvent 260 48 2 0 0 Control_L\n" "MouseMoveEvent 254 39 2 0 0 Control_L\n" "MouseMoveEvent 248 33 2 0 0 Control_L\n" "LeftButtonReleaseEvent 248 33 2 0 0 Control_L\n" // Widget translation (Left button click on center) "LeftButtonPressEvent 454 148 0 0 0 0\n" "MouseMoveEvent 454 148 0 0 0 Control_L\n" "MouseMoveEvent 445 148 0 0 0 Control_L\n" "MouseMoveEvent 424 146 0 0 0 Control_L\n" "MouseMoveEvent 416 146 0 0 0 Control_L\n" "LeftButtonReleaseEvent 416 146 0 0 0 0\n" // Widget rotation (Ctrl + Left button click on axis) "LeftButtonPressEvent 368 147 2 0 0 Control_L\n" "MouseMoveEvent 367 147 2 0 0 Control_L\n" "MouseMoveEvent 367 137 2 0 0 Control_L\n" "MouseMoveEvent 395 89 2 0 0 Control_L\n" "MouseMoveEvent 492 100 2 0 0 Control_L\n" "MouseMoveEvent 511 175 2 0 0 Control_L\n" "MouseMoveEvent 492 206 2 0 0 Control_L\n" "MouseMoveEvent 491 219 2 0 0 Control_L\n" "LeftButtonReleaseEvent 491 219 2 0 0 Control_L\n"; //------------------------------------------------------------------------------ int TestResliceCursorWidget3(int argc, char* argv[]) { char* fname = vtkTestUtilities::ExpandDataFileName(argc, argv, "Data/headsq/quarter"); vtkSmartPointer reader = vtkSmartPointer::New(); reader->SetDataDimensions(64, 64); reader->SetDataByteOrderToLittleEndian(); reader->SetImageRange(1, 93); reader->SetDataSpacing(3.2, 3.2, 1.5); reader->SetFilePrefix(fname); reader->SetDataMask(0x7fff); reader->Update(); delete[] fname; vtkSmartPointer outline = vtkSmartPointer::New(); outline->SetInputConnection(reader->GetOutputPort()); vtkSmartPointer outlineMapper = vtkSmartPointer::New(); outlineMapper->SetInputConnection(outline->GetOutputPort()); vtkSmartPointer outlineActor = vtkSmartPointer::New(); outlineActor->SetMapper(outlineMapper); vtkSmartPointer ren[4]; vtkSmartPointer renWin = vtkSmartPointer::New(); renWin->SetMultiSamples(0); for (int i = 0; i < 4; i++) { ren[i] = vtkSmartPointer::New(); renWin->AddRenderer(ren[i]); } vtkSmartPointer iren = vtkSmartPointer::New(); iren->SetRenderWindow(renWin); vtkSmartPointer picker = vtkSmartPointer::New(); picker->SetTolerance(0.005); vtkSmartPointer ipwProp = vtkSmartPointer::New(); // assign default props to the ipw's texture plane actor vtkSmartPointer planeWidget[3]; int imageDims[3]; reader->GetOutput()->GetDimensions(imageDims); for (int i = 0; i < 3; i++) { planeWidget[i] = vtkSmartPointer::New(); planeWidget[i]->SetInteractor(iren); planeWidget[i]->SetPicker(picker); planeWidget[i]->RestrictPlaneToVolumeOn(); double color[3] = { 0, 0, 0 }; color[i] = 1; planeWidget[i]->GetPlaneProperty()->SetColor(color); planeWidget[i]->SetTexturePlaneProperty(ipwProp); planeWidget[i]->TextureInterpolateOff(); planeWidget[i]->SetResliceInterpolateToLinear(); planeWidget[i]->SetInputConnection(reader->GetOutputPort()); planeWidget[i]->SetPlaneOrientation(i); planeWidget[i]->SetSliceIndex(imageDims[i] / 2); planeWidget[i]->DisplayTextOn(); planeWidget[i]->SetDefaultRenderer(ren[3]); planeWidget[i]->SetWindowLevel(1358, -27); planeWidget[i]->On(); planeWidget[i]->InteractionOn(); } planeWidget[1]->SetLookupTable(planeWidget[0]->GetLookupTable()); planeWidget[2]->SetLookupTable(planeWidget[0]->GetLookupTable()); vtkSmartPointer cbk = vtkSmartPointer::New(); // Create the reslice cursor, widget and rep vtkSmartPointer resliceCursor = vtkSmartPointer::New(); resliceCursor->SetCenter(reader->GetOutput()->GetCenter()); resliceCursor->SetThickMode(1); resliceCursor->SetThickness(10, 10, 10); resliceCursor->SetImage(reader->GetOutput()); vtkSmartPointer resliceCursorWidget[3]; vtkSmartPointer resliceCursorRep[3]; double viewUp[3][3] = { { 0, 0, -1 }, { 0, 0, 1 }, { 0, 1, 0 } }; for (int i = 0; i < 3; i++) { resliceCursorWidget[i] = vtkSmartPointer::New(); resliceCursorWidget[i]->SetInteractor(iren); resliceCursorRep[i] = vtkSmartPointer::New(); resliceCursorWidget[i]->SetRepresentation(resliceCursorRep[i]); resliceCursorRep[i]->GetResliceCursorActor()->GetCursorAlgorithm()->SetResliceCursor( resliceCursor); resliceCursorRep[i]->GetResliceCursorActor()->GetCursorAlgorithm()->SetReslicePlaneNormal(i); const double minVal = reader->GetOutput()->GetScalarRange()[0]; if (vtkImageReslice* reslice = vtkImageReslice::SafeDownCast(resliceCursorRep[i]->GetReslice())) { reslice->SetBackgroundColor(minVal, minVal, minVal, minVal); } resliceCursorWidget[i]->SetDefaultRenderer(ren[i]); resliceCursorWidget[i]->SetEnabled(1); ren[i]->GetActiveCamera()->SetFocalPoint(0, 0, 0); double camPos[3] = { 0, 0, 0 }; camPos[i] = 1; ren[i]->GetActiveCamera()->SetPosition(camPos); ren[i]->GetActiveCamera()->ParallelProjectionOn(); ren[i]->GetActiveCamera()->SetViewUp(viewUp[i]); ren[i]->ResetCamera(); // ren[i]->ResetCameraClippingRange(); // Tie the Image plane widget and the reslice cursor widget together cbk->IPW[i] = planeWidget[i]; cbk->RCW[i] = resliceCursorWidget[i]; resliceCursorWidget[i]->AddObserver(vtkResliceCursorWidget::ResliceAxesChangedEvent, cbk); // Initialize the window level to a sensible value double range[2]; reader->GetOutput()->GetScalarRange(range); resliceCursorRep[i]->SetWindowLevel(range[1] - range[0], (range[0] + range[1]) / 2.0); planeWidget[i]->SetWindowLevel(range[1] - range[0], (range[0] + range[1]) / 2.0); // Make them all share the same color map. resliceCursorRep[i]->SetLookupTable(resliceCursorRep[0]->GetLookupTable()); planeWidget[i]->GetColorMap()->SetLookupTable(resliceCursorRep[0]->GetLookupTable()); // clang-format off // Workaround VTK issue #18441 // Make sure vtkResliceCursorActor is visible by forcing its representation to wireframe. // vtkResliceCursorActor is a quad with a normal parallel to the camera view up vector. // When represented as a surface, it has a thickness of 0 pixels. The class internally turns // edge visibility on to workaround the problem, which does not seem to be enough. resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(0)->SetRepresentationToWireframe(); resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(1)->SetRepresentationToWireframe(); resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(2)->SetRepresentationToWireframe(); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(0)->SetRepresentationToWireframe(); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(1)->SetRepresentationToWireframe(); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(2)->SetRepresentationToWireframe(); // Workaround rendering artefacts with Intel chipsets and osmesa, where lines are rendered // black if perfectly aligned with the camera viewup (see #18453) resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(0)->RenderLinesAsTubesOn(); resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(1)->RenderLinesAsTubesOn(); resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(2)->RenderLinesAsTubesOn(); resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(0)->SetLineWidth(2); resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(1)->SetLineWidth(2); resliceCursorRep[i]->GetResliceCursorActor()->GetCenterlineProperty(2)->SetLineWidth(2); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(0)->RenderLinesAsTubesOn(); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(1)->RenderLinesAsTubesOn(); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(2)->RenderLinesAsTubesOn(); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(0)->SetLineWidth(2); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(1)->SetLineWidth(2); resliceCursorRep[i]->GetResliceCursorActor()->GetThickSlabProperty(2)->SetLineWidth(2); // clang-format on } // Add the actors // ren[0]->SetBackground(0.3, 0.1, 0.1); ren[1]->SetBackground(0.1, 0.3, 0.1); ren[2]->SetBackground(0.1, 0.1, 0.3); ren[3]->AddActor(outlineActor); ren[3]->SetBackground(0.1, 0.1, 0.1); renWin->SetSize(600, 600); // renWin->SetFullScreen(1); ren[0]->SetViewport(0, 0, 0.5, 0.5); ren[1]->SetViewport(0.5, 0, 1, 0.5); ren[2]->SetViewport(0, 0.5, 0.5, 1); ren[3]->SetViewport(0.5, 0.5, 1, 1); // Set the actors' positions // renWin->Render(); ren[3]->GetActiveCamera()->Elevation(110); ren[3]->GetActiveCamera()->SetViewUp(0, 0, -1); ren[3]->GetActiveCamera()->Azimuth(45); ren[3]->GetActiveCamera()->Dolly(1.15); ren[3]->ResetCameraClippingRange(); vtkSmartPointer style = vtkSmartPointer::New(); iren->SetInteractorStyle(style); iren->Initialize(); vtkNew recorder; recorder->ReadFromInputStringOn(); recorder->SetInputString(TestResliceCursorWidget3Events); recorder->SetInteractor(iren); recorder->Play(); recorder->Off(); int retVal = vtkRegressionTestImage(renWin); if (retVal == vtkRegressionTester::DO_INTERACTOR) { iren->Start(); } return !retVal; }