From 2a6b28a9f2add72d4fa8530d0f3a88d0425b409b Mon Sep 17 00:00:00 2001 From: David Borland Date: Fri, 4 Jun 2021 16:40:15 -0400 Subject: [PATCH] Feature/volume rendering (#110) * Add initial version of volume rendering * Add toggle for volume rendering * Turn on front face culling when volume rendering * Add initial window/level control for transfer function * Fix issue with initializing window level start position, use wireframe when volume rendering * Use otsu method to set initial volume rendering window level * Use raycast volume mapper * Add surface render mode, using front face culling with no lighting for volume rendering * Adjust maximum opacity * Use extract voi for current region when volume rendering * Mask volume by visible region extents * Show full volume if no current region * Set current region in pop temp history --- CMakeLists.txt | 4 +- Segmentor.qrc | 1 + icons/icon_volume_render.png | Bin 0 -> 1617 bytes icons/icon_volume_render.svg | 171 +++++++++++++++++++ interaction/InteractionCallbacks.cxx | 8 + interaction/InteractionCallbacks.h | 1 + interaction/vtkInteractorStyleVolume.cxx | 162 +++++++++++++++++- interaction/vtkInteractorStyleVolume.h | 20 ++- qt/MainWindow.cxx | 5 + qt/MainWindow.h | 1 + region/RegionSurface.cxx | 30 +++- region/RegionSurface.h | 8 + visualization/VisualizationContainer.cxx | 11 ++ visualization/VisualizationContainer.h | 1 + visualization/VolumeView.cxx | 206 ++++++++++++++++++++++- visualization/VolumeView.h | 37 +++- 16 files changed, 647 insertions(+), 19 deletions(-) create mode 100644 icons/icon_volume_render.png create mode 100644 icons/icon_volume_render.svg diff --git a/CMakeLists.txt b/CMakeLists.txt index f705672..f743c9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,8 +67,8 @@ set(UI_RESOURCES # Create executable qt5_wrap_ui(UISrcs ${UI_FILES} ) # CMAKE_AUTOMOC is ON so the MOC headers will be automatically wrapped. -add_executable(Segmentor MACOSX_BUNDLE WIN32 ${CXX_FILES} ${UISrcs} ${QT_WRAP} ${UI_RESOURCES}) -#add_executable(Segmentor MACOSX_BUNDLE ${CXX_FILES} ${UISrcs} ${QT_WRAP} ${UI_RESOURCES}) +#add_executable(Segmentor MACOSX_BUNDLE WIN32 ${CXX_FILES} ${UISrcs} ${QT_WRAP} ${UI_RESOURCES}) +add_executable(Segmentor MACOSX_BUNDLE ${CXX_FILES} ${UISrcs} ${QT_WRAP} ${UI_RESOURCES}) qt5_use_modules(Segmentor Core Gui) target_link_libraries(Segmentor ${VTK_LIBRARIES}) install(TARGETS Segmentor diff --git a/Segmentor.qrc b/Segmentor.qrc index 0fda75f..d00de0f 100644 --- a/Segmentor.qrc +++ b/Segmentor.qrc @@ -25,5 +25,6 @@ icons/icon_show_neighbor_regions.png icons/icon_filter_regions.png icons/icon_reset_view.png + icons/icon_volume_render.png \ No newline at end of file diff --git a/icons/icon_volume_render.png b/icons/icon_volume_render.png new file mode 100644 index 0000000000000000000000000000000000000000..9a63b39afd778e1bc32051268e41e7d5c090afb7 GIT binary patch literal 1617 zcmV-X2Cn&uP)F_nTSkDZf+Zeee6d_nUe1eeatMaDW3Gpd9F7kPZ(IH<+fWwYIkA zy1Tmz4?d|Pdt~RC{0t@Ny z@2`tSqZa}60fg?IGV`F1uxhl8-_@50sxklmT>RhJ=ZZJddn}Lln}!(ZUR_z+V9`L?>UE= z%a$55V}5?#>uH|6cJ116ncVgvB_y3r3jo*Ml5xY<))sEvx`pxaaZFE7V`*v0^~qV5 z1;a30IV{Uc%H*~W*_R#6{FT!di9|~L*4NiDH8q8NJ`ZNb#>NH=!@$JE1lHEpO8m3g z?1QDc7({e#aB#3RfCr?7#A2}@0sLKTYieq;`7kqPXJ;XVD4D;rvx7T#?qFkMqh!wA zyLa6}Mv3UOY<|*0y1TnA0ACl|VzHRbr%)(hdwbiHLkNMnxw#UdtE;PCW%`nAe$qnr z@MZBuC={}lS11&Ms+Mo4^Z7io*{scGSr&4+(gv8BpOa0@myqV>=AQtpIcyL9CL-v1 z!t=06aN7Jlr6Yn=c`I<@Iy1t)amlM@ESAJppz*1h@Pw%j%NJ&2MReh$f1! zBasM*{%zT6YilcV2q8+|C9m#42=RnWUcQAm+m&Jwd*iY^wrj6Nf{5B=_l`9- zU6-BkErgjni?5xf)Y8%dRaLRRz78P-6h%Q@T^$-58xab5AIZIzZ6SnvSLuMS(qd); zaIDzo_D<-!jzl7XBS(%Pkw~DmwY98>Xf#@~A3aD=QT~+4&6kj&p`phB*dqEp9Xg?D z8ag^UN|y0GnE8DES1BH!agSAYkw;BU4LUnJJvUKi{;sdDukv~hpF)`VDd(MQXlRhy zJDg~4Zbm#FFI#eoqI@dL=Sv6?9d+6cA3luD%}uXf$p%$b(bUw0wzf8zR_1qI*Go>N zUhrF59Zo|{O%1xbx{%Fgv9`7b(=@@%5JEuHG=#%pgu~&oFSHAGgb)|^np7$H71H8n zBqGFOv66#U6?nV1xA&%>34VnH`}X4lM0B~Qr{{{_dA^okB9hvi0wTxEZ}jx^cz&8K z2frKQH$j3(@cv*~*6~y-H4>D=S8181`AH}gdX0#l4I0pa8_fJsDwX=SS|Z=SA&!iU z=!HU|AHeGfcAlBR?$?;llgZ>C0p`0R@UOZ90|On3qMQS862LP69>MO{^nK(2EC84V zFiAw?%zPu6OwL!6uowQ*e;BxW^=hN0X=+_v-PZZ@=PRGE4sd`2{BQUNKO85>hz>Vh P00000NkvXXu0mjf + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/interaction/InteractionCallbacks.cxx b/interaction/InteractionCallbacks.cxx index 6f32777..42a7a8c 100644 --- a/interaction/InteractionCallbacks.cxx +++ b/interaction/InteractionCallbacks.cxx @@ -13,6 +13,7 @@ #include "VolumeView.h" #include "SliceView.h" #include "vtkInteractorStyleSlice.h" +#include "vtkInteractorStyleVolume.h" bool InteractionCallbacks::firstCameraCallback = true; @@ -293,4 +294,11 @@ void InteractionCallbacks::WindowLevel(vtkObject* caller, unsigned long eventId, VisualizationContainer* vis = static_cast(clientData); vis->SetWindowLevel(style->GetWindow(), style->GetLevel()); +} + +void InteractionCallbacks::VolumeWindowLevel(vtkObject* caller, unsigned long eventId, void* clientData, void *callData) { + vtkInteractorStyleVolume* style = static_cast(caller); + VisualizationContainer* vis = static_cast(clientData); + + vis->SetVolumeWindowLevel(style->GetWindow(), style->GetLevel()); } \ No newline at end of file diff --git a/interaction/InteractionCallbacks.h b/interaction/InteractionCallbacks.h index 2051114..9f0dc5e 100644 --- a/interaction/InteractionCallbacks.h +++ b/interaction/InteractionCallbacks.h @@ -34,6 +34,7 @@ class InteractionCallbacks { static void MouseMove(vtkObject* caller, unsigned long eventId, void* clientData, void *callData); static void WindowLevel(vtkObject* caller, unsigned long eventId, void* clientData, void *callData); + static void VolumeWindowLevel(vtkObject* caller, unsigned long eventId, void* clientData, void *callData); private: static bool firstCameraCallback; diff --git a/interaction/vtkInteractorStyleVolume.cxx b/interaction/vtkInteractorStyleVolume.cxx index 5b197ea..40a63b6 100644 --- a/interaction/vtkInteractorStyleVolume.cxx +++ b/interaction/vtkInteractorStyleVolume.cxx @@ -17,7 +17,18 @@ vtkStandardNewMacro(vtkInteractorStyleVolume); vtkInteractorStyleVolume::vtkInteractorStyleVolume() { this->MouseMoved = false; + this->Mode = NavigationMode; + + this->WindowLevelStartPosition[0] = 0; + this->WindowLevelStartPosition[1] = 0; + + this->WindowLevelCurrentPosition[0] = 0; + this->WindowLevelCurrentPosition[1] = 0; + + this->WindowLevelCurrent[0] = this->WindowLevelInitial[0] = 1.0; + this->WindowLevelCurrent[1] = this->WindowLevelInitial[1] = 0.5; + this->Picker = vtkSmartPointer::New(); } @@ -183,6 +194,127 @@ void vtkInteractorStyleVolume::EndVisible() this->StopState(); } +//---------------------------------------------------------------------------- +void vtkInteractorStyleVolume::StartWindowLevel() +{ + if (this->State != VTKIS_NONE) + { + return; + } + this->StartState(VTKIS_WINDOW_LEVEL_VOLUME); + + if (this->HandleObservers) + { + this->InvokeEvent(StartWindowLevelEvent, nullptr); + } + + this->WindowLevelInitial[0] = this->WindowLevelCurrent[0]; + this->WindowLevelInitial[1] = this->WindowLevelCurrent[1]; +} + +//---------------------------------------------------------------------------- +void vtkInteractorStyleVolume::EndWindowLevel() +{ + if (this->State != VTKIS_WINDOW_LEVEL_VOLUME) + { + return; + } + if (this->HandleObservers) + { + this->InvokeEvent(EndWindowLevelEvent, nullptr); + } + this->StopState(); +} + +//---------------------------------------------------------------------------- +void vtkInteractorStyleVolume::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); + } + + 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->WindowLevelCurrent[0] = newWindow; + this->WindowLevelCurrent[1] = newLevel; + + this->Interactor->Render(); +} + +//---------------------------------------------------------------------------- +void vtkInteractorStyleVolume::SetWindowLevel(double window, double level) { + this->WindowLevelCurrent[0] = this->WindowLevelInitial[0] = window; + this->WindowLevelCurrent[1] = this->WindowLevelInitial[1] = level; +} + + +//---------------------------------------------------------------------------- +double vtkInteractorStyleVolume::GetWindow() { + return this->WindowLevelCurrent[0]; +} + +//---------------------------------------------------------------------------- +double vtkInteractorStyleVolume::GetLevel() { + return this->WindowLevelCurrent[1]; +} + //---------------------------------------------------------------------------- void vtkInteractorStyleVolume::OnMouseMove() { @@ -205,6 +337,11 @@ void vtkInteractorStyleVolume::OnMouseMove() this->FindPokedRenderer(x, y); this->Slice(); break; + + case VTKIS_WINDOW_LEVEL_VOLUME: + this->WindowLevel(); + this->InvokeEvent(WindowLevelEvent, nullptr); + break; } // Call parent to handle all other states and perform additional work @@ -246,11 +383,21 @@ void vtkInteractorStyleVolume::OnLeftButtonDown() else if (this->Mode == VisibleMode) { this->StartVisible(); - } + } else { - // Rotate - this->StartRotate(); + // If shift is held down, start window level + if (this->Interactor->GetShiftKey()) { + this->WindowLevelStartPosition[0] = x; + this->WindowLevelStartPosition[1] = y; + this->StartWindowLevel(); + } + + // Otherwise rotate + else + { + this->StartRotate(); + } } } @@ -291,6 +438,10 @@ void vtkInteractorStyleVolume::OnLeftButtonUp() case VTKIS_VISIBLE_VOLUME: this->EndVisible(); break; + + case VTKIS_WINDOW_LEVEL_VOLUME: + this->EndWindowLevel(); + break; } // Call parent to handle all other states and perform additional work @@ -365,6 +516,11 @@ void vtkInteractorStyleVolume::OnRightButtonDown() { this->StartSlice(); } + // Transfer function if shift is held down + else if (this->Interactor->GetShiftKey()) + { + this->StartWindowLevel(); + } else { if (this->Mode == EditMode) diff --git a/interaction/vtkInteractorStyleVolume.h b/interaction/vtkInteractorStyleVolume.h index b0f07db..8144ff9 100644 --- a/interaction/vtkInteractorStyleVolume.h +++ b/interaction/vtkInteractorStyleVolume.h @@ -18,6 +18,7 @@ class vtkCellPicker; #define VTKIS_SLICE_VOLUME 2051 #define VTKIS_MERGE_VOLUME 2052 #define VTKIS_VISIBLE_VOLUME 2053 +#define VTKIS_WINDOW_LEVEL_VOLUME 2054 class vtkInteractorStyleVolume : public vtkInteractorStyleTrackballCamera { public: @@ -53,17 +54,27 @@ class vtkInteractorStyleVolume : public vtkInteractorStyleTrackballCamera { virtual void EndMerge(); virtual void StartVisible(); virtual void EndVisible(); + virtual void StartWindowLevel(); + virtual void EndWindowLevel(); + + virtual void WindowLevel(); + virtual void SetWindowLevel(double window, double level); + virtual double GetWindow(); + virtual double GetLevel(); enum VolumeEventIds { SelectLabelEvent = vtkCommand::UserEvent + 1, StartPaintEvent, PaintEvent, - EndPaintEvent, + EndPaintEvent, StartEraseEvent, EraseEvent, EndEraseEvent, MergeEvent, - VisibleEvent + VisibleEvent, + StartWindowLevelEvent, + WindowLevelEvent, + EndWindowLevelEvent }; protected: @@ -76,6 +87,11 @@ class vtkInteractorStyleVolume : public vtkInteractorStyleTrackballCamera { vtkSmartPointer Picker; + int WindowLevelStartPosition[2]; + int WindowLevelCurrentPosition[2]; + double WindowLevelInitial[2]; + double WindowLevelCurrent[2]; + void SetOrientation(const double leftToRight[3], const double viewUp[3]); private: diff --git a/qt/MainWindow.cxx b/qt/MainWindow.cxx index 7b31f45..c5d898f 100644 --- a/qt/MainWindow.cxx +++ b/qt/MainWindow.cxx @@ -746,6 +746,10 @@ void MainWindow::on_actionSmoothSurfaces(bool checked) { visualizationContainer->GetVolumeView()->SetSmoothSurfaces(checked); } +void MainWindow::on_actionVolumeRendering(bool checked) { + visualizationContainer->GetVolumeView()->SetVolumeRendering(checked); +} + void MainWindow::on_actionShowPlane(bool checked) { visualizationContainer->GetVolumeView()->SetShowPlane(checked); } @@ -1079,6 +1083,7 @@ void MainWindow::createToolBar() { toolBar->addWidget(createLabel("3D")); toolBar->addAction(createActionIcon(":/icons/icon_smooth_normals.png", "Smooth normals (n)", "n", visualizationContainer->GetVolumeView()->GetSmoothShading(), &MainWindow::on_actionSmoothNormals)); toolBar->addAction(createActionIcon(":/icons/icon_smooth_surface.png", "Smooth surfaces (s)", "s", visualizationContainer->GetVolumeView()->GetSmoothSurfaces(), &MainWindow::on_actionSmoothSurfaces)); + toolBar->addAction(createActionIcon(":/icons/icon_volume_render.png", "Volume rendering (])", "]", visualizationContainer->GetVolumeView()->GetVolumeRendering(), &MainWindow::on_actionVolumeRendering)); toolBar->addAction(createActionIcon(":/icons/icon_plane.png", "Show plane (o)", "o", visualizationContainer->GetVolumeView()->GetShowPlane(), &MainWindow::on_actionShowPlane)); toolBar->addSeparator(); toolBar->addWidget(createLabel("Filter")); diff --git a/qt/MainWindow.h b/qt/MainWindow.h index b79010a..29b8cd1 100644 --- a/qt/MainWindow.h +++ b/qt/MainWindow.h @@ -103,6 +103,7 @@ public slots: virtual void on_actionToggleAutoRescale(bool checked); virtual void on_actionSmoothNormals(bool checked); virtual void on_actionSmoothSurfaces(bool checked); + virtual void on_actionVolumeRendering(bool checked); virtual void on_actionShowPlane(bool checked); virtual void on_actionClearRegionVisibilities(); virtual void on_actionShowPlaneRegions(); diff --git a/region/RegionSurface.cxx b/region/RegionSurface.cxx index 19bf45b..9b803f2 100644 --- a/region/RegionSurface.cxx +++ b/region/RegionSurface.cxx @@ -45,14 +45,17 @@ RegionSurface::RegionSurface(Region* inputRegion, double color[3]) { mapper = vtkSmartPointer::New(); mapper->ScalarVisibilityOff(); - + actor = vtkSmartPointer::New(); actor->SetMapper(mapper); + actor->GetProperty()->SetColor(color); actor->GetProperty()->SetDiffuse(1.0); actor->GetProperty()->SetAmbient(0.1); actor->GetProperty()->SetSpecular(0.0); + SetRenderMode(Normal); + UpdatePipeline(); } @@ -78,6 +81,31 @@ void RegionSurface::SetSmoothShading(bool smooth) { UpdatePipeline(); } +void RegionSurface::SetRenderMode(RenderMode mode) { + switch (mode) { + case Normal: + actor->GetProperty()->SetRepresentationToSurface(); + actor->GetProperty()->FrontfaceCullingOff(); + actor->GetProperty()->LightingOn(); + + break; + + case Wireframe: + actor->GetProperty()->SetRepresentationToWireframe(); + actor->GetProperty()->FrontfaceCullingOff(); + actor->GetProperty()->LightingOn(); + + break; + + case CullFrontFace: + actor->GetProperty()->SetRepresentationToSurface(); + actor->GetProperty()->FrontfaceCullingOn(); + actor->GetProperty()->LightingOff(); + + break; + } +} + bool RegionSurface::IntersectsPlane(double p[3], double n[3]) { if (!IntersectsBoundingBox(p, n)) return false; diff --git a/region/RegionSurface.h b/region/RegionSurface.h index a04ca1b..730b294 100644 --- a/region/RegionSurface.h +++ b/region/RegionSurface.h @@ -21,6 +21,14 @@ class RegionSurface { void SetSmoothSurface(bool smooth); void SetSmoothShading(bool smooth); + enum RenderMode { + Normal, + Wireframe, + CullFrontFace + }; + + void SetRenderMode(RenderMode mode); + bool IntersectsPlane(double p[3], double n[3]); protected: diff --git a/visualization/VisualizationContainer.cxx b/visualization/VisualizationContainer.cxx index c232b61..170e94e 100644 --- a/visualization/VisualizationContainer.cxx +++ b/visualization/VisualizationContainer.cxx @@ -191,6 +191,11 @@ VisualizationContainer::VisualizationContainer(vtkRenderWindowInteractor* volume windowLevelCallback->SetCallback(InteractionCallbacks::WindowLevel); windowLevelCallback->SetClientData(this); sliceView->GetInteractorStyle()->AddObserver(vtkCommand::WindowLevelEvent, windowLevelCallback); + + vtkSmartPointer volumeWindowLevelCallback = vtkSmartPointer::New(); + volumeWindowLevelCallback->SetCallback(InteractionCallbacks::VolumeWindowLevel); + volumeWindowLevelCallback->SetClientData(this); + volumeView->GetInteractorStyle()->AddObserver(vtkCommand::WindowLevelEvent, volumeWindowLevelCallback); } VisualizationContainer::~VisualizationContainer() { @@ -1975,6 +1980,10 @@ void VisualizationContainer::SetWindowLevel(double window, double level) { qtWindow->setWindowLevel(window, level); } +void VisualizationContainer::SetVolumeWindowLevel(double window, double level) { + volumeView->SetWindowLevel(window, level); +} + void VisualizationContainer::SetVisibleOpacity(double opacity) { volumeView->SetVisibleOpacity(opacity, filterRegions); } @@ -2089,7 +2098,9 @@ void VisualizationContainer::PopTempHistory() { volumeView->SetRegions(labels, regions); sliceView->SetSegmentationData(labels, regions); + SetCurrentRegion(currentRegion); qtWindow->updateRegions(regions); + Render(); } diff --git a/visualization/VisualizationContainer.h b/visualization/VisualizationContainer.h index a9375a3..d70e8cc 100644 --- a/visualization/VisualizationContainer.h +++ b/visualization/VisualizationContainer.h @@ -114,6 +114,7 @@ class VisualizationContainer { void ToggleCurrentRegionDone(); void SetWindowLevel(double window, double level); + void SetVolumeWindowLevel(double window, double level); void SliceUp(); void SliceDown(); diff --git a/visualization/VolumeView.cxx b/visualization/VolumeView.cxx index fe77648..bc79399 100644 --- a/visualization/VolumeView.cxx +++ b/visualization/VolumeView.cxx @@ -3,12 +3,19 @@ #include #include #include +#include #include #include +#include +#include +#include #include +#include #include #include #include +#include +#include #include #include #include @@ -19,6 +26,8 @@ #include #include #include +#include +#include #include "vtkInteractorStyleVolume.h" @@ -29,6 +38,9 @@ #include "RegionSurface.h" #include "RegionHighlight3D.h" #include "RegionCollection.h" +#include "SegmentorMath.h" + +#include double rescale(double value, double min, double max) { return min + (max - min) * value; @@ -41,8 +53,12 @@ void VolumeView::cameraChange(vtkObject* caller, unsigned long eventId, void* cl } VolumeView::VolumeView(vtkRenderWindowInteractor* interactor) { + data = nullptr; + labels = nullptr; + smoothSurfaces = false; smoothShading = false; + volumeRendering = false; regions = nullptr; currentRegion = nullptr; @@ -87,6 +103,9 @@ VolumeView::VolumeView(vtkRenderWindowInteractor* interactor) { // Interaction mode label CreateInteractionModeLabel(); + // Volume rendering + CreateVolumeRenderer(); + // Lighting double lightPosition[3] = { 0, 0.5, 1 }; double lightFocalPoint[3] = { 0, 0, 0 }; @@ -103,6 +122,9 @@ VolumeView::~VolumeView() { } void VolumeView::Reset() { + data = nullptr; + labels = nullptr; + SetCurrentRegion(nullptr); HighlightRegion(nullptr); @@ -111,10 +133,16 @@ void VolumeView::Reset() { plane->VisibilityOff(); corners->VisibilityOff(); interactionModeLabel->VisibilityOff(); + + volume->VisibilityOff(); } -void VolumeView::SetImageData(vtkImageData* data) { +void VolumeView::SetImageData(vtkImageData* imageData) { + data = imageData; + brush->UpdateData(data); + + UpdateVolumeRenderer(); } void VolumeView::Enable(bool enable) { @@ -147,7 +175,8 @@ void VolumeView::UpdateVoxelSize(vtkImageData* data) { UpdateAxes(data); } -void VolumeView::SetRegions(vtkImageData* data, RegionCollection* newRegions) { +void VolumeView::SetRegions(vtkImageData* imageLabels, RegionCollection* newRegions) { + labels = imageLabels; regions = newRegions; // Reset @@ -187,6 +216,7 @@ void VolumeView::AddRegion(Region* region) { surface->SetSmoothSurface(smoothSurfaces); surface->SetSmoothShading(smoothShading); + surface->SetRenderMode(volumeRendering ? RegionSurface::CullFrontFace : RegionSurface::Normal); highlight->GetActor()->VisibilityOff(); highlight->SetCamera(renderer->GetActiveCamera()); @@ -297,6 +327,26 @@ void VolumeView::ToggleSmoothShading() { SetSmoothShading(!smoothShading); } +bool VolumeView::GetVolumeRendering() { + return volumeRendering; +} + +void VolumeView::SetVolumeRendering(bool useVolumeRendering) { + volumeRendering = useVolumeRendering; + + volume->SetVisibility(volumeRendering); + + for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { + regions->Get(it)->GetSurface()->SetRenderMode(volumeRendering ? RegionSurface::CullFrontFace : RegionSurface::Normal); + } + + Render(); +} + +void VolumeView::ToggleVolumeRendering() { + SetVolumeRendering(!volumeRendering); +} + void VolumeView::UpdatePlane() { vtkCamera* cam = renderer->GetActiveCamera(); @@ -331,16 +381,18 @@ void VolumeView::SetVisibleOpacity(double opacity, bool apply) { } void VolumeView::UpdateVisibleOpacity(bool apply) { - if (!regions) return; - - for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { - Region* region = regions->Get(it); - RegionSurface* surface = region->GetSurface(); + if (regions) { + for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { + Region* region = regions->Get(it); + RegionSurface* surface = region->GetSurface(); - double o = !apply || region == currentRegion ? 1 : visibleOpacity; + double o = !apply || region == currentRegion ? 1 : visibleOpacity; - surface->GetActor()->GetProperty()->SetOpacity(o); + surface->GetActor()->GetProperty()->SetOpacity(o); + } } + + UpdateVolumeMask(apply); } void VolumeView::SetBrushRadius(int radius) { @@ -348,6 +400,81 @@ void VolumeView::SetBrushRadius(int radius) { brush->GetActor()->SetVisibility(radius > 1); } +void VolumeView::SetWindowLevel(double window, double level) { + UpdateVolumeRenderingTransferFunctions(level - window * 0.5, level + window * 0.5); +} + +void VolumeView::UpdateVolumeMask(bool filter) { + if (regions && currentRegion && filter) { + vtkSmartPointer mask = volumeCopy->GetOutput(); + + const int* extent = mask->GetExtent(); + + for (int i = extent[0]; i <= extent[1]; i++) { + for (int j = extent[2]; j <= extent[3]; j++) { + for (int k = extent[4]; k <= extent[5]; k++) { + unsigned char* p = static_cast(mask->GetScalarPointer(i, j, k)); + *p = 0; + } + } + } + + for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { + Region* region = regions->Get(it); + const int* extent = region->GetExtent(); + + if (region == currentRegion || region->GetVisible()) { + for (int i = extent[0]; i <= extent[1]; i++) { + for (int j = extent[2]; j <= extent[3]; j++) { + for (int k = extent[4]; k <= extent[5]; k++) { + // Current label + unsigned char* p = static_cast(mask->GetScalarPointer(i, j, k)); + *p = 1; + } + } + } + } + } + + mask->Modified(); + + volumeMask->SetMaskInputData(mask); + + volumeMapper->SetInputConnection(volumeMask->GetOutputPort()); + } + else { + volumeMapper->SetInputDataObject(data); + } +} + +void VolumeView::UpdateVolumeRenderingTransferFunctions(double x1, double x2) { + // Opacity + volumeOpacity->RemoveAllPoints(); + volumeOpacity->AddPoint(x1, 0.0); + volumeOpacity->AddPoint(x2, 0.5); + + // Colors + // Paraview diverging + const int numColors = 3; + double colors[numColors][3] = { + { 59, 76, 192 }, + { 221, 221, 221 }, + { 180, 4, 38 } + }; + + for (int i = 0; i < numColors; i++) { + for (int j = 0; j < 3; j++) { + colors[i][j] /= 255.0; + } + } + + volumeColor->RemoveAllPoints(); + for (int i = 0; i < numColors; i++) { + double x = x1 + (double)i / (numColors - 1) * (x2 - x1); + volumeColor->AddRGBPoint(x, colors[i][0], colors[i][1], colors[i][2]); + } +} + void VolumeView::Render() { renderer->GetRenderWindow()->Render(); } @@ -371,6 +498,67 @@ void VolumeView::CreateInteractionModeLabel() { renderer->AddActor2D(interactionModeLabel); } +void VolumeView::CreateVolumeRenderer() { + volumeCopy = vtkSmartPointer::New(); + volumeCopy->SetOutputScalarTypeToUnsignedChar(); + + volumeMask = vtkSmartPointer::New(); + volumeMask->SetMaskInputData(volumeCopy->GetOutput()); + + volumeMapper = vtkSmartPointer::New(); + volumeMapper->SetBlendModeToComposite(); + volumeMapper->AutoAdjustSampleDistancesOn(); + volumeMapper->SetSampleDistance(0.1); + volumeMapper->SetInteractiveSampleDistance(0.1); + volumeMapper->SetImageSampleDistance(0.5); + volumeMapper->SetMaximumImageSampleDistance(2); + + volumeOpacity = vtkSmartPointer::New(); + + volumeColor = vtkSmartPointer::New(); + + vtkSmartPointer volumeProperty = vtkSmartPointer::New(); + volumeProperty->ShadeOff(); + volumeProperty->SetInterpolationTypeToLinear(); + volumeProperty->SetScalarOpacity(volumeOpacity); + volumeProperty->SetColor(volumeColor); + + volume = vtkSmartPointer::New(); + volume->SetMapper(volumeMapper); + volume->SetProperty(volumeProperty); + volume->VisibilityOff(); + volume->PickableOff(); + + renderer->AddVolume(volume); +} + +void VolumeView::UpdateVolumeRenderer() { + if (!data) return; + + // Start with full volume + volumeCopy->SetInputDataObject(data); + volumeCopy->Update(); + + volumeMask->SetInputDataObject(data); + + volumeMapper->SetInputDataObject(data); + volume->SetVisibility(volumeRendering); + + // Initialize window level + SegmentorMath::OtsuValues otsu = SegmentorMath::OtsuThreshold(data); + + double minValue = otsu.backgroundMean; + double maxValue = otsu.foregroundMean; + + double range = maxValue - minValue;; + + double window = range; + double level = minValue + range / 2; + + SetWindowLevel(window, level); + style->SetWindowLevel(window, level); +} + void VolumeView::CreatePlane() { planeSource = vtkSmartPointer::New(); planeSource->SetXResolution(100); diff --git a/visualization/VolumeView.h b/visualization/VolumeView.h index 7f63921..c6ce198 100644 --- a/visualization/VolumeView.h +++ b/visualization/VolumeView.h @@ -9,14 +9,23 @@ class vtkActor; class vtkBox; +class vtkColorTransferFunction; class vtkCubeAxesActor; +class vtkImageCast; class vtkImageData; +class vtkLookupTable; +class vtkImageMapToColors; +class vtkImageMask; +class vtkFixedPointVolumeRayCastMapper; +class vtkPiecewiseFunction; class vtkPlaneSource; class vtkObject; class vtkOutlineCornerFilter; +class vtkPassThrough; class vtkRenderer; class vtkRenderWindowInteractor; class vtkTextActor; +class vtkVolume; class vtkInteractorStyleVolume; @@ -31,8 +40,8 @@ class VolumeView { VolumeView(vtkRenderWindowInteractor* interactor); ~VolumeView(); - void SetImageData(vtkImageData* data); - void SetRegions(vtkImageData* data, RegionCollection* newRegions); + void SetImageData(vtkImageData* imageData); + void SetRegions(vtkImageData* imageLabels, RegionCollection* newRegions); void AddRegion(Region* region); void Reset(); @@ -59,6 +68,10 @@ class VolumeView { void SetSmoothShading(bool smooth); void ToggleSmoothShading(); + bool GetVolumeRendering(); + void SetVolumeRendering(bool useVolumeRendering); + void ToggleVolumeRendering(); + bool GetShowPlane(); void SetShowPlane(bool show); void ToggleShowPlane(); @@ -70,14 +83,23 @@ class VolumeView { void SetBrushRadius(int radius); + void SetWindowLevel(double window, double level); + + void UpdateVolumeMask(bool filter); + void Render(); vtkRenderer* GetRenderer(); vtkInteractorStyleVolume* GetInteractorStyle(); protected: + // Data + vtkSmartPointer data; + vtkSmartPointer labels; + bool smoothSurfaces; bool smoothShading; + bool volumeRendering; Region* currentRegion; Region* highlightRegion; @@ -116,6 +138,17 @@ class VolumeView { // Interaction mode label vtkSmartPointer interactionModeLabel; void CreateInteractionModeLabel(); + + // Volume rendering + vtkSmartPointer volumeCopy; + vtkSmartPointer volumeMask; + vtkSmartPointer volumeMapper; + vtkSmartPointer volume; + vtkSmartPointer volumeOpacity; + vtkSmartPointer volumeColor; + void CreateVolumeRenderer(); + void UpdateVolumeRenderer(); + void UpdateVolumeRenderingTransferFunctions(double x1, double x2); double visibleOpacity;