diff --git a/src/src/Interaction/VolumeInteractorStyle.cpp b/src/src/Interaction/VolumeInteractorStyle.cpp index 76fd30e..efde306 100644 --- a/src/src/Interaction/VolumeInteractorStyle.cpp +++ b/src/src/Interaction/VolumeInteractorStyle.cpp @@ -10,6 +10,11 @@ #include #include #include +#include +#include +#include +#include +#include vtkStandardNewMacro(VolumeInteractorStyle) @@ -22,8 +27,9 @@ VolumeInteractorStyle::~VolumeInteractorStyle() { } void VolumeInteractorStyle::OnLeftButtonDown() { - this->FindPokedRenderer(this->Interactor->GetEventPosition()[0], - this->Interactor->GetEventPosition()[1]); + int x = this->Interactor->GetEventPosition()[0]; + int y = this->Interactor->GetEventPosition()[1]; + this->FindPokedRenderer(x,y); if (this->CurrentRenderer == nullptr) { return; @@ -47,6 +53,201 @@ void VolumeInteractorStyle::OnLeftButtonDown() { this->StartDolly(); break; } + case VOLUME_WINDOW: { + this->WindowLevelStartPosition[0] = x; + this->WindowLevelStartPosition[1] = y; + this->StartWindowLevel(); + break; + } + } +} + +void VolumeInteractorStyle::OnLeftButtonUp() { + switch (this->State) { + case VTKIS_WINDOW_LEVEL: + this->EndWindowLevel(); + if (this->Interactor) { + this->ReleaseFocus(); + } + break; + } + this->Superclass::OnLeftButtonUp(); +} + +void VolumeInteractorStyle::StartWindowLevel() { + if (this->State != VTKIS_NONE) + { + return; + } + this->StartState(VTKIS_WINDOW_LEVEL); + + if (this->HandleObservers && + this->HasObserver(vtkCommand::StartWindowLevelEvent)) + { + this->InvokeEvent(vtkCommand::StartWindowLevelEvent, this); + } + GetCurrentVolumeProperty(); + if(VolumeProperty){ + auto opacity = VolumeProperty->GetScalarOpacity(); + if (opacity) { + double range[2] = {0.0, 0.0}; + opacity->GetRange(range); + this->WindowLevelInitial[0] = range[1] - range[0]; + this->WindowLevelInitial[1] = (range[1] + range[0]) / 2; + } + } +} + +void VolumeInteractorStyle::EndWindowLevel() { + if (this->State != VTKIS_WINDOW_LEVEL) + { + return; + } + if (this->HandleObservers) + { + this->InvokeEvent(vtkCommand::EndWindowLevelEvent, this); + } + this->StopState(); +} + +void VolumeInteractorStyle::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; + } + this->Superclass::OnMouseMove(); +} + +void VolumeInteractorStyle::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(VolumeProperty){ + auto opacity = VolumeProperty->GetScalarOpacity(); + if (opacity) { + double range[2] = {0.0, 0.0}; + opacity->GetRange(range); + 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]) ; + double dy = (this->WindowLevelStartPosition[1] - + this->WindowLevelCurrentPosition[1]); + + // Scale by current values + + if ( fabs( window ) > 300.0) + { + dx = dx/size[0] * window; + } +// if ( fabs( level ) > 1200.0 ) +// { +// dy = dy/size[1] * level; +// } + + // 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; + } + + //apply new window + double newMin = newLevel - newWindow/2.0; + double newMax = newLevel + newWindow/2.0; + opacity->RemoveAllPoints(); + opacity->AddPoint(newMin,0.0); + opacity->AddPoint(newMax,1.0); + this->Interactor->Render(); + } + } +} + +void VolumeInteractorStyle::GetCurrentVolumeProperty() { + if (!this->CurrentRenderer) + { + return; + } + + vtkPropCollection *props = this->CurrentRenderer->GetViewProps(); + vtkProp *prop = nullptr; + vtkAssemblyPath *path; + vtkVolume *volumeProp = 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(); + volumeProp = vtkVolume::SafeDownCast(tryProp); + if (volumeProp) + { + if (volumeProp->GetPickable()) + { + foundImageProp = true; + break; + } + volumeProp = nullptr; + j++; + } + } + if (foundImageProp) + { + break; + } + } + } + vtkVolumeProperty* property = nullptr; + if (volumeProp) + { + property = volumeProp->GetProperty(); + } + if (property != this->VolumeProperty){ + if (this->VolumeProperty){ + this->VolumeProperty->Delete(); + } + this->VolumeProperty = property; + if (VolumeProperty){ + this->VolumeProperty->Register(this); + } } } diff --git a/src/src/Interaction/VolumeInteractorStyle.h b/src/src/Interaction/VolumeInteractorStyle.h index 3610bf4..a444254 100644 --- a/src/src/Interaction/VolumeInteractorStyle.h +++ b/src/src/Interaction/VolumeInteractorStyle.h @@ -11,6 +11,11 @@ #define VOLUME_ROTATE2D 1 #define VOLUME_ZOOM 2 #define VOLUME_PAN 3 +#define VOLUME_WINDOW 4 + +#define VTKIS_WINDOW_LEVEL 1024 + +class vtkVolumeProperty; class VolumeInteractorStyle:public vtkInteractorStyleTrackballCamera { public: @@ -18,18 +23,22 @@ public: vtkTypeMacro(VolumeInteractorStyle, vtkInteractorStyleTrackballCamera); - vtkSetClampMacro(InteractionMode, int, VOLUME_ROTATE3D, VOLUME_PAN); + vtkSetClampMacro(InteractionMode, int, VOLUME_ROTATE3D, VOLUME_WINDOW); vtkGetMacro(InteractionMode, int); + virtual void StartWindowLevel(); + virtual void EndWindowLevel(); void OnLeftButtonDown() override; -// void OnLeftButtonUp() override; + void OnLeftButtonUp() override; // void OnRightButtonDown() override; // void OnRightButtonUp() override; -// void OnMouseMove() override; + void OnMouseMove() override; void OnMouseWheelForward() override{}; void OnMouseWheelBackward() override{}; + void WindowLevel(); + protected: VolumeInteractorStyle(); @@ -40,7 +49,15 @@ private: void operator=(const VolumeInteractorStyle &) = delete; + void GetCurrentVolumeProperty(); + int InteractionMode; + + vtkVolumeProperty* VolumeProperty = nullptr; + + double WindowLevelInitial[2] = {0.0, 0.0}; + double WindowLevelStartPosition[2] = {0.0, 0.0}; + double WindowLevelCurrentPosition[2] = {0.0, 0.0}; }; diff --git a/src/src/Rendering/Viewer/VolumeRenderingViewer.cpp b/src/src/Rendering/Viewer/VolumeRenderingViewer.cpp index e64f6b3..d20b1d8 100644 --- a/src/src/Rendering/Viewer/VolumeRenderingViewer.cpp +++ b/src/src/Rendering/Viewer/VolumeRenderingViewer.cpp @@ -137,8 +137,12 @@ void VolumeRenderingViewer::InstallPipeline() { if (this->Interactor) { if (!this->InteractorStyle) { auto style = VolumeInteractorStyle::New(); - style->SetInteractionMode(0); +// style->SetInteractionMode(1024); this->InteractorStyle = style; + this->InteractorStyle->AddObserver(vtkCommand::StartWindowLevelEvent,this, + &VolumeRenderingViewer::DecreaseMaximumImageSampleDistance); + this->InteractorStyle->AddObserver(vtkCommand::EndWindowLevelEvent,this, + &VolumeRenderingViewer::ResetMaximumImageSampleDistance); } this->Interactor->SetInteractorStyle(this->InteractorStyle); @@ -148,7 +152,7 @@ void VolumeRenderingViewer::InstallPipeline() { if (this->Renderer && this->VolumeActor) { this->Renderer->AddVolume(this->VolumeActor); this->Renderer->AddViewProp(annotation); - Renderer->AddObserver(vtkCommand::EndEvent,this,&VolumeRenderingViewer::printFrameRate); + Renderer->AddObserver(vtkCommand::EndEvent,this, &VolumeRenderingViewer::renderAnnotation); this->Renderer->SetBackground(0.0, 0.0, 0.0); } //TODO: annotation for orientation @@ -263,13 +267,61 @@ vtkImageData *VolumeRenderingViewer::GetInput() { return this->VolumeMapper->GetInput(); } -void VolumeRenderingViewer::printFrameRate() { +void VolumeRenderingViewer::renderAnnotation() { + // set FPS double timeInSeconds = Renderer->GetLastRenderTimeInSeconds(); double fps = 1.0 / timeInSeconds; char buff[200]={0}; - sprintf(buff,"FPS:%3.0f", fps); + double *range = VolumeActor->GetProperty()->GetScalarOpacity()->GetRange(); + double wl = (range[0]+range[1])/2.0; + double ww = (range[1] - range[0]); + sprintf(buff,"FPS:%3.0f\r\nWW:%.0f WL:%.0f", fps,ww,wl); annotation->SetText(0,buff ); + + auto camera = Renderer->GetActiveCamera(); + double dop[4] = {.0, .0, .0, 1.}; + camera->GetDirectionOfProjection(dop); + double viewUp[4] = {.0, .0, .0, 1.}; + camera->GetViewUp(viewUp); + double viewRight[4] = {.0, .0, .0, 1.}; + vtkMath::Cross(dop,viewUp,viewRight); + OrientationMatrix->Invert(); + OrientationMatrix->MultiplyPoint(viewUp,viewUp); + OrientationMatrix->MultiplyPoint(viewRight,viewRight); + OrientationMatrix->Invert(); + double viewDown[4] = {-viewUp[0], -viewUp[1], -viewUp[2], 1.}; + double viewLeft[4] = {-viewRight[0], -viewRight[1], -viewRight[2], 1.}; + std::string str; + GetDirectionString(viewUp, str); + annotation->SetText(7,str.c_str() ); + GetDirectionString(viewRight, str); + annotation->SetText(5,str.c_str() ); + GetDirectionString(viewDown, str); + annotation->SetText(4,str.c_str() ); + GetDirectionString(viewLeft, str); + annotation->SetText(6,str.c_str() ); +} + +void VolumeRenderingViewer::GetDirectionString(const double *directionVector, std::string &str) const { + str.clear(); + char dV [3] = {' ', ' ', ' '}; + if (fabs(directionVector[0]) > 0.5){ + dV[0] = directionVector[0] > 0 ? 'L' : 'R'; + } + if (fabs(directionVector[1])>0.5){ + dV[1] = directionVector[1]>0?'P':'A'; + } + if (fabs(directionVector[2])>0.5){ + dV[2] =directionVector[2]>0?'H':'F'; + } + if (fabs(directionVector[2]) > fabs(directionVector[1])) std::swap(dV[1], dV[2]); + if (fabs(directionVector[1]) > fabs(directionVector[0])) std::swap(dV[0], dV[1]); + for (int i = 0; i < 3; ++i) { + if(dV[i]!=' '){ + str.append(1, dV[i]); + } + } } void VolumeRenderingViewer::SetInteractorStyleMode(int mode) { @@ -371,9 +423,23 @@ void VolumeRenderingViewer::ResetZoomFitWindow() { camera->Dolly(0.99/halfDisX); } else{ - //use 1.97 to avoid border out of screen + //use 0.99 to avoid border out of screen camera->Dolly(0.99/halfDisY); } Renderer->ResetCameraClippingRange(); } + +void VolumeRenderingViewer::DecreaseMaximumImageSampleDistance() { + auto mapper = vtkFixedPointVolumeRayCastMapper::SafeDownCast(VolumeMapper); + if (mapper){ + mapper->SetMaximumImageSampleDistance(1.0); + } +} + +void VolumeRenderingViewer::ResetMaximumImageSampleDistance() { + auto mapper = vtkFixedPointVolumeRayCastMapper::SafeDownCast(VolumeMapper); + if (mapper){ + mapper->SetMaximumImageSampleDistance(4.0); + } +} diff --git a/src/src/Rendering/Viewer/VolumeRenderingViewer.h b/src/src/Rendering/Viewer/VolumeRenderingViewer.h index 9a1a566..5fbb8e2 100644 --- a/src/src/Rendering/Viewer/VolumeRenderingViewer.h +++ b/src/src/Rendering/Viewer/VolumeRenderingViewer.h @@ -99,12 +99,15 @@ protected: virtual void UnInstallPipeline(); - void printFrameRate(); + void renderAnnotation(); private: VolumeRenderingViewer(const VolumeRenderingViewer &) = delete; void operator=(const VolumeRenderingViewer &) = delete; + void DecreaseMaximumImageSampleDistance(); + void ResetMaximumImageSampleDistance(); + vtkRenderWindow *RenderWindow; vtkRenderer *Renderer; vtkVolume * VolumeActor; @@ -115,6 +118,8 @@ private: vtkNew OrientationMatrix; bool gpuMode = false; bool firstRender = true; + + void GetDirectionString(const double *directionVector, std::string &str) const; };