/*========================================================================= Program: Visualization Toolkit Module: vtkInteractorStyleImage.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 "vtkInteractorStyleImage.h" #include "vtkAbstractPropPicker.h" #include "vtkAssemblyPath.h" #include "vtkPropCollection.h" #include "vtkCallbackCommand.h" #include "vtkCamera.h" #include "vtkImageMapper3D.h" #include "vtkImageProperty.h" #include "vtkImageSlice.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkRenderer.h" vtkStandardNewMacro(vtkInteractorStyleImage); //------------------------------------------------------------------------------ vtkInteractorStyleImage::vtkInteractorStyleImage() { this->WindowLevelStartPosition[0] = 0; this->WindowLevelStartPosition[1] = 0; this->WindowLevelCurrentPosition[0] = 0; this->WindowLevelCurrentPosition[1] = 0; this->WindowLevelInitial[0] = 1.0; // Window this->WindowLevelInitial[1] = 0.5; // Level this->CurrentImageProperty = nullptr; this->CurrentImageNumber = -1; this->InteractionMode = VTKIS_IMAGE2D; this->XViewRightVector[0] = 0; this->XViewRightVector[1] = 1; this->XViewRightVector[2] = 0; this->XViewUpVector[0] = 0; this->XViewUpVector[1] = 0; this->XViewUpVector[2] = -1; this->YViewRightVector[0] = 1; this->YViewRightVector[1] = 0; this->YViewRightVector[2] = 0; this->YViewUpVector[0] = 0; this->YViewUpVector[1] = 0; this->YViewUpVector[2] = -1; this->ZViewRightVector[0] = 1; this->ZViewRightVector[1] = 0; this->ZViewRightVector[2] = 0; this->ZViewUpVector[0] = 0; this->ZViewUpVector[1] = 1; this->ZViewUpVector[2] = 0; } //------------------------------------------------------------------------------ vtkInteractorStyleImage::~vtkInteractorStyleImage() { if (this->CurrentImageProperty) { this->CurrentImageProperty->Delete(); } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::StartWindowLevel() { if (this->State != VTKIS_NONE) { return; } this->StartState(VTKIS_WINDOW_LEVEL); // Get the last (the topmost) image this->SetCurrentImageNumber(this->CurrentImageNumber); if (this->HandleObservers && this->HasObserver(vtkCommand::StartWindowLevelEvent)) { this->InvokeEvent(vtkCommand::StartWindowLevelEvent, this); } else { if (this->CurrentImageProperty) { vtkImageProperty* property = this->CurrentImageProperty; this->WindowLevelInitial[0] = property->GetColorWindow(); this->WindowLevelInitial[1] = property->GetColorLevel(); } } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::EndWindowLevel() { if (this->State != VTKIS_WINDOW_LEVEL) { return; } if (this->HandleObservers) { this->InvokeEvent(vtkCommand::EndWindowLevelEvent, this); } this->StopState(); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::StartPick() { if (this->State != VTKIS_NONE) { return; } this->StartState(VTKIS_PICK); if (this->HandleObservers) { this->InvokeEvent(vtkCommand::StartPickEvent, this); } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::EndPick() { if (this->State != VTKIS_PICK) { return; } if (this->HandleObservers) { this->InvokeEvent(vtkCommand::EndPickEvent, this); } this->StopState(); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::StartSlice() { if (this->State != VTKIS_NONE) { return; } this->StartState(VTKIS_SLICE); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::EndSlice() { if (this->State != VTKIS_SLICE) { return; } this->StopState(); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::OnMouseMove() { int x = this->Interactor->GetEventPosition()[0]; int y = this->Interactor->GetEventPosition()[1]; switch (this->State) { case VTKIS_WINDOW_LEVEL: this->FindPokedRenderer(x, y); this->WindowLevel(); this->InvokeEvent(vtkCommand::InteractionEvent, nullptr); break; case VTKIS_PICK: this->FindPokedRenderer(x, y); this->Pick(); this->InvokeEvent(vtkCommand::InteractionEvent, nullptr); break; case VTKIS_SLICE: this->FindPokedRenderer(x, y); this->Slice(); this->InvokeEvent(vtkCommand::InteractionEvent, nullptr); break; } // Call parent to handle all other states and perform additional work this->Superclass::OnMouseMove(); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::OnLeftButtonDown() { int x = this->Interactor->GetEventPosition()[0]; int y = this->Interactor->GetEventPosition()[1]; this->FindPokedRenderer(x, y); if (this->CurrentRenderer == nullptr) { return; } // Redefine this button to handle window/level this->GrabFocus(this->EventCallbackCommand); if (!this->Interactor->GetShiftKey() && !this->Interactor->GetControlKey()) { this->WindowLevelStartPosition[0] = x; this->WindowLevelStartPosition[1] = y; this->StartWindowLevel(); } // If shift is held down, do a rotation else if (this->InteractionMode == VTKIS_IMAGE3D && this->Interactor->GetShiftKey()) { this->StartRotate(); } // If ctrl is held down in slicing mode, slice the image else if (this->InteractionMode == VTKIS_IMAGE_SLICING && this->Interactor->GetControlKey()) { this->StartSlice(); } // The rest of the button + key combinations remain the same else { this->Superclass::OnLeftButtonDown(); } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::OnLeftButtonUp() { switch (this->State) { case VTKIS_WINDOW_LEVEL: this->EndWindowLevel(); if (this->Interactor) { this->ReleaseFocus(); } break; case VTKIS_SLICE: this->EndSlice(); if (this->Interactor) { this->ReleaseFocus(); } break; } // Call parent to handle all other states and perform additional work this->Superclass::OnLeftButtonUp(); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::OnMiddleButtonDown() { this->FindPokedRenderer( this->Interactor->GetEventPosition()[0], this->Interactor->GetEventPosition()[1]); if (this->CurrentRenderer == nullptr) { return; } // If shift is held down, change the slice if ((this->InteractionMode == VTKIS_IMAGE3D || this->InteractionMode == VTKIS_IMAGE_SLICING) && this->Interactor->GetShiftKey()) { this->StartSlice(); } // The rest of the button + key combinations remain the same else { this->Superclass::OnMiddleButtonDown(); } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::OnMiddleButtonUp() { switch (this->State) { case VTKIS_SLICE: this->EndSlice(); if (this->Interactor) { this->ReleaseFocus(); } break; } // Call parent to handle all other states and perform additional work this->Superclass::OnMiddleButtonUp(); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::OnRightButtonDown() { int x = this->Interactor->GetEventPosition()[0]; int y = this->Interactor->GetEventPosition()[1]; this->FindPokedRenderer(x, y); if (this->CurrentRenderer == nullptr) { return; } // Redefine this button + shift to handle pick this->GrabFocus(this->EventCallbackCommand); if (this->Interactor->GetShiftKey()) { this->StartPick(); } else if (this->InteractionMode == VTKIS_IMAGE3D && this->Interactor->GetControlKey()) { this->StartSlice(); } else if (this->InteractionMode == VTKIS_IMAGE_SLICING && this->Interactor->GetControlKey()) { this->StartSpin(); } // The rest of the button + key combinations remain the same else { this->Superclass::OnRightButtonDown(); } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::OnRightButtonUp() { switch (this->State) { case VTKIS_PICK: this->EndPick(); if (this->Interactor) { this->ReleaseFocus(); } break; case VTKIS_SLICE: this->EndSlice(); if (this->Interactor) { this->ReleaseFocus(); } break; case VTKIS_SPIN: if (this->Interactor) { this->EndSpin(); } break; } // Call parent to handle all other states and perform additional work this->Superclass::OnRightButtonUp(); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::OnChar() { vtkRenderWindowInteractor* rwi = this->Interactor; switch (rwi->GetKeyCode()) { case 'f': case 'F': { this->AnimState = VTKIS_ANIM_ON; vtkAssemblyPath* path = nullptr; this->FindPokedRenderer(rwi->GetEventPosition()[0], rwi->GetEventPosition()[1]); rwi->GetPicker()->Pick( rwi->GetEventPosition()[0], rwi->GetEventPosition()[1], 0.0, this->CurrentRenderer); vtkAbstractPropPicker* picker; if ((picker = vtkAbstractPropPicker::SafeDownCast(rwi->GetPicker()))) { path = picker->GetPath(); } if (path != nullptr) { rwi->FlyToImage(this->CurrentRenderer, picker->GetPickPosition()); } this->AnimState = VTKIS_ANIM_OFF; break; } case 'r': case 'R': // Allow either shift/ctrl to trigger the usual 'r' binding // otherwise trigger reset window level event if (rwi->GetShiftKey() || rwi->GetControlKey()) { this->Superclass::OnChar(); } else if (this->HandleObservers && this->HasObserver(vtkCommand::ResetWindowLevelEvent)) { this->InvokeEvent(vtkCommand::ResetWindowLevelEvent, this); } else if (this->CurrentImageProperty) { vtkImageProperty* property = this->CurrentImageProperty; property->SetColorWindow(this->WindowLevelInitial[0]); property->SetColorLevel(this->WindowLevelInitial[1]); this->Interactor->Render(); } break; case 'x': case 'X': { this->SetImageOrientation(this->XViewRightVector, this->XViewUpVector); this->Interactor->Render(); } break; case 'y': case 'Y': { this->SetImageOrientation(this->YViewRightVector, this->YViewUpVector); this->Interactor->Render(); } break; case 'z': case 'Z': { this->SetImageOrientation(this->ZViewRightVector, this->ZViewUpVector); this->Interactor->Render(); } break; default: this->Superclass::OnChar(); break; } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::WindowLevel() { vtkRenderWindowInteractor* rwi = this->Interactor; this->WindowLevelCurrentPosition[0] = rwi->GetEventPosition()[0]; this->WindowLevelCurrentPosition[1] = rwi->GetEventPosition()[1]; if (this->HandleObservers && this->HasObserver(vtkCommand::WindowLevelEvent)) { this->InvokeEvent(vtkCommand::WindowLevelEvent, this); } else if (this->CurrentImageProperty) { const int* size = this->CurrentRenderer->GetSize(); double window = this->WindowLevelInitial[0]; double level = this->WindowLevelInitial[1]; // Compute normalized delta double dx = (this->WindowLevelCurrentPosition[0] - this->WindowLevelStartPosition[0]) * 4.0 / size[0]; double dy = (this->WindowLevelStartPosition[1] - this->WindowLevelCurrentPosition[1]) * 4.0 / size[1]; // Scale by current values if (fabs(window) > 0.01) { dx = dx * window; } else { dx = dx * (window < 0 ? -0.01 : 0.01); } if (fabs(level) > 0.01) { dy = dy * level; } else { dy = dy * (level < 0 ? -0.01 : 0.01); } // Abs so that direction does not flip if (window < 0.0) { dx = -1 * dx; } if (level < 0.0) { dy = -1 * dy; } // Compute new window level double newWindow = dx + window; double newLevel = level - dy; if (newWindow < 0.01) { newWindow = 0.01; } this->CurrentImageProperty->SetColorWindow(newWindow); this->CurrentImageProperty->SetColorLevel(newLevel); this->Interactor->Render(); } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::Pick() { this->InvokeEvent(vtkCommand::PickEvent, this); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::Slice() { if (this->CurrentRenderer == nullptr) { return; } vtkRenderWindowInteractor* rwi = this->Interactor; int dy = rwi->GetEventPosition()[1] - rwi->GetLastEventPosition()[1]; vtkCamera* camera = this->CurrentRenderer->GetActiveCamera(); double* range = camera->GetClippingRange(); double distance = camera->GetDistance(); // scale the interaction by the height of the viewport double viewportHeight = 0.0; if (camera->GetParallelProjection()) { viewportHeight = camera->GetParallelScale(); } else { double angle = vtkMath::RadiansFromDegrees(camera->GetViewAngle()); viewportHeight = 2.0 * distance * tan(0.5 * angle); } const int* size = this->CurrentRenderer->GetSize(); double delta = dy * viewportHeight / size[1]; distance += delta; // clamp the distance to the clipping range if (distance < range[0]) { distance = range[0] + viewportHeight * 1e-3; } if (distance > range[1]) { distance = range[1] - viewportHeight * 1e-3; } camera->SetDistance(distance); rwi->Render(); } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::SetImageOrientation( const double leftToRight[3], const double bottomToTop[3]) { if (this->CurrentRenderer) { // the cross product points out of the screen double vector[3]; vtkMath::Cross(leftToRight, bottomToTop, vector); double focus[3]; vtkCamera* camera = this->CurrentRenderer->GetActiveCamera(); camera->GetFocalPoint(focus); double d = camera->GetDistance(); camera->SetPosition( focus[0] + d * vector[0], focus[1] + d * vector[1], focus[2] + d * vector[2]); camera->SetFocalPoint(focus); camera->SetViewUp(bottomToTop); } } //------------------------------------------------------------------------------ // This is a way of dealing with images as if they were layers. // It looks through the renderer's list of props and sets the // interactor ivars from the Nth image that it finds. You can // also use negative numbers, i.e. -1 will return the last image, // -2 will return the second-to-last image, etc. void vtkInteractorStyleImage::SetCurrentImageNumber(int i) { this->CurrentImageNumber = i; if (!this->CurrentRenderer) { return; } vtkPropCollection* props = this->CurrentRenderer->GetViewProps(); vtkProp* prop = nullptr; vtkAssemblyPath* path; vtkImageSlice* imageProp = nullptr; vtkCollectionSimpleIterator pit; for (int k = 0; k < 2; k++) { int j = 0; for (props->InitTraversal(pit); (prop = props->GetNextProp(pit));) { bool foundImageProp = false; for (prop->InitPathTraversal(); (path = prop->GetNextPath());) { vtkProp* tryProp = path->GetLastNode()->GetViewProp(); imageProp = vtkImageSlice::SafeDownCast(tryProp); if (imageProp) { if (j == i && imageProp->GetPickable()) { foundImageProp = true; break; } imageProp = nullptr; j++; } } if (foundImageProp) { break; } } if (i < 0) { i += j; } } vtkImageProperty* property = nullptr; if (imageProp) { property = imageProp->GetProperty(); } if (property != this->CurrentImageProperty) { if (this->CurrentImageProperty) { this->CurrentImageProperty->Delete(); } this->CurrentImageProperty = property; if (this->CurrentImageProperty) { this->CurrentImageProperty->Register(this); } } } //------------------------------------------------------------------------------ void vtkInteractorStyleImage::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Window Level Current Position: (" << this->WindowLevelCurrentPosition[0] << ", " << this->WindowLevelCurrentPosition[1] << ")\n"; os << indent << "Window Level Start Position: (" << this->WindowLevelStartPosition[0] << ", " << this->WindowLevelStartPosition[1] << ")\n"; os << indent << "Interaction Mode: "; if (this->InteractionMode == VTKIS_IMAGE2D) { os << "Image2D\n"; } else if (this->InteractionMode == VTKIS_IMAGE3D) { os << "Image3D\n"; } else if (this->InteractionMode == VTKIS_IMAGE_SLICING) { os << "ImageSlicing\n"; } else { os << "Unknown\n"; } os << indent << "X View Right Vector: (" << this->XViewRightVector[0] << ", " << this->XViewRightVector[1] << ", " << this->XViewRightVector[2] << ")\n"; os << indent << "X View Up Vector: (" << this->XViewUpVector[0] << ", " << this->XViewUpVector[1] << ", " << this->XViewUpVector[2] << ")\n"; os << indent << "Y View Right Vector: (" << this->YViewRightVector[0] << ", " << this->YViewRightVector[1] << ", " << this->YViewRightVector[2] << ")\n"; os << indent << "Y View Up Vector: (" << this->YViewUpVector[0] << ", " << this->YViewUpVector[1] << ", " << this->YViewUpVector[2] << ")\n"; os << indent << "Z View Right Vector: (" << this->ZViewRightVector[0] << ", " << this->ZViewRightVector[1] << ", " << this->ZViewRightVector[2] << ")\n"; os << indent << "Z View Up Vector: (" << this->ZViewUpVector[0] << ", " << this->ZViewUpVector[1] << ", " << this->ZViewUpVector[2] << ")\n"; }