From 9a8cd6679626b656790c0fdc60892de531c3e80b Mon Sep 17 00:00:00 2001 From: David Borland Date: Thu, 14 Oct 2021 13:57:24 -0400 Subject: [PATCH] Stage (#137) * Feature/default directory (#119) * Add count label * Use single default directory * Add count label (#121) * Add check for done status when ovewriting (#122) * Feature/feedback enhancements (#123) * Work on updating feedback types * Implement basic comment interface * Display comment in 2D view * Fix bug with setting done from region table * Add verified column, auto stretch comment column * Use delegate for line edit in feedback table * Finish feedback table updates * Add verified color, working on logic for done and verified * Finish logic for done and verified * Add done/verified logic to region table * Add search box * Disable changing done status via shortcut if verified, set verified color when loading * Remove console on windows * Bugfix/done sort (#126) * Stage (#124) * Feature/default directory (#119) * Add count label * Use single default directory * Add count label (#121) * Add check for done status when ovewriting (#122) * Feature/feedback enhancements (#123) * Work on updating feedback types * Implement basic comment interface * Display comment in 2D view * Fix bug with setting done from region table * Add verified column, auto stretch comment column * Use delegate for line edit in feedback table * Finish feedback table updates * Add verified color, working on logic for done and verified * Finish logic for done and verified * Add done/verified logic to region table * Add search box * Disable changing done status via shortcut if verified, set verified color when loading * Remove console on windows * Set data in addCheckWidget * Fix sorting issue with feedback table * Fix issue with merge * Bump version number * Feature/dot annotation (#134) * Stage (#127) * Feature/default directory (#119) * Add count label * Use single default directory * Add count label (#121) * Add check for done status when ovewriting (#122) * Feature/feedback enhancements (#123) * Work on updating feedback types * Implement basic comment interface * Display comment in 2D view * Fix bug with setting done from region table * Add verified column, auto stretch comment column * Use delegate for line edit in feedback table * Finish feedback table updates * Add verified color, working on logic for done and verified * Finish logic for done and verified * Add done/verified logic to region table * Add search box * Disable changing done status via shortcut if verified, set verified color when loading * Remove console on windows * Bugfix/done sort (#126) * Stage (#124) * Feature/default directory (#119) * Add count label * Use single default directory * Add count label (#121) * Add check for done status when ovewriting (#122) * Feature/feedback enhancements (#123) * Work on updating feedback types * Implement basic comment interface * Display comment in 2D view * Fix bug with setting done from region table * Add verified column, auto stretch comment column * Use delegate for line edit in feedback table * Finish feedback table updates * Add verified color, working on logic for done and verified * Finish logic for done and verified * Add done/verified logic to region table * Add search box * Disable changing done status via shortcut if verified, set verified color when loading * Remove console on windows * Set data in addCheckWidget * Fix sorting issue with feedback table * Fix issue with merge * Bump version number * Add region center sphere visualization * Add button for dot annotation mode * Add dot annotation mode * Add 2D dot visualization * Work on applying dot annotation to current full annotations * Fix issues with center at edges, automatically show/hide dots based on mode * Use triangle for out of plane 2D centers * Use cone for triangle * Enable picking 3D dot annotations * Change dot annotation icon * Set default verified and comment in RegionInfo * Add shortcut to open/close feedback dialog * Apply dot annotation in extract regions * Only apply dot annotation to regions when saving in dot annotation mode * Add verified shortcut to feedback table * Feature/dot mode guard (#135) * Use period for verified shortcut * Add enable dot annotation checkbox to settings * Add warning when switching to dot annotation mode * Push history for dot mode * Add separate action to apply dot annotation * Use WIN32 --- CMakeLists.txt | 2 +- Segmentor.qrc | 1 + icons/icon_dot.png | Bin 0 -> 1095 bytes icons/icon_dot.svg | 4 + interaction/InteractionCallbacks.cxx | 15 ++++ interaction/InteractionCallbacks.h | 1 + interaction/InteractionEnums.h | 3 +- interaction/vtkInteractorStyleSlice.cxx | 32 +++++++ interaction/vtkInteractorStyleSlice.h | 6 +- interaction/vtkInteractorStyleVolume.cxx | 2 +- qt/FeedbackDialog.cxx | 9 +- qt/MainWindow.cxx | 63 ++++++++++++++ qt/MainWindow.h | 5 ++ qt/MainWindow.ui | 12 +++ qt/SettingsDialog.cxx | 5 ++ qt/SettingsDialog.h | 5 ++ qt/SettingsDialog.ui | 22 ++++- region/Region.cxx | 50 ++++++++++- region/Region.h | 11 +++ region/RegionCenter2D.cxx | 77 +++++++++++++++++ region/RegionCenter2D.h | 29 +++++++ region/RegionCenter3D.cxx | 47 +++++++++++ region/RegionCenter3D.h | 28 +++++++ region/RegionInfo.cxx | 2 +- visualization/SliceView.cxx | 16 +++- visualization/VisualizationContainer.cxx | 101 ++++++++++++++++++++++- visualization/VisualizationContainer.h | 4 + visualization/VolumeView.cxx | 3 + 28 files changed, 543 insertions(+), 12 deletions(-) create mode 100644 icons/icon_dot.png create mode 100644 icons/icon_dot.svg create mode 100644 region/RegionCenter2D.cxx create mode 100644 region/RegionCenter2D.h create mode 100644 region/RegionCenter3D.cxx create mode 100644 region/RegionCenter3D.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 188d751..ad5a38a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ if(POLICY CMP0020) cmake_policy(SET CMP0020 NEW) endif() -set(SEGMENTOR_VERSION 0.4.3) +set(SEGMENTOR_VERSION 0.4.4) project(Segmentor VERSION ${SEGMENTOR_VERSION}) diff --git a/Segmentor.qrc b/Segmentor.qrc index d00de0f..a9ebe0e 100644 --- a/Segmentor.qrc +++ b/Segmentor.qrc @@ -26,5 +26,6 @@ icons/icon_filter_regions.png icons/icon_reset_view.png icons/icon_volume_render.png + icons/icon_dot.png \ No newline at end of file diff --git a/icons/icon_dot.png b/icons/icon_dot.png new file mode 100644 index 0000000000000000000000000000000000000000..1336c9aa454fed748e8a6423422aef1352e3ed0e GIT binary patch literal 1095 zcmV-N1i1T&P)t<88FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H11H?&0K~!jg?b^?e6h#!k@y~2n;>F-5u!GV4LHvp9E_xD|BmaZ? zclaYQs0R!xtHvLYs4>R7fk>2aQzPi=Q4=%~2?9YOdU1`31Y}tc)x??Vp}T8#dKQxY zCY?;B>%Drfs;lbNtD&Kxp`oFnp`oE+Ahd?i#t7!{DDKDY7|Y)MiSsytZ*Uw}hhS4J zY{P#1ft8BS@7O1F&`rZEjtC=F)zSF5io=*0vh7KXVlV#HzoYSU882bo5dEmcW_+u+ z(N}O3Phva9h0Ynxes9N9IF{AzrDGB8p?Oon7Mzkd@*li{O$oZjv5?8e`c7hcP)7I5 zX1$&L2zRVP?*tC?(0kGvj`XPfXS|GO2c*A+U760#Homq-b9%45vyau#&g1e<@2R1k zLS}>&w9|+|jchNTi`u$`tu?lntl(rLqtPcJgUS z{XN2bt;!T$EU6POR`Ut^lX+Wr?>Yhbj><)I z?M$NXs&#yiSK7G95JlY$QV>zz4#ulsm-pK6Q81|g`J z%F1Vj#jR-0USm?2ubs!kIs8z`>Q!=BcTXQs@2Azm$5ESyYHTk@uN2R&8ryq8ei{rl z#Y4Ce=rm$kNVpA}C*+fOyK88tlu`LDVp$mJL44jVJTLF;QEPbh(iW2A@ydHp;wdI# zQhv^^2#Z$AO-3zD>+SSKyejRcppDlu*iLf-g->W-er zn`%4|4C7BNEx~~uxEFW&{}8`~Uxfu}5kCr!H#9UfG&D3cG&J1k{0A_>z0^7T%rO7} N002ovPDHLkV1g+v5m^8L literal 0 HcmV?d00001 diff --git a/icons/icon_dot.svg b/icons/icon_dot.svg new file mode 100644 index 0000000..43a1150 --- /dev/null +++ b/icons/icon_dot.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/interaction/InteractionCallbacks.cxx b/interaction/InteractionCallbacks.cxx index 42a7a8c..6e969f7 100644 --- a/interaction/InteractionCallbacks.cxx +++ b/interaction/InteractionCallbacks.cxx @@ -246,6 +246,21 @@ void InteractionCallbacks::Visible(vtkObject* caller, unsigned long eventId, voi } } +void InteractionCallbacks::Dot(vtkObject* caller, unsigned long eventId, void* clientData, void *callData) { + vtkRenderWindowInteractor* rwi = static_cast(caller)->GetInteractor(); + VisualizationContainer* vis = static_cast(clientData); + + // Pick at the mouse location provided by the interactor + int pick = Pick(rwi); + + if (pick) { + // Get the position for the pick event + double p[3]; + PickPosition(p); + vis->SetDotAnnotation(p); + } +} + void InteractionCallbacks::MouseMove(vtkObject* caller, unsigned long eventId, void* clientData, void *callData) { vtkRenderWindowInteractor* rwi = static_cast(caller); VisualizationContainer* vis = static_cast(clientData); diff --git a/interaction/InteractionCallbacks.h b/interaction/InteractionCallbacks.h index 9f0dc5e..894b9d0 100644 --- a/interaction/InteractionCallbacks.h +++ b/interaction/InteractionCallbacks.h @@ -30,6 +30,7 @@ class InteractionCallbacks { static void Grow(vtkObject* caller, unsigned long eventId, void* clientData, void *callData); static void Done(vtkObject* caller, unsigned long eventId, void* clientData, void *callData); static void Visible(vtkObject* caller, unsigned long eventId, void* clientData, void *callData); + static void Dot(vtkObject* caller, unsigned long eventId, void* clientData, void *callData); static void MouseMove(vtkObject* caller, unsigned long eventId, void* clientData, void *callData); diff --git a/interaction/InteractionEnums.h b/interaction/InteractionEnums.h index 70e0d03..ebbcafa 100644 --- a/interaction/InteractionEnums.h +++ b/interaction/InteractionEnums.h @@ -9,7 +9,8 @@ enum InteractionMode { MergeMode, SplitMode, GrowMode, - VisibleMode + VisibleMode, + DotMode }; enum FilterMode { diff --git a/interaction/vtkInteractorStyleSlice.cxx b/interaction/vtkInteractorStyleSlice.cxx index ff035f3..8977a59 100644 --- a/interaction/vtkInteractorStyleSlice.cxx +++ b/interaction/vtkInteractorStyleSlice.cxx @@ -242,6 +242,30 @@ void vtkInteractorStyleSlice::EndVisible() this->StopState(); } +//---------------------------------------------------------------------------- +void vtkInteractorStyleSlice::StartDot() +{ + if (this->State != VTKIS_NONE) + { + return; + } + this->StartState(VTKIS_DOT_SLICE); +} + +//---------------------------------------------------------------------------- +void vtkInteractorStyleSlice::EndDot() +{ + if (this->State != VTKIS_DOT_SLICE) + { + return; + } + if (this->HandleObservers) + { + this->InvokeEvent(DotEvent, nullptr); + } + this->StopState(); +} + //---------------------------------------------------------------------------- void vtkInteractorStyleSlice::WindowLevel() { @@ -414,6 +438,10 @@ void vtkInteractorStyleSlice::OnLeftButtonDown() { this->StartVisible(); } + else if (this->Mode == DotMode) + { + this->StartDot(); + } else { // Rotate @@ -474,6 +502,10 @@ void vtkInteractorStyleSlice::OnLeftButtonUp() { case VTKIS_VISIBLE_SLICE: this->EndVisible(); break; + + case VTKIS_DOT_SLICE: + this->EndDot(); + break; } // Call parent to handle all other states and perform additional work diff --git a/interaction/vtkInteractorStyleSlice.h b/interaction/vtkInteractorStyleSlice.h index afbc91f..46ef593 100644 --- a/interaction/vtkInteractorStyleSlice.h +++ b/interaction/vtkInteractorStyleSlice.h @@ -20,6 +20,7 @@ class vtkCellPicker; #define VTKIS_MERGE_SLICE 2053 #define VTKIS_GROW_SLICE 2054 #define VTKIS_VISIBLE_SLICE 2055 +#define VTKIS_DOT_SLICE 2056 class vtkInteractorStyleSlice : public vtkInteractorStyleImage { public: @@ -56,6 +57,8 @@ class vtkInteractorStyleSlice : public vtkInteractorStyleImage { virtual void EndGrow(); virtual void StartVisible(); virtual void EndVisible(); + virtual void StartDot(); + virtual void EndDot(); virtual void WindowLevel() override; double GetWindow(); @@ -79,7 +82,8 @@ class vtkInteractorStyleSlice : public vtkInteractorStyleImage { AddEvent, MergeEvent, GrowEvent, - VisibleEvent + VisibleEvent, + DotEvent }; protected: diff --git a/interaction/vtkInteractorStyleVolume.cxx b/interaction/vtkInteractorStyleVolume.cxx index 40a63b6..93ae071 100644 --- a/interaction/vtkInteractorStyleVolume.cxx +++ b/interaction/vtkInteractorStyleVolume.cxx @@ -383,7 +383,7 @@ void vtkInteractorStyleVolume::OnLeftButtonDown() else if (this->Mode == VisibleMode) { this->StartVisible(); - } + } else { // If shift is held down, start window level diff --git a/qt/FeedbackDialog.cxx b/qt/FeedbackDialog.cxx index 0c1f4a6..58c8789 100644 --- a/qt/FeedbackDialog.cxx +++ b/qt/FeedbackDialog.cxx @@ -39,10 +39,13 @@ FeedbackDialog::FeedbackDialog(QWidget* parent, VisualizationContainer* visualiz QObject::connect(table, &FeedbackTable::highlightRegion, this, &FeedbackDialog::on_highlightRegion); QObject::connect(table, &FeedbackTable::countChanged, this, &FeedbackDialog::on_countChanged); - // Shortcut - QShortcut* shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_V), this); + // Shortcuts + QShortcut* shortcut = new QShortcut(QKeySequence(Qt::Key_Period), this); QObject::connect(shortcut, &QShortcut::activated, this, &FeedbackDialog::on_verifiedShortcut); + QShortcut* closeShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F), this); + QObject::connect(closeShortcut, &QShortcut::activated, this, &FeedbackDialog::close); + updateRegions(); } @@ -110,5 +113,5 @@ void FeedbackDialog::on_countChanged(int count) { } void FeedbackDialog::on_verifiedShortcut() { - //printf("DLKJF"); + visualizationContainer->ToggleCurrentRegionVerified(); } \ No newline at end of file diff --git a/qt/MainWindow.cxx b/qt/MainWindow.cxx index d13eeea..b2b1bca 100644 --- a/qt/MainWindow.cxx +++ b/qt/MainWindow.cxx @@ -88,6 +88,7 @@ MainWindow::MainWindow() { // Settings dialog settingsDialog = new SettingsDialog(this, visualizationContainer); + QObject::connect(settingsDialog, &SettingsDialog::enableDotAnnotationChanged, this, &MainWindow::on_enableDotAnnotationChanged); // Feedback dialog feedbackDialog = new FeedbackDialog(this, visualizationContainer); @@ -588,6 +589,32 @@ void MainWindow::on_actionSegment_Volume_triggered() { } } +void MainWindow::on_actionApply_Dot_Annotation_triggered() { + QMessageBox message; + message.setIcon(QMessageBox::Warning); + message.setText("Apply dot annotation."); + message.setInformativeText( + "This operation will replace each region with a single voxel at the region center.\n\n" + "Do you wish to continue?" + ); + message.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + message.setDefaultButton(QMessageBox::Save); + int ret = message.exec(); + + switch (ret) { + case QMessageBox::Yes: + visualizationContainer->ApplyDotAnnotation(); + break; + + case QMessageBox::No: + break; + + default: + // Should never be reached + break; + } +} + void MainWindow::on_actionExit_triggered() { close(); } @@ -691,6 +718,10 @@ void MainWindow::on_actionVisible() { visualizationContainer->SetInteractionMode(VisibleMode); } +void MainWindow::on_actionDot() { + visualizationContainer->SetInteractionMode(DotMode); +} + void MainWindow::on_actionUpdate() { visualizationContainer->RelabelCurrentRegion(); } @@ -807,6 +838,22 @@ void MainWindow::on_brushRadiusSpinBox_valueChanged(int value) { visualizationContainer->SetBrushRadius(value); } +void MainWindow::on_enableDotAnnotationChanged(bool enable) { + // Enable apply dot annotation + actionApply_Dot_Annotation->setEnabled(enable); + + // Enable dot mode + QList actions = modeBarWidget->findChild()->actions(); + + for (int i = 0; i < actions.length(); i++) { + QAction* action = actions[i]; + if (action->objectName() == "dotModeAction") { + action->setEnabled(enable); + break; + } + } +} + void MainWindow::on_toggleView() { if (qvtkWidgetLeft->isVisible()) { qvtkWidgetLeft->setVisible(false); @@ -1059,6 +1106,11 @@ void MainWindow::createModeBar() { toolBar->addAction(createActionIcon(":/icons/icon_merge.png", "Merge with current region (m)", "m", interactionModeGroup, currentMode == MergeMode, &MainWindow::on_actionMerge)); toolBar->addAction(createActionIcon(":/icons/icon_grow.png", "Grow / shrink region (g)", "g", interactionModeGroup, currentMode == GrowMode, &MainWindow::on_actionGrow)); toolBar->addAction(createActionIcon(":/icons/icon_visible.png", "Toggle region visibility (v)", "v", interactionModeGroup, currentMode == VisibleMode, &MainWindow::on_actionVisible)); + + QAction* dotAction = createActionIcon(":/icons/icon_dot.png", "Dot annotation mode (Ctrl + d)", QKeySequence(Qt::CTRL + Qt::Key_D), interactionModeGroup, currentMode == DotMode, &MainWindow::on_actionDot); + dotAction->setObjectName("dotModeAction"); + dotAction->setEnabled(false); + toolBar->addAction(dotAction); toolBar->addSeparator(); toolBar->addWidget(createLabel("Actions", 0, 0, 5, 5)); @@ -1163,6 +1215,17 @@ QAction* MainWindow::createActionIcon(const QString& fileName, const QString& te return action; } +QAction* MainWindow::createActionIcon(const QString& fileName, const QString& text, const QKeySequence& shortcut, QActionGroup* group, bool checked, void (MainWindow::*slot)()) { + QAction* action = new QAction(QIcon(fileName), text, group); + action->setShortcut(QKeySequence(shortcut)); + action->setCheckable(true); + action->setChecked(checked); + + QObject::connect(action, &QAction::triggered, this, slot); + + return action; +} + QLabel* MainWindow::createLabel(const QString& text, int topMargin, int bottomMargin, int leftMargin, int rightMargin) { QString style = QStringLiteral("color:#999;margin-top:%1px;margin-bottom:%2px;margin-left:%3px;margin-right:%4px") .arg(topMargin).arg(bottomMargin).arg(leftMargin).arg(rightMargin); diff --git a/qt/MainWindow.h b/qt/MainWindow.h index eea47cb..58ef7c2 100644 --- a/qt/MainWindow.h +++ b/qt/MainWindow.h @@ -62,6 +62,7 @@ public slots: virtual void on_actionRedo_triggered(); virtual void on_actionSegment_Volume_triggered(); + virtual void on_actionApply_Dot_Annotation_triggered(); virtual void on_actionExit_triggered(); @@ -90,6 +91,7 @@ public slots: virtual void on_actionGrow(); virtual void on_actionDone(); virtual void on_actionVisible(); + virtual void on_actionDot(); // Action events virtual void on_actionUpdate(); @@ -142,6 +144,8 @@ public slots: virtual void on_brushRadiusDown(); virtual void on_brushRadiusUp(); + virtual void on_enableDotAnnotationChanged(bool enable); + signals: void windowLevelChanged(double window, double value); @@ -184,6 +188,7 @@ public slots: QAction* createActionIcon(const QString& fileName, const QString& text, const QKeySequence& shortcut, void (MainWindow::*slot)()); QAction* createActionIcon(const QString& fileName, const QString& text, const QString& shortcut, bool checked, void (MainWindow::*slot)(bool)); QAction* createActionIcon(const QString& fileName, const QString& text, const QString& shortcut, QActionGroup* group, bool checked, void (MainWindow::*slot)()); + QAction* createActionIcon(const QString& fileName, const QString& text, const QKeySequence& shortcut, QActionGroup* group, bool checked, void (MainWindow::*slot)()); QLabel* createLabel(const QString& text, int topMargin = 10, int bottomMargin = 5, int leftMargin = 0, int rightMargin = 0); InteractionMode navEditMode; diff --git a/qt/MainWindow.ui b/qt/MainWindow.ui index 99d3cca..d2445f8 100644 --- a/qt/MainWindow.ui +++ b/qt/MainWindow.ui @@ -398,6 +398,7 @@ Analyze + @@ -611,6 +612,17 @@ Show Feedback + + Ctrl+F + + + + + false + + + Apply Dot Annotation + diff --git a/qt/SettingsDialog.cxx b/qt/SettingsDialog.cxx index 1b2aee8..2cdb72c 100644 --- a/qt/SettingsDialog.cxx +++ b/qt/SettingsDialog.cxx @@ -114,4 +114,9 @@ void SettingsDialog::on_gradientOpacityCheckBox_stateChanged(int state) { void SettingsDialog::on_autoAdjustSamplingCheckBox_stateChanged(int state) { visualizationContainer->GetVolumeView()->SetVolumeRenderingAutoAdjustSampling(state != 0); +} + + +void SettingsDialog::on_enableDotAnnotationCheckBox_stateChanged(int state) { + emit enableDotAnnotationChanged(state != 0); } \ No newline at end of file diff --git a/qt/SettingsDialog.h b/qt/SettingsDialog.h index 396fa18..93aba9d 100644 --- a/qt/SettingsDialog.h +++ b/qt/SettingsDialog.h @@ -40,6 +40,11 @@ public slots: virtual void on_gradientOpacityCheckBox_stateChanged(int state); virtual void on_autoAdjustSamplingCheckBox_stateChanged(int state); + virtual void on_enableDotAnnotationCheckBox_stateChanged(int state); + +signals: + void enableDotAnnotationChanged(bool enable); + protected: VisualizationContainer* visualizationContainer; }; diff --git a/qt/SettingsDialog.ui b/qt/SettingsDialog.ui index 3fa258e..3971fe4 100644 --- a/qt/SettingsDialog.ui +++ b/qt/SettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 314 - 367 + 425 @@ -330,6 +330,26 @@ + + + + Dot annotation + + + + + + + + Enable dot annotation + + + + + + + + diff --git a/region/Region.cxx b/region/Region.cxx index 1529a1b..60dd8a7 100644 --- a/region/Region.cxx +++ b/region/Region.cxx @@ -25,6 +25,8 @@ #include "RegionOutline.h" #include "RegionVoxelOutlines.h" #include "RegionHighlight3D.h" +#include "RegionCenter3D.h" +#include "RegionCenter2D.h" Region::Region(unsigned short regionLabel, double regionColor[3], vtkImageData* inputData, const int* regionExtent) { visible = false; @@ -72,6 +74,8 @@ Region::Region(unsigned short regionLabel, double regionColor[3], vtkImageData* outline = new RegionOutline(this, color); voxelOutlines = new RegionVoxelOutlines(this, color); highlight3D = new RegionHighlight3D(this, color); + center3D = new RegionCenter3D(this, color); + center2D = new RegionCenter2D(this, color); #ifdef SHOW_REGION_BOX // Outline for testing bounding box @@ -118,6 +122,8 @@ Region::Region(const RegionInfo& info, vtkImageData* inputData) { outline = new RegionOutline(this, color); voxelOutlines = new RegionVoxelOutlines(this, color); highlight3D = new RegionHighlight3D(this, color); + center3D = new RegionCenter3D(this, color); + center2D = new RegionCenter2D(this, color); } Region::~Region() { @@ -137,6 +143,8 @@ Region::~Region() { delete outline; delete voxelOutlines; delete highlight3D; + delete center3D; + delete center2D; } vtkAlgorithmOutput* Region::GetOutput() { @@ -198,6 +206,14 @@ RegionHighlight3D* Region::GetHighlight3D() { return highlight3D; } +RegionCenter3D* Region::GetCenter3D() { + return center3D; +} + +RegionCenter2D* Region::GetCenter2D() { + return center2D; +} + vtkSmartPointer Region::GetText() { return text; } @@ -425,6 +441,8 @@ void Region::UpdateColor() { outline->GetActor()->GetProperty()->SetColor(currentColor); voxelOutlines->GetActor()->GetProperty()->SetColor(currentColor); highlight3D->GetActor()->GetProperty()->SetColor(currentColor); + center3D->GetActor()->GetProperty()->SetColor(currentColor); + center2D->GetActor()->GetProperty()->SetColor(currentColor); } void Region::ShowText(bool show) { @@ -456,6 +474,11 @@ void Region::ShowText(bool show) { } } +void Region::ShowCenter(bool show) { + center2D->GetActor()->SetVisibility(show); + center3D->GetActor()->SetVisibility(show); +} + unsigned short Region::GetLabel() { return label; } @@ -507,7 +530,10 @@ void Region::GetExtent(int outExtent[6]) { } double* Region::GetCenter() { - return voi->GetOutput()->GetCenter(); + center[0] = (extent[0] + extent[1]) / 2.0; + center[1] = (extent[2] + extent[3]) / 2.0; + center[2] = (extent[4] + extent[5]) / 2.0; + return center; } double Region::GetLength() { @@ -612,6 +638,28 @@ void Region::SetComment(const std::string& commentString) { text->SetInput(LabelString().c_str()); } +void Region::ApplyDot() { + double* c = GetCenter(); + int x = (int)c[0]; + int y = (int)c[1]; + int z = (int)c[2]; + + ClearLabels(); + + unsigned short* p = static_cast(data->GetScalarPointer(x, y, z)); + + *p = label; + + data->Modified(); + + int ext[6] = { x, x, y, y, z, z, }; + SetExtent(ext); + voi->Update(); + + center3D->Update(); + center2D->Update(center2D->GetActor()->GetPosition()[2]); +} + void Region::ClearLabels() { for (int i = extent[0]; i <= extent[1]; i++) { for (int j = extent[2]; j <= extent[3]; j++) { diff --git a/region/Region.h b/region/Region.h index 198d151..910014f 100644 --- a/region/Region.h +++ b/region/Region.h @@ -23,6 +23,8 @@ class RegionSurface; class RegionOutline; class RegionVoxelOutlines; class RegionHighlight3D; +class RegionCenter3D; +class RegionCenter2D; class Feedback; class Region { @@ -40,6 +42,8 @@ class Region { RegionOutline* GetOutline(); RegionVoxelOutlines* GetVoxelOutlines(); RegionHighlight3D* GetHighlight3D(); + RegionCenter3D* GetCenter3D(); + RegionCenter2D* GetCenter2D(); vtkSmartPointer GetText(); vtkSmartPointer GetZSlice(int z); @@ -70,6 +74,8 @@ class Region { void ShowText(bool show); + void ShowCenter(bool show); + unsigned short GetLabel(); int GetNumVoxels(); int GetNumVoxels(int slice); @@ -89,10 +95,13 @@ class Region { const std::string& GetComment(); void SetComment(const std::string& commentString); + void ApplyDot(); + protected: unsigned short label; double color[3]; int extent[6]; + double center[3]; bool visible; bool modified; @@ -108,6 +117,8 @@ class Region { RegionOutline* outline; RegionVoxelOutlines* voxelOutlines; RegionHighlight3D* highlight3D; + RegionCenter3D* center3D; + RegionCenter2D* center2D; vtkSmartPointer text; #ifdef SHOW_REGION_BOX diff --git a/region/RegionCenter2D.cxx b/region/RegionCenter2D.cxx new file mode 100644 index 0000000..9b09133 --- /dev/null +++ b/region/RegionCenter2D.cxx @@ -0,0 +1,77 @@ +#include "RegionCenter2D.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Region.h" + +RegionCenter2D::RegionCenter2D(Region* inputRegion, double color[3]) { + region = inputRegion; + + vtkSmartPointer cone = vtkSmartPointer::New(); + cone->SetResolution(16); + + vtkSmartPointer sphere = vtkSmartPointer::New(); + sphere->SetThetaResolution(16); + sphere->SetPhiResolution(16); + + coneMapper = vtkSmartPointer::New(); + coneMapper->ScalarVisibilityOff(); + coneMapper->SetInputConnection(cone->GetOutputPort()); + + sphereMapper = vtkSmartPointer::New(); + sphereMapper->ScalarVisibilityOff(); + sphereMapper->SetInputConnection(sphere->GetOutputPort()); + + actor = vtkSmartPointer::New(); + actor->GetProperty()->SetColor(color); + actor->GetProperty()->SetDiffuse(0.0); + actor->GetProperty()->SetAmbient(1.0); + actor->GetProperty()->SetSpecular(0.0); + actor->PickableOff(); + actor->VisibilityOff(); + + Update(region->GetCenter()[2]); +} + +RegionCenter2D::~RegionCenter2D() { + while (actor->GetNumberOfConsumers() > 0) { + vtkRenderer::SafeDownCast(actor->GetConsumer(0))->RemoveActor(actor); + } +} + +vtkSmartPointer RegionCenter2D::GetActor() { + return actor; +} + +void RegionCenter2D::Update(double z) { + double r = 1.25; + + double numSlices = 4.0; + + double* c = region->GetCenter(); + + double d = fabs(z - c[2]); + + double s = (numSlices - d) / numSlices; + if (s < 0) s = 0; + + actor->SetPosition(c[0], c[1], z); + actor->SetScale(r * s); + + double epsilon = 0.9; + if (d < epsilon) { + // Sphere + actor->SetMapper(sphereMapper); + } + else { + // Cone + actor->SetMapper(coneMapper); + actor->SetOrientation(0, 0, z > c[2] ? 90 : -90); + } +} \ No newline at end of file diff --git a/region/RegionCenter2D.h b/region/RegionCenter2D.h new file mode 100644 index 0000000..b2f2fa4 --- /dev/null +++ b/region/RegionCenter2D.h @@ -0,0 +1,29 @@ +#ifndef RegionCenter2D_H +#define RegionCenter2D_H + +#include "vtkSmartPointer.h" + +class vtkActor; +class vtkCamera; +class vtkMapper; + +class Region; + +class RegionCenter2D { +public: + RegionCenter2D(Region* inputRegion, double color[3]); + ~RegionCenter2D(); + + vtkSmartPointer GetActor(); + + void Update(double z); + +protected: + Region* region; + + vtkSmartPointer coneMapper; + vtkSmartPointer sphereMapper; + vtkSmartPointer actor; +}; + +#endif diff --git a/region/RegionCenter3D.cxx b/region/RegionCenter3D.cxx new file mode 100644 index 0000000..43faf61 --- /dev/null +++ b/region/RegionCenter3D.cxx @@ -0,0 +1,47 @@ +#include "RegionCenter3D.h" + +#include +#include +#include +#include +#include +#include + +#include "Region.h" + +RegionCenter3D::RegionCenter3D(Region* inputRegion, double color[3]) { + region = inputRegion; + + sphere = vtkSmartPointer::New(); + sphere->SetThetaResolution(16); + sphere->SetPhiResolution(16); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->ScalarVisibilityOff(); + mapper->SetInputConnection(sphere->GetOutputPort()); + + actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->SetScale(1.25); + actor->GetProperty()->SetColor(color); + actor->GetProperty()->SetDiffuse(1.0); + actor->GetProperty()->SetAmbient(0.0); + actor->GetProperty()->SetSpecular(0.0); + actor->VisibilityOff(); + + Update(); +} + +RegionCenter3D::~RegionCenter3D() { + while (actor->GetNumberOfConsumers() > 0) { + vtkRenderer::SafeDownCast(actor->GetConsumer(0))->RemoveActor(actor); + } +} + +vtkSmartPointer RegionCenter3D::GetActor() { + return actor; +} + +void RegionCenter3D::Update() { + actor->SetPosition(region->GetCenter()); +} \ No newline at end of file diff --git a/region/RegionCenter3D.h b/region/RegionCenter3D.h new file mode 100644 index 0000000..3a3c292 --- /dev/null +++ b/region/RegionCenter3D.h @@ -0,0 +1,28 @@ +#ifndef RegionCenter3D_H +#define RegionCenter3D_H + +#include "vtkSmartPointer.h" + +class vtkActor; +class vtkCamera; +class vtkSphereSource; + +class Region; + +class RegionCenter3D { +public: + RegionCenter3D(Region* inputRegion, double color[3]); + ~RegionCenter3D(); + + vtkSmartPointer GetActor(); + + void Update(); + +protected: + Region* region; + + vtkSmartPointer sphere; + vtkSmartPointer actor; +}; + +#endif diff --git a/region/RegionInfo.cxx b/region/RegionInfo.cxx index 7f923ad..a6acf38 100644 --- a/region/RegionInfo.cxx +++ b/region/RegionInfo.cxx @@ -2,7 +2,7 @@ #include "Region.h" -RegionInfo::RegionInfo() : label(-1), visible(true), modified(false), done(false) { +RegionInfo::RegionInfo() : label(-1), visible(true), modified(false), done(false), verified(false), comment("") { color[0] = color[1] = color[2] = -1.0; extent[0] = extent[1] = extent[2] = extent[3] = extent[4] = extent[5] = -1; } diff --git a/visualization/SliceView.cxx b/visualization/SliceView.cxx index 66a4be2..1cb4bf8 100644 --- a/visualization/SliceView.cxx +++ b/visualization/SliceView.cxx @@ -35,6 +35,7 @@ #include "Region.h" #include "RegionOutline.h" #include "RegionSurface.h" +#include "RegionCenter2D.h" #include "RegionCollection.h" #include "SegmentorMath.h" #include "SliceLocation.h" @@ -200,6 +201,9 @@ void SliceView::AddRegionActors(Region* region) { //renderer->AddActor(region->GetText()); regionOutlinesRenderer->AddActor(region->GetText()); + regionOutlinesRenderer->AddActor(region->GetCenter2D()->GetActor()); + region->GetCenter2D()->Update(plane->GetOrigin()[2]); + #ifdef SHOW_REGION_BOX //regionOutlinesRenderer->AddActor(region->GetBox()); #endif @@ -268,6 +272,7 @@ void SliceView::SetInteractionMode(enum InteractionMode mode) { mode == MergeMode ? "Merge region" : mode == GrowMode ? "Grow/shrink region" : mode == VisibleMode ? "Toggle region visibility" : + mode == DotMode ? "Dot annotation mode" : ""; interactionModeLabel->SetInput(s.c_str()); @@ -411,9 +416,18 @@ void SliceView::UpdatePlane() { sliceLocation->UpdateView(cam, plane); - if (plane->GetOrigin()[2] != oldZ) { + double z = plane->GetOrigin()[2]; + + if (z != oldZ) { DoAutoRescale(); } + + if (regions) { + for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { + Region* region = regions->Get(it); + region->GetCenter2D()->Update(z); + } + } } void SliceView::SetBrushRadius(int radius) { diff --git a/visualization/VisualizationContainer.cxx b/visualization/VisualizationContainer.cxx index b5ece82..06e71fc 100644 --- a/visualization/VisualizationContainer.cxx +++ b/visualization/VisualizationContainer.cxx @@ -179,6 +179,12 @@ VisualizationContainer::VisualizationContainer(vtkRenderWindowInteractor* volume volumeView->GetInteractorStyle()->AddObserver(vtkInteractorStyleVolume::VisibleEvent, visibleCallback); sliceView->GetInteractorStyle()->AddObserver(vtkInteractorStyleSlice::VisibleEvent, visibleCallback); + // Dot annotation + vtkSmartPointer dotCallback = vtkSmartPointer::New(); + dotCallback->SetCallback(InteractionCallbacks::Dot); + dotCallback->SetClientData(this); + sliceView->GetInteractorStyle()->AddObserver(vtkInteractorStyleSlice::DotEvent, dotCallback); + // Mouse move vtkSmartPointer mouseMoveCallback = vtkSmartPointer::New(); mouseMoveCallback->SetCallback(InteractionCallbacks::MouseMove); @@ -637,6 +643,13 @@ void VisualizationContainer::SetInteractionMode(InteractionMode mode) { sliceView->SetInteractionMode(interactionMode); volumeView->SetInteractionMode(interactionMode); + for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { + Region* region = regions->Get(it); + region->ShowCenter(interactionMode == DotMode); + } + + UpdateVisibility(); + Render(); } @@ -1904,6 +1917,82 @@ void VisualizationContainer::ToggleCurrentRegionDone() { SetRegionDone(currentRegion->GetLabel(), !currentRegion->GetDone()); } +void VisualizationContainer::ToggleCurrentRegionVerified() { + if (!currentRegion || !currentRegion->GetDone()) return; + + SetRegionVerified(currentRegion->GetLabel(), !currentRegion->GetVerified()); +} + +void VisualizationContainer::SetDotAnnotation(double point[3]) { + int ijk[3]; + PointToIndex(point, ijk); + int x = ijk[0]; + int y = ijk[1]; + int z = ijk[2]; + + // Get data at point + unsigned short* labelData = static_cast(labels->GetScalarPointer(x, y, z)); + + if (*labelData > 0) { + regions->Remove(*labelData); + + } + else { + // Create new label + unsigned short newLabel = regions->GetNewLabel(); + + UpdateColors(newLabel); + + // Set dot label + *labelData = newLabel; + + // Create new region + int extent[6] = { x, x, y, y, z, z }; + Region* newRegion = new Region(newLabel, labelColors->GetTableValue(newLabel), labels, extent); + newRegion->ShowCenter(true); + + regions->Add(newRegion); + volumeView->AddRegion(newRegion); + sliceView->AddRegion(newRegion); + + newRegion->SetModified(true); + } + + qtWindow->updateRegions(regions); + + SetCurrentRegion(nullptr); + + labels->Modified(); + Render(); +} + +bool VisualizationContainer::CheckDots() { + for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { + Region* region = regions->Get(it); + + if (region->GetNumVoxels() > 1) { + return false; + } + } + + return true; +} + +void VisualizationContainer::ApplyDotAnnotation() { + for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { + Region* region = regions->Get(it); + region->ApplyDot(); + } + + labels->Modified(); + + qtWindow->updateRegions(regions); + + Render(); + + PushHistory(); +} + Region* VisualizationContainer::SetRegionDone(unsigned short label, bool done) { Region* region = regions->Get(label); @@ -2348,6 +2437,11 @@ void VisualizationContainer::ExtractRegions(vtkIntArray* extents) { delete region; } + if (interactionMode == DotMode) { + region->ApplyDot(); + region->ShowCenter(true); + } + qtWindow->updateProgress((double)label / maxLabel); } @@ -2394,6 +2488,11 @@ void VisualizationContainer::ExtractRegions(const std::vector& metad region->SetVerified(true); labelColors->SetTableValue(region->GetLabel(), LabelColors::verifiedColor); } + + if (interactionMode == DotMode) { + region->ApplyDot(); + region->ShowCenter(true); + } } else { delete region; @@ -2511,7 +2610,7 @@ void VisualizationContainer::UpdateVisibility(Region* highlightRegion) { for (RegionCollection::Iterator it = regions->Begin(); it != regions->End(); it++) { Region* region = regions->Get(it); - bool show = !filterRegions || region->GetVisible() || region == currentRegion || region == highlightRegion; + bool show = interactionMode != DotMode && (!filterRegions || region->GetVisible() || region == currentRegion || region == highlightRegion); volumeView->ShowRegion(region, show); sliceView->ShowRegion(region, show); diff --git a/visualization/VisualizationContainer.h b/visualization/VisualizationContainer.h index c02dd1a..5559281 100644 --- a/visualization/VisualizationContainer.h +++ b/visualization/VisualizationContainer.h @@ -114,6 +114,10 @@ class VisualizationContainer { void FillCurrentRegionSlice(); void GrowCurrentRegion(double point[3]); void ToggleCurrentRegionDone(); + void ToggleCurrentRegionVerified(); + void SetDotAnnotation(double point[3]); + bool CheckDots(); + void ApplyDotAnnotation(); void SetWindowLevel(double window, double level); void SetVolumeWindowLevel(double window, double level); diff --git a/visualization/VolumeView.cxx b/visualization/VolumeView.cxx index b7d85f0..190e933 100644 --- a/visualization/VolumeView.cxx +++ b/visualization/VolumeView.cxx @@ -37,6 +37,7 @@ #include "Region.h" #include "RegionSurface.h" #include "RegionHighlight3D.h" +#include "RegionCenter3D.h" #include "RegionCollection.h" #include "SegmentorMath.h" @@ -213,6 +214,7 @@ void VolumeView::SetRegions(vtkImageData* imageLabels, RegionCollection* newRegi void VolumeView::AddRegion(Region* region) { RegionSurface* surface = region->GetSurface(); RegionHighlight3D* highlight = region->GetHighlight3D(); + RegionCenter3D* center = region->GetCenter3D(); surface->SetSmoothSurface(smoothSurfaces); surface->SetSmoothShading(smoothShading); @@ -223,6 +225,7 @@ void VolumeView::AddRegion(Region* region) { renderer->AddActor(surface->GetActor()); renderer->AddActor(highlight->GetActor()); + renderer->AddActor(center->GetActor()); #ifdef SHOW_REGION_BOX renderer->AddActor(region->GetBox());