diff --git a/imaging/components/dicom/DicomDialog.cpp b/imaging/components/dicom/DicomDialog.cpp index 9b7f4cf48babc494a0f2d5294aface311e2ca388..d77d891897117c1b145ad1aa27bff55ef9b77383 100644 --- a/imaging/components/dicom/DicomDialog.cpp +++ b/imaging/components/dicom/DicomDialog.cpp @@ -1,4 +1,4 @@ -/***************************************************************************** +/***************************************************************************** * $CAMITK_LICENCE_BEGIN$ * * CamiTK - Computer Assisted Medical Intervention ToolKit @@ -59,6 +59,10 @@ DicomDialog::DicomDialog(QList elements): QDialog() { ui.tableWidget->setItem(row, column++, new QTableWidgetItem(item->getSeriesName())); row++; } + // To get checkbox checked if there is only one study in list + if (elements.size() == 1) { + ui.tableWidget->itemAt(0, 0)->setCheckState(Qt::Checked); + } ui.tableWidget->resizeColumnsToContents(); diff --git a/sdk/actions/application/QuitAction.cpp b/sdk/actions/application/QuitAction.cpp index d1be48f0037ebff34db042cc95e654568b86a2f1..e0a0d4f64f4e2f952894ef6030103390b4691184 100644 --- a/sdk/actions/application/QuitAction.cpp +++ b/sdk/actions/application/QuitAction.cpp @@ -57,7 +57,16 @@ QWidget* QuitAction::getWidget() { // --------------- apply ------------------- Action::ApplyStatus QuitAction::apply() { - Application::getMainWindow()->close(); - return SUCCESS; + // close all components (and therefore ask the user to savet the modified ones) + ApplyStatus closeAllStatus = Application::getAction("Close All")->apply(); + if (closeAllStatus == SUCCESS) { + // Quit the app (will call Application::quitting() and therefore unload all action extensions and delete all actions) + Application::quit(); + return SUCCESS; + } + else { + // or abort the operation + return ABORTED; + } } diff --git a/sdk/actions/application/SaveAllAction.cpp b/sdk/actions/application/SaveAllAction.cpp index 6f8d4b621cd8569f84ce41320624fdbb8afc661b..7365738dbc02c9728d7a39e17651f9982b39ce48 100644 --- a/sdk/actions/application/SaveAllAction.cpp +++ b/sdk/actions/application/SaveAllAction.cpp @@ -25,14 +25,8 @@ #include "SaveAllAction.h" #include -#include -#include -#include -#include #include -#include - using namespace camitk; diff --git a/sdk/actions/image/imagelut/ImageLutAction.cpp b/sdk/actions/image/imagelut/ImageLutAction.cpp index 37f3d9a61ad74b4fca8228f810e5a0960b404b89..fd4ece71154cd8c635f743835e4f17693a98db3d 100644 --- a/sdk/actions/image/imagelut/ImageLutAction.cpp +++ b/sdk/actions/image/imagelut/ImageLutAction.cpp @@ -36,11 +36,11 @@ using namespace camitk; // --------------- constructor ------------------- ImageLutAction::ImageLutAction(ActionExtension* extension) : Action(extension) { - setName("Lut"); + setName("Image Look Up Table"); setDescription("Modify the LUT of an image components"); setComponent("ImageComponent"); - setFamily("ImageLut"); - addTag("Lut"); + setFamily("Image LUT"); + addTag("LUT"); } // --------------- getWidget ------------------- diff --git a/sdk/actions/image/imagelut/ImageLutWidget.cpp b/sdk/actions/image/imagelut/ImageLutWidget.cpp index 5deaa94a342719c5dd257aa3fcd01b2b1d5ac4cd..bdbcb6fa19a3f9101e32d218c5e2d28ee1186a19 100644 --- a/sdk/actions/image/imagelut/ImageLutWidget.cpp +++ b/sdk/actions/image/imagelut/ImageLutWidget.cpp @@ -47,7 +47,7 @@ using namespace camitk; // ---------------------- Constructor ---------------------------- ImageLutWidget::ImageLutWidget(QWidget* parent) : QWidget(parent) { - myComponent = NULL; + myComponent = nullptr; lutMin = 0; lutMax = 255; @@ -92,7 +92,7 @@ void ImageLutWidget::updateComponent(ImageComponent* comp) { myComponent = comp; // if there is no lookup table, just don't display this widget! - if (myComponent->getLut() == NULL) { + if (myComponent->getLut() == nullptr) { setEnabled(false); return; } diff --git a/sdk/actions/image/volumerendering/VolumeRenderingAction.h b/sdk/actions/image/volumerendering/VolumeRenderingAction.h index 590017190d9979db21cb46eadd1c2076719e3604..8cc958f535dfa5a8d445cc1c77701299444bb60c 100644 --- a/sdk/actions/image/volumerendering/VolumeRenderingAction.h +++ b/sdk/actions/image/volumerendering/VolumeRenderingAction.h @@ -51,10 +51,8 @@ public: /// Default Destructor virtual ~VolumeRenderingAction() = default; - void createVolumeRendering(camitk::ImageComponent* comp); - public slots: /** this method is automatically called when the action is triggered. * Call getTargets() method to get the list of components to use. diff --git a/sdk/actions/mesh/meshprocessing/WarpOut.cpp b/sdk/actions/mesh/meshprocessing/WarpOut.cpp index 7a80b0d8d4657841b859d0b31a0abf97439f28ba..116d273f94b614c3476ce8e6a325445d7a97ab5a 100644 --- a/sdk/actions/mesh/meshprocessing/WarpOut.cpp +++ b/sdk/actions/mesh/meshprocessing/WarpOut.cpp @@ -189,7 +189,7 @@ Action::ApplyStatus WarpOut::apply() { for (vtkIdType i = 0; i < connectivityFilter->GetOutput()->GetNumberOfPoints(); i++) { // get the id of the point in the original point set -#if VTK_MAJOR_VERSION == 8 +#if VTK_MAJOR_VERSION >= 7 pointId->GetTypedTuple(i, &id); #else pointId->GetTupleValue(i, &id); diff --git a/sdk/applications/imp/ImpMainWindow.cpp b/sdk/applications/imp/ImpMainWindow.cpp index 6eccc8d05818536f92e8d1919a4a95f5b608bd28..5c6713d516fc2ebe1efb40af2554efa3697bb2df 100644 --- a/sdk/applications/imp/ImpMainWindow.cpp +++ b/sdk/applications/imp/ImpMainWindow.cpp @@ -1,4 +1,4 @@ -/***************************************************************************** +/***************************************************************************** * $CAMITK_LICENCE_BEGIN$ * * CamiTK - Computer Assisted Medical Intervention ToolKit @@ -61,6 +61,8 @@ ImpMainWindow::ImpMainWindow() : MainWindow("imp") { // set the specific actions state machine icon setWindowIcon(QPixmap(":/applicationIcon")); + fileOpenDataDirectoryMenu = nullptr; + // init all other GUI initActions(); initMenuBar(); @@ -68,7 +70,6 @@ ImpMainWindow::ImpMainWindow() : MainWindow("imp") { // initialize architecture updateActionStates(); - updateOpenDirectoryMenu(); // now add the different viewers setCentralViewer(MedicalImageViewer::getInstance()); @@ -111,10 +112,9 @@ void ImpMainWindow::aboutToShow() { void ImpMainWindow::refresh() { MainWindow::refresh(); - // update Data directory menu - updateOpenDirectoryMenu(); // update all the action states updateActionStates(); + // update menu updateRecentDocumentsMenu(); } @@ -229,10 +229,7 @@ void ImpMainWindow::initMenuBar() { fileMenu = new QMenu(tr("&File")); fileMenu->addAction(fileOpen); - fileOpenDataDirectoryMenu = new QMenu(tr("Open &Data Directory...")); - fileOpenDataDirectoryMenu->setIcon(QPixmap(":/fileOpen")); - fileOpenDataDirectoryMenu->setEnabled(false); - fileMenu->addMenu(fileOpenDataDirectoryMenu); + openDataDirectoryMenuBuilder(); fileMenu->addAction(fileClose); fileMenu->addAction(fileCloseAll); @@ -423,32 +420,9 @@ void ImpMainWindow::resetWindows() { move(0, 0); } -// ------------------------ updateDataDirectoryMenu ---------------------------- -void ImpMainWindow::updateOpenDirectoryMenu() { - unsigned int nrOfDataDirectoryManager = 0; - - disconnect(fileOpenDataDirectoryMenu, SIGNAL(triggered(QAction*)), this, SLOT(openDirectory(QAction*))); - fileOpenDataDirectoryMenu->clear(); - fileOpenDataDirectoryMenu->setEnabled(false); - - foreach (QString name, ExtensionManager::getDataDirectoryExtNames()) { - nrOfDataDirectoryManager++; - QAction* openDirectory = new QAction(name, this); - openDirectory->setStatusTip(tr(QString("Opens data directory for " + name).toStdString().c_str())); - openDirectory->setWhatsThis(tr(QString("Opens data directory for " + name).toStdString().c_str())); - - fileOpenDataDirectoryMenu->addAction(openDirectory); - } - - if (nrOfDataDirectoryManager > 0) { - fileOpenDataDirectoryMenu->setEnabled(true); - connect(fileOpenDataDirectoryMenu, SIGNAL(triggered(QAction*)), this, SLOT(openDirectory(QAction*))); - } -} - // ------------- openDataDirectory ----------------- -void ImpMainWindow::openDirectory(QAction* emitter) { - QString pluginName = emitter->text().replace("&", ""); +void ImpMainWindow::openDataDirectory(QString plugin) { + QString pluginName = plugin; statusBar()->showMessage(tr(QString("Opening " + pluginName + " directory...").toStdString().c_str())); @@ -499,7 +473,7 @@ void ImpMainWindow::updateViewMenu() { // change the central viewer if there is more than one viewer in the central area if (qobject_cast(centralWidget())->count() > 1) { viewMenu->addSeparator()->setText(tr("Toggle Central Viewers")); - + // add a toggle action for each widget stacked inside the central widget for (int i = 0; i < qobject_cast(centralWidget())->count(); i++) { QWidget* cWidget = qobject_cast(centralWidget())->widget(i); @@ -514,15 +488,15 @@ void ImpMainWindow::updateViewMenu() { }); Viewer* cViewer = (*it); // viewerAction is own by viewMenu, viewMenu->clear() will delete it - QAction *viewerAction = viewMenu->addAction(cViewer->objectName()); + QAction* viewerAction = viewMenu->addAction(cViewer->objectName()); viewerAction->setCheckable(true); viewerAction->setChecked(cViewer == centralViewer); QString tipString = "Hide/show the " + cViewer->objectName() + " central viewer"; viewerAction->setStatusTip(tr(tipString.toStdString().c_str())); viewerAction->setWhatsThis(tr(tipString.toStdString().c_str())); // add the toggle action slot using C++11 lambda so that everything is contained inside viewMenu - connect(viewerAction, &QAction::toggled, [=](bool) { - setCentralViewer(cViewer); + connect(viewerAction, &QAction::toggled, [ = ](bool) { + setCentralViewer(cViewer); }); } } @@ -569,7 +543,7 @@ void ImpMainWindow::editSettings() { } // check if there are anything to change in the data directory menu (for data directory manager) - updateOpenDirectoryMenu(); + ImpMainWindow::openDataDirectoryMenuBuilder(); } // ------------- setApplicationConsole ----------------- @@ -623,7 +597,68 @@ void ImpMainWindow::saveHistoryAsSCXML() { Application::saveHistoryAsSXML(); } +// ------------------------- openDataDirectoryMenuBuilder --------------------------- +void ImpMainWindow::openDataDirectoryMenuBuilder() { + QStringList dirExt = ExtensionManager::getDataDirectoryExtNames(); + + if (dirExt.size() > 1) { + if (fileOpenDataDirectoryMenu == nullptr) { + fileOpenDataDirectoryMenu = new QMenu("Open Data Directory"); + fileMenu->addMenu(fileOpenDataDirectoryMenu); + } + else { + disconnect(fileOpenDataDirectoryMenu, 0, 0, 0); + fileOpenDataDirectoryMenu->clear(); + } + fileOpenDataDirectoryMenu->setIcon(QPixmap(":/fileOpen")); + for (QString dirExtName : dirExt) { + QAction* openDirectory = new QAction(dirExtName, this); + openDirectory->setStatusTip(tr(QString("Opens data directory for " + dirExtName).toStdString().c_str())); + openDirectory->setWhatsThis(tr(QString("Opens data directory for " + dirExtName).toStdString().c_str())); + connect(openDirectory, &QAction::triggered, this, [ = ]() { + this->openDataDirectory(dirExtName); + }); + + fileOpenDataDirectoryMenu->addAction(openDirectory); + + } + } + else + if (dirExt.size() == 1) { + // if it exists, remove the default directory extension menu (that has only one submenu) + // from the file "open" action list + bool updateAction = false; + QList menuActions = fileMenu->actions(); + int indexAction = 0; + while (indexAction < menuActions.size() && !updateAction) { + if (menuActions.at(indexAction)->text().contains(" Directory")) { + menuActions.at(indexAction)->destroyed(); + updateAction = true; + } + indexAction++; + } + // create the single open directory action + QString dirExtName = dirExt.at(0); + QAction* openDirectoryAction = new QAction("Open " + dirExtName + " Directory", this); + if (!updateAction) { + // there was no specific menu, just add the single "open directory" action in the file menu + fileMenu->addAction(openDirectoryAction); + } + openDirectoryAction->setStatusTip(tr(QString("Opens data directory for " + dirExtName).toStdString().c_str())); + openDirectoryAction->setWhatsThis(tr(QString("Opens data directory for " + dirExtName).toStdString().c_str())); + connect(openDirectoryAction, &QAction::triggered, this, [ = ]() { + this->openDataDirectory(dirExtName); + }); + openDirectoryAction->setIcon(QPixmap(":/fileOpen")); + } + else { + fileOpenDataDirectoryMenu = new QMenu("No plugins loaded to Open Data Directory"); + fileOpenDataDirectoryMenu->setIcon(QPixmap(":/fileOpen")); + fileOpenDataDirectoryMenu->setEnabled(false); + fileMenu->addMenu(fileOpenDataDirectoryMenu); + } +} diff --git a/sdk/applications/imp/ImpMainWindow.h b/sdk/applications/imp/ImpMainWindow.h index 4cb294d3aa48e6cf1bfa5de09ec0b62d360e5091..aeabf143cc4ce4d0bd5f6edb272478e918d26eb0 100644 --- a/sdk/applications/imp/ImpMainWindow.h +++ b/sdk/applications/imp/ImpMainWindow.h @@ -1,4 +1,4 @@ -/***************************************************************************** +/***************************************************************************** * $CAMITK_LICENCE_BEGIN$ * * CamiTK - Computer Assisted Medical Intervention ToolKit @@ -80,7 +80,7 @@ public slots: /// @name file menu slot ///@{ /// open a data directory, "called" from the fileOpenDataDirectoryMenu - void openDirectory(QAction*); + void openDataDirectory(QString plugin); ///@} /// @name viewers' slot @@ -122,11 +122,15 @@ protected: /// update the viewer's menu void updateViewMenu(); + /// Build the open data directory menu + void openDataDirectoryMenuBuilder(); + /** @name Extension and ComponentPlugin attributes */ ///@{ /// update the data directory menu depending on registered plugins void updateOpenDirectoryMenu(); + /// update the recent document menu void updateRecentDocumentsMenu(); ///@} diff --git a/sdk/libraries/core/action/Action.h b/sdk/libraries/core/action/Action.h index e1953cb320f6159c9dd74e7ec23096dfa49ddb34..1e60429b08629af4242bb872079dd6145ffe079b 100644 --- a/sdk/libraries/core/action/Action.h +++ b/sdk/libraries/core/action/Action.h @@ -572,7 +572,7 @@ private: } // -------------------- declare the interface for QPluginLoader -------------------- -Q_DECLARE_INTERFACE(camitk::Action, "TIMC-IMAG.Action/2.1") //TODO use svn version? +Q_DECLARE_INTERFACE(camitk::Action, "TIMC-IMAG.Action/2.1") #endif // ACTION_H diff --git a/sdk/libraries/core/action/ActionExtension.cpp b/sdk/libraries/core/action/ActionExtension.cpp index e257f8c0de62a0ce8b8cb507e0724fe3c468a2f9..54d0b1492fb3d85c2301c5b98bf07b6013774fc7 100644 --- a/sdk/libraries/core/action/ActionExtension.cpp +++ b/sdk/libraries/core/action/ActionExtension.cpp @@ -58,6 +58,7 @@ void ActionExtension::initResources() { // -------------------- destructor -------------------- ActionExtension::~ActionExtension() { + // delete all actions one by one while (!actions.empty()) { Action* toDelete = actions.takeFirst(); // do not delete the "Quit" action: it is the action that triggers this delete! @@ -66,7 +67,7 @@ ActionExtension::~ActionExtension() { } } - //delete internationalization instance + // delete internationalization instance if (translator) { delete translator; } diff --git a/sdk/libraries/core/action/ActionExtension.h b/sdk/libraries/core/action/ActionExtension.h index f3703595d28cf4e5a5f200e059a8e5b9e28264c8..63fec35aff75c3e79af51a3008262127aa50329e 100644 --- a/sdk/libraries/core/action/ActionExtension.h +++ b/sdk/libraries/core/action/ActionExtension.h @@ -108,7 +108,7 @@ private: } // -------------------- declare the interface for QPluginLoader -------------------- -Q_DECLARE_INTERFACE(camitk::ActionExtension, "TIMC-IMAG. Action Extension/2.1") //TODO use variable from CMake? +Q_DECLARE_INTERFACE(camitk::ActionExtension, "TIMC-IMAG. Action Extension/2.1") #endif //ACTION_EXTENSION_H diff --git a/sdk/libraries/core/application/Application.cpp b/sdk/libraries/core/application/Application.cpp index d5fe1c9e29bef9b0df3f24294b891b67f640f55f..1d8aa5cdf42bb50f590e61b855b0970a4e392889 100644 --- a/sdk/libraries/core/application/Application.cpp +++ b/sdk/libraries/core/application/Application.cpp @@ -204,7 +204,7 @@ QString Application::getName() { // ----------------- quitting -------------------- void Application::quitting() { - // this is connect to the aboutToQuit signal from QApplication + // this is connected to the aboutToQuit signal from QApplication // it should contain all the code that frees the resources // delete all actions (they are instantiated when the extension is loaded) diff --git a/sdk/libraries/core/component/Geometry.h b/sdk/libraries/core/component/Geometry.h index adf2f003f9436736b021a70665a96f9514a28776..5b837173cabf518e835499b02282f9f8cdc0ec3c 100644 --- a/sdk/libraries/core/component/Geometry.h +++ b/sdk/libraries/core/component/Geometry.h @@ -186,6 +186,27 @@ public: */ vtkSmartPointer getActor(const RenderingModes) override; + /** Set a texture to this object. */ + void setTexture(vtkSmartPointer texture) override; + + /// a vtkPoint of the structured was picked (to be reimplemented in a Component inherited class if needed) + void pointPicked(vtkIdType, bool) {}; + + /// a vtkCell of the structured was picked (to be reimplemented in a Component inherited class if needed) + void cellPicked(vtkIdType, bool) {}; + + ///@} + + /// @name manage extra prop associated with a Geometry + /// @{ + /// TODO + /// - put all this management into a dedicated interface + /// - remove it from InterfaceBitMap and InterfaceGeometry + /// - remove it from Slice and Geometry helper classes + /// - create a new associated helper class + /// - update Component class and all other code using it (if needed) + /// Note : beware that Geometry requires this to manage to at least "label" and "glyph" extra actors + /// Return the vtkProp (actors, volumes and annotations) corresponding to the given name vtkSmartPointer getProp(const QString&) override; @@ -204,18 +225,8 @@ public: * @return true if effictively done */ bool removeProp(const QString&) override; - - /** Set a texture to this object. */ - void setTexture(vtkSmartPointer texture) override; - - /// a vtkPoint of the structured was picked (to be reimplemented in a Component inherited class if needed) - void pointPicked(vtkIdType, bool) {}; - - /// a vtkCell of the structured was picked (to be reimplemented in a Component inherited class if needed) - void cellPicked(vtkIdType, bool) {}; - - ///@} - + /// @} + /// @name InterfaceGeometry Helpers inherited methods /// @{ @@ -307,10 +318,7 @@ private: /// the VTK mapper vtkSmartPointer mapper; - - /// The additional map for prop (include at least "label" and "glyph" - QMap > extraProp; - + /// the mapper to create the text vtkSmartPointer labelActorMapper; @@ -386,8 +394,15 @@ private: double oldAlphaPoints; double oldPointsColor[4]; ///@} -protected: - vtkSmartPointer< vtkPointSet > New(); + + /// @name manage extra prop associated with a Geometry + /// @{ + /// TODO see extra prop management method section + + /// The additional map for prop (include at least "label" and "glyph") + QMap > extraProp; + ///@} + }; diff --git a/sdk/libraries/core/component/InterfaceBitMap.h b/sdk/libraries/core/component/InterfaceBitMap.h index e23bdbbc388a4e0b64082ceaf3217a96c3a922df..a680a8badc0d6ff14e0f3267b2c14314576df82d 100644 --- a/sdk/libraries/core/component/InterfaceBitMap.h +++ b/sdk/libraries/core/component/InterfaceBitMap.h @@ -85,19 +85,20 @@ public: /** Return the vtkActor used to pick pixels in the slices. */ virtual vtkSmartPointer getPixelActor() = 0; - virtual void updateReslice() = 0; - - virtual void setReslicerTransform(vtkSmartPointer) = 0; - - /** Return 2D Axes at the proper slice origin */ -// virtual vtkSmartPointer get2DAxesActor() = 0; + /** For an arbitrary slice: update the image depending on the current value of the reslice transform */ + virtual void updateReslice() = 0; + /** Set the pointer to the image transformation. + * this should be done once, at initialization, using the frame transformation (getTransform) + */ + virtual void setReslicerTransform(vtkSmartPointer) = 0; /** This method is called when the associated plane has been picked in the InteractiveViewer, * the given coordinates is position where the plane was picked. */ virtual void pixelPicked(double, double, double) = 0; + /// update the position of the plane surrounding the currently selected slice virtual void updatePickPlane() = 0; /** Return the number of slices in the image data set. */ @@ -126,10 +127,18 @@ public: /** Returns the encapsultaed data structure: the image as a vtkImageData. */ virtual vtkSmartPointer getImageData() const = 0; - // TODO : put all of this into a dedicated interface - /// The additional map for prop (include at least "label" and "glyph" - QMap > extraProp; - + + + /// @name manage extra prop associated with an InterfaceBitMap + /// @{ + /// TODO + /// - put all this management into a dedicated interface + /// - remove it from InterfaceBitMap and InterfaceGeometry + /// - remove it from Slice and Geometry helper classes + /// - create a new associated helper class + /// - update Component class and all other code using it (if needed) + /// Note : beware that Geometry requires this to manage to at least "label" and "glyph" extra actors + /// Return the vtkProp (actors, volumes and annotations) corresponding to the given name virtual vtkSmartPointer getProp(const QString&) = 0; @@ -148,7 +157,7 @@ public: * @return true if effictively done */ virtual bool removeProp(const QString&) = 0; - // END TODO + /// @} }; diff --git a/sdk/libraries/core/component/InterfaceGeometry.h b/sdk/libraries/core/component/InterfaceGeometry.h index 4526b65dfeac4699e0fb7b10ed8ddc4bbb3bdb4a..9ca41ddbf34c176b44177b1cded439420eaea32b 100644 --- a/sdk/libraries/core/component/InterfaceGeometry.h +++ b/sdk/libraries/core/component/InterfaceGeometry.h @@ -152,29 +152,7 @@ public: * @see Geometry */ virtual void setDataConnection(vtkSmartPointer) = 0; - - /// Return the actor for the representation mode, NULL if the actor doesn't exist. - virtual vtkSmartPointer getActor(const RenderingModes) = 0; - - /// Return the vtkProp (actors, volumes and annotations) corresponding to the given name - virtual vtkSmartPointer getProp(const QString&) = 0; - - /// return the number of additional prop - virtual unsigned int getNumberOfProp() const = 0; - - /// return an additional prop by its index - virtual vtkSmartPointer getProp(unsigned int) = 0; - - /** remove a given additional prop. - * @return true if effictively done - */ - virtual bool removeProp(const QString&) = 0; - - /** insert an additional prop, defining it by its name (default visibility = false). - * @return true if the additional prop was added (i.e. another additional prop of the same name does not exist) - */ - virtual bool addProp(const QString&, vtkSmartPointer) = 0; - + /// Set a texture to this object. virtual void setTexture(vtkSmartPointer) = 0; @@ -204,6 +182,40 @@ public: * @see InteractiveViewer */ virtual void cellPicked(vtkIdType cellId, bool pickingIsSelecting) = 0; + + /// Return the actor for the representation mode, NULL if the actor doesn't exist. + virtual vtkSmartPointer getActor(const RenderingModes) = 0; + ///@} + + /// @name manage extra prop associated with an InterfaceGeometry + /// @{ + /// TODO + /// - put all this management into a dedicated interface + /// - remove it from InterfaceBitMap and InterfaceGeometry + /// - remove it from Slice and Geometry helper classes + /// - create a new associated helper class + /// - update Component class and all other code using it (if needed) + /// Note : beware that Geometry requires this to manage to at least "label" and "glyph" extra actors + + /// Return the vtkProp (actors, volumes and annotations) corresponding to the given name + virtual vtkSmartPointer getProp(const QString&) = 0; + + /// return the number of additional prop + virtual unsigned int getNumberOfProp() const = 0; + + /// return an additional prop by its index + virtual vtkSmartPointer getProp(unsigned int) = 0; + + /** remove a given additional prop. + * @return true if effictively done + */ + virtual bool removeProp(const QString&) = 0; + + /** insert an additional prop, defining it by its name (default visibility = false). + * @return true if the additional prop was added (i.e. another additional prop of the same name does not exist) + */ + virtual bool addProp(const QString&, vtkSmartPointer) = 0; + ///@} /// @name Helpers methods diff --git a/sdk/libraries/core/component/Slice.cpp b/sdk/libraries/core/component/Slice.cpp index 85b3379c560b273b3540f1cded30d15d5c9c05f2..db8787e31148c439bdbdbddaea19bd16d38874e5 100644 --- a/sdk/libraries/core/component/Slice.cpp +++ b/sdk/libraries/core/component/Slice.cpp @@ -30,14 +30,12 @@ #include "Slice.h" // -- vtk stuff -#include #include #include -#include -#include -#include -#include #include +#include +#include +#include using namespace std; @@ -45,8 +43,8 @@ using namespace std; namespace camitk { // -------------------- constructor -------------------- Slice::Slice(vtkSmartPointer volume, SliceOrientation orientation, vtkSmartPointer lookupTable) { - this->sliceOrientation = orientation; - this->lut = lookupTable; + sliceOrientation = orientation; + lut = lookupTable; init(); setOriginalVolume(volume); setSlice(getNumberOfSlices() / 2); @@ -56,62 +54,29 @@ Slice::Slice(vtkSmartPointer volume, SliceOrientation orientation, // -------------------- Destructor -------------------- Slice::~Slice() { // Let's unreference vtkSmartPointers - - originalVolume = nullptr; - lut = nullptr; - imgToMapFilter = nullptr; - image3DActor = nullptr; - image2DActor = nullptr; - pickPlane = nullptr; - pickPlaneActor = nullptr; - pickPlaneMapper = nullptr; - pixelActor = nullptr; + init(); } - // -------------------- init -------------------- void Slice::init() { currentSliceIndex = 0; - extent[0] = 0; - extent[1] = 0; - extent[2] = 0; - extent[3] = 0; - extent[4] = 0; - extent[5] = 0; - - originalDimensions[0] = -1; - originalDimensions[1] = -1; - originalDimensions[2] = -1; - - originalSpacing[0] = -1.0; - originalSpacing[1] = -1.0; - originalSpacing[2] = -1.0; - - originalVolume = nullptr; - - imgToMapFilter = nullptr; - image3DActor = nullptr; - image2DActor = nullptr; + for (int i=0; i<3; i++) { + originalSpacing[i] = 1.0; + } - pickPlane = nullptr; - pickPlaneMapper = nullptr; - pickPlaneActor = nullptr; - pixelActor = nullptr; + originalVolume = nullptr; + image3DActor = nullptr; + image2DActor = nullptr; + pickPlaneActor = nullptr; + pickPlaneActorPointSet = nullptr; + pixelActor = nullptr; + pixelActorPointSet = nullptr; - imgToMapFilter = vtkSmartPointer::New(); - image3DActor = vtkSmartPointer::New(); - image2DActor = vtkSmartPointer::New(); image2DChangeInfo = vtkSmartPointer::New(); image2DReslicer = vtkSmartPointer::New(); - transformReslicer = vtkSmartPointer::New(); resliceTransform = vtkSmartPointer::New(); - - pickPlane = vtkSmartPointer::New(); - pickPlaneMapper = vtkSmartPointer::New(); - pickPlaneActor = vtkSmartPointer::New(); - transformReslicer->Identity(); resliceTransform->Identity(); } @@ -127,119 +92,32 @@ void Slice::setOriginalVolume(vtkSmartPointer volume) { // de-reference the smart pointer. originalVolume = volume; - // Get volume information + // Get the original volume information + // Original volume dimensions in number of voxels (x, y and z) + int originalDimensions[3]; originalVolume->GetDimensions(originalDimensions); originalVolume->GetSpacing(originalSpacing); - originalVolume->GetExtent(extent); - - originalSize[0] = originalDimensions[0] * originalSpacing[0]; - originalSize[1] = originalDimensions[1] * originalSpacing[1]; - originalSize[2] = originalDimensions[2] * originalSpacing[2]; - - // In other methods, there will be divisions by originalSpacing[k] - // so let's avoid division by 0 - if (originalSpacing[0] == 0.0) { - originalSpacing[0] = -1.0; - } - if (originalSpacing[1] == 0.0) { - originalSpacing[1] = -1.0; - } - - if (originalSpacing[2] == 0.0) { - originalSpacing[2] = -1.0; + for (int i=0; i<3; i++) { + // compute original size (nb of slice * spacing) + originalSize[i] = originalDimensions[i] * originalSpacing[i]; + + // As originalSpacing[i] will be used to get the slice number + // for 2D images, lets replace 0.0 by 1.0 to avoid division by 0 + if (originalSpacing[i] == 0.0) { + originalSpacing[i] = 1.0; + } } - - // Sets the end of the pipeline + + // Prepare all the visualization pipeline initActors(); } -// -------------------- initActors -------------------- -void Slice::initActors() { - imgToMapFilter->SetInputData(originalVolume); - - if (lut == nullptr) { - imgToMapFilter->SetLookupTable(nullptr); - } - else { - imgToMapFilter->SetLookupTable(lut); - } - - image2DChangeInfo->SetInputData(imgToMapFilter->GetOutput()); - image2DChangeInfo->SetOutputOrigin(0.0, 0.0, 0.0); - image2DReslicer->SetInputConnection(image2DChangeInfo->GetOutputPort()); - image2DReslicer->SetInformationInput(imgToMapFilter->GetOutput()); - image2DReslicer->AutoCropOutputOn(); - image2DReslicer->SetOutputOriginToDefault(); - image2DReslicer->SetOutputExtentToDefault(); - image2DReslicer->SetOutputSpacingToDefault(); - - /* 3D Actor case: directly pluged to the output of imgToMapFilter */ - image3DActor->GetMapper()->SetInputConnection(imgToMapFilter->GetOutputPort()); - image3DActor->InterpolateOn(); - - // 2D actors - image2DActor->GetMapper()->SetInputConnection(imgToMapFilter->GetOutputPort()); - image2DActor->InterpolateOn(); - - // Pick plane - updatePickPlane(); - - pickPlaneMapper->SetInputConnection(pickPlane->GetOutputPort()); - pickPlaneActor->SetMapper(pickPlaneMapper); - pickPlaneActor->GetProperty()->SetRepresentationToWireframe(); - - switch (sliceOrientation) { - default: - break; - case ARBITRARY: - pickPlaneActor->GetProperty()->SetColor(1.0, 1.0, 0.0); - pickPlaneActor->GetProperty()->SetEdgeColor(1.0, 1.0, 1.0); - break; - case AXIAL: - pickPlaneActor->GetProperty()->SetColor(0.0, 0.0, 1.0); - pickPlaneActor->GetProperty()->SetEdgeColor(0.0, 0.0, 1.0); - break; - case CORONAL: - pickPlaneActor->GetProperty()->SetColor(0.0, 1.0, 0.0); - pickPlaneActor->GetProperty()->SetEdgeColor(0.0, 1.0, 0.0); - break; - case SAGITTAL: - pickPlaneActor->GetProperty()->SetColor(1.0, 0.0, 0.0); - pickPlaneActor->GetProperty()->SetEdgeColor(1.0, 0.0, 0.0); - break; - } - - pickPlaneActor->GetProperty()->SetOpacity(1.0); - pickPlaneActor->GetProperty()->SetLineWidth(2.0); - - // Orientation 2D Axes - init2DAxesActor(); - update2DAxesActorPosition(); -} - - // -------------------- setImageWorldTransform -------------------- void Slice::setImageWorldTransform(vtkSmartPointer transform) { image3DActor->SetUserTransform(transform); } - -// -------------------- get2DImageActor -------------------- -vtkSmartPointer Slice::get2DImageActor() const { - return image2DActor; -} - -// -------------------- get3DImageActor -------------------- -vtkSmartPointer Slice::get3DImageActor() const { - return image3DActor; -} - -// -------------------- getPickPlaneActor -------------------- -vtkSmartPointer Slice::getPickPlaneActor() const { - return pickPlaneActor; -} - // -------------------- pixelPicked -------------------- void Slice::pixelPicked(double x, double y, double z) { return; @@ -259,48 +137,15 @@ void Slice::volumeToReslicedCoords(const double* xyz, double* ijk) { ijk[2] = xyz[2] / originalSpacing[2]; } -// -------------------- updatePickPlane -------------------- -void Slice::updatePickPlane() { - // Be carefull, The center of the first voxel (0,0,0) is displayed at coordinates (0.0, 0.0, 0.0). - // Pixels winthin borders are represented (in 2D) only by their half, quarter or eigth depending on their coordinates. - - double planeXCoord, planeYCoord, planeZCoord; - - switch (sliceOrientation) { - default: - break; - case AXIAL: - planeZCoord = currentSliceIndex * originalSpacing[2]; - pickPlane->SetOrigin(0.0, 0.0, planeZCoord); - pickPlane->SetPoint1(originalSize[0], 0.0, planeZCoord); - pickPlane->SetPoint2(0.0, originalSize[1], planeZCoord); - break; - - case CORONAL: - planeYCoord = currentSliceIndex * originalSpacing[1]; - pickPlane->SetOrigin(0.0, planeYCoord, 0.0); - pickPlane->SetPoint1(originalSize[0], planeYCoord, 0.0); - pickPlane->SetPoint2(0.0, planeYCoord, originalSize[2]); - break; - - case SAGITTAL: - planeXCoord = currentSliceIndex * originalSpacing[0]; - pickPlane->SetOrigin(planeXCoord, 0.0, 0.0); - pickPlane->SetPoint1(planeXCoord, originalSize[1], 0.0); - pickPlane->SetPoint2(planeXCoord, 0.0, originalSize[2]); - break; - } - - pickPlane->UpdateWholeExtent(); -} - // -------------------- getNumberOfSlices -------------------- int Slice::getNumberOfSlices() const { - int nbSlices = 0; - + // Array containing first and last indices of the image in each direction + // 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z + int extent[6]; + originalVolume->GetExtent(extent); + + int nbSlices; switch (sliceOrientation) { - default: - break; case ARBITRARY: nbSlices = max(max(extent[1] - extent[0], extent[3] - extent[2]), extent[5] - extent[4]) + 1; break; @@ -314,6 +159,9 @@ int Slice::getNumberOfSlices() const { case SAGITTAL: nbSlices = extent[1] - extent[0] + 1; break; + default: + nbSlices = 0; + break; } return nbSlices; @@ -330,71 +178,57 @@ void Slice::setSlice(int s) { if (s < 0) { currentSliceIndex = 0.0; } - else if (s >= getNumberOfSlices()) { - currentSliceIndex = getNumberOfSlices() - 1; - } else { - currentSliceIndex = s; + if (s >= getNumberOfSlices()) { + currentSliceIndex = getNumberOfSlices() - 1; + } + else { + currentSliceIndex = s; + } } + // Array containing first and last indices of the image in each direction + // 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z + int extent[6]; + originalVolume->GetExtent(extent); + switch (sliceOrientation) { - default: - break; case ARBITRARY: updateReslice(); break; case AXIAL: - image3DActor->SetDisplayExtent( - extent[0], extent[1], - extent[2], extent[3], - currentSliceIndex, currentSliceIndex); - image2DActor->SetDisplayExtent( - extent[0], extent[1], - extent[2], extent[3], - currentSliceIndex, currentSliceIndex); + case AXIAL_NEURO: + image3DActor->SetDisplayExtent(extent[0], extent[1], extent[2], extent[3], s, s); + image2DActor->SetDisplayExtent(extent[0], extent[1], extent[2], extent[3], s, s); break; - case CORONAL: - image3DActor->SetDisplayExtent( - extent[0], extent[1], - currentSliceIndex, currentSliceIndex, - extent[4], extent[5]); - image2DActor->SetDisplayExtent( - extent[0], extent[1], - currentSliceIndex, currentSliceIndex, - extent[4], extent[5]); + image3DActor->SetDisplayExtent(extent[0], extent[1], s, s, extent[4], extent[5]); + image2DActor->SetDisplayExtent(extent[0], extent[1], s, s, extent[4], extent[5]); break; - case SAGITTAL: - image3DActor->SetDisplayExtent( - currentSliceIndex, currentSliceIndex, - extent[2], extent[3], - extent[4], extent[5]); - image2DActor->SetDisplayExtent( - s, s, - extent[2], extent[3], - extent[4], extent[5]); + image3DActor->SetDisplayExtent(s, s, extent[2], extent[3], extent[4], extent[5]); + image2DActor->SetDisplayExtent(s, s, extent[2], extent[3], extent[4], extent[5]); + break; + default: break; } updatePickPlane(); - update2DAxesActorPosition(); + + // hide pixel actor for now (it should be visible only when user Ctrl-Click on the image + pixelActor->VisibilityOff(); } // -------------------- setSlice -------------------- void Slice::setSlice(double x, double y, double z) { - // At this point, coordinates are expressed in Image coordinate system - + // At this point, coordinates are expressed in the image coordinate system double ijk[3] = {0.0, 0.0, 0.0}; double xyz[3] = {x, y, z}; // Tranform real coordinates to index coordinates volumeToReslicedCoords(xyz, ijk); - // Set pixel position in current slice. - setPixelRealPosition(x, y, z); - switch (sliceOrientation) { default: break; @@ -408,6 +242,12 @@ void Slice::setSlice(double x, double y, double z) { setSlice(rint(ijk[0])); break; } + + // Set pixel position in current slice. + setPixelRealPosition(x, y, z); + + // show the pixel actor + pixelActor->VisibilityOn(); } // -------------------- getNumberOfColors -------------------- @@ -417,15 +257,19 @@ int Slice::getNumberOfColors() const { // -------------------- setPixelRealPosition -------------------- void Slice::setPixelRealPosition(double x, double y, double z) { - updatePixelActorPosition(x, y, z); + updatePixelActor(x, y, z); } +// -------------------- setReslicerTransform -------------------- void Slice::setReslicerTransform(vtkSmartPointer t) { + // store the transformation pointer + // (when the transformation is changed, you need to call updateReslice() in order + // to take the new transformation into account and update the arbitrary slice image resliceTransform = t; } +// -------------------- updateReslice -------------------- void Slice::updateReslice() { - if (sliceOrientation == ARBITRARY) { double wxyz[4]; @@ -449,6 +293,7 @@ void Slice::updateReslice() { updatePickPlane(); } +// -------------------- updateLocalTransformation -------------------- void Slice::updateLocalTransformation() { image2DReslicer->SetOutputDimensionality(3); image2DReslicer->UpdateInformation(); @@ -457,221 +302,343 @@ void Slice::updateLocalTransformation() { image2DReslicer->UpdateInformation(); } +// -------------------- get2DImageActor -------------------- +vtkSmartPointer Slice::get2DImageActor() const { + return image2DActor; +} + +// -------------------- get3DImageActor -------------------- +vtkSmartPointer Slice::get3DImageActor() const { + return image3DActor; +} + +// -------------------- getPixelActor -------------------- +vtkSmartPointer Slice::getPixelActor() { + return pixelActor; +} + +// -------------------- getPickPlaneActor -------------------- +vtkSmartPointer Slice::getPickPlaneActor() const { + return pickPlaneActor; +} + +// -------------------- initActors -------------------- +void Slice::initActors() { + // Image mapper + vtkSmartPointer imgToMapFilter = vtkSmartPointer::New(); + imgToMapFilter->SetInputData(originalVolume); + + // set the lookupTable + imgToMapFilter->SetLookupTable(lut); + + image2DChangeInfo->SetInputData(imgToMapFilter->GetOutput()); + image2DChangeInfo->SetOutputOrigin(0.0, 0.0, 0.0); + image2DReslicer->SetInputConnection(image2DChangeInfo->GetOutputPort()); + image2DReslicer->SetInformationInput(imgToMapFilter->GetOutput()); + image2DReslicer->AutoCropOutputOn(); + image2DReslicer->SetOutputOriginToDefault(); + image2DReslicer->SetOutputExtentToDefault(); + image2DReslicer->SetOutputSpacingToDefault(); + + // the 2D and 3D image actors are directly plugged to the output of imgToMapFilter + image3DActor = vtkSmartPointer::New(); + image3DActor->GetMapper()->SetInputConnection(imgToMapFilter->GetOutputPort()); + image3DActor->InterpolateOn(); + + image2DActor = vtkSmartPointer::New(); + image2DActor->GetMapper()->SetInputConnection(imgToMapFilter->GetOutputPort()); + image2DActor->InterpolateOn(); + + // Pick plane + initPickPlaneActor(); + updatePickPlane(); + + // pixel + initPixelActor(); + updatePixelActor(); +} + +// -------------------- initPickPlaneActor -------------------- +void Slice::initPickPlaneActor() { + // create the picked plane actor 3D geometry + vtkSmartPointer planePoints = vtkSmartPointer::New(); + planePoints->SetNumberOfPoints(8); + + // create zero-positionned points + for (int i = 0; i < planePoints->GetNumberOfPoints(); i++) { + planePoints->InsertPoint(i, 0.0, 0.0, 0.0); + } + + // vtkQuad is defined by the four points (0,1,2,3) in counterclockwise order. + vtkSmartPointer leftQuad = vtkSmartPointer::New(); + leftQuad->GetPointIds()->SetId(0, 0); + leftQuad->GetPointIds()->SetId(1, 1); + leftQuad->GetPointIds()->SetId(2, 2); + leftQuad->GetPointIds()->SetId(3, 3); + + vtkSmartPointer topQuad = vtkSmartPointer::New(); + topQuad->GetPointIds()->SetId(0, 4); + topQuad->GetPointIds()->SetId(1, 5); + topQuad->GetPointIds()->SetId(2, 1); + topQuad->GetPointIds()->SetId(3, 0); + + vtkSmartPointer rightQuad = vtkSmartPointer::New(); + rightQuad->GetPointIds()->SetId(0, 5); + rightQuad->GetPointIds()->SetId(1, 4); + rightQuad->GetPointIds()->SetId(2, 7); + rightQuad->GetPointIds()->SetId(3, 6); + + vtkSmartPointer bottomQuad = vtkSmartPointer::New(); + bottomQuad->GetPointIds()->SetId(0, 3); + bottomQuad->GetPointIds()->SetId(1, 2); + bottomQuad->GetPointIds()->SetId(2, 6); + bottomQuad->GetPointIds()->SetId(3, 7); + + // Create the unstructured grid that includes the two quads + pickPlaneActorPointSet = vtkSmartPointer::New(); + pickPlaneActorPointSet->Allocate(4); + pickPlaneActorPointSet->InsertNextCell(leftQuad->GetCellType(), leftQuad->GetPointIds()); + pickPlaneActorPointSet->InsertNextCell(topQuad->GetCellType(), topQuad->GetPointIds()); + pickPlaneActorPointSet->InsertNextCell(rightQuad->GetCellType(), rightQuad->GetPointIds()); + pickPlaneActorPointSet->InsertNextCell(bottomQuad->GetCellType(), bottomQuad->GetPointIds()); + pickPlaneActorPointSet->SetPoints(planePoints); + + // Create the corresponding mapper + vtkSmartPointer pickPlaneMapper = vtkSmartPointer::New(); + pickPlaneMapper->SetInputData(pickPlaneActorPointSet); + + // instantiate the actor + pickPlaneActor = vtkSmartPointer::New(); + pickPlaneActor->SetMapper(pickPlaneMapper); + pickPlaneActor->GetProperty()->SetAmbient(1.0); + pickPlaneActor->GetProperty()->SetDiffuse(1.0); + + // Update pixel actor properties + switch (sliceOrientation) { + case ARBITRARY: + pickPlaneActor->GetProperty()->SetColor(1.0, 1.0, 0.0); + break; + case AXIAL_NEURO: + pickPlaneActor->GetProperty()->SetColor(0.0, 0.0, 1.0); + break; + case AXIAL: + pickPlaneActor->GetProperty()->SetColor(0.0, 0.0, 1.0); + break; + case CORONAL: + pickPlaneActor->GetProperty()->SetColor(0.0, 1.0, 0.0); + break; + case SAGITTAL: + pickPlaneActor->GetProperty()->SetColor(1.0, 0.0, 0.0); + break; + default: + pickPlaneActor->GetProperty()->SetColor(1.0, 1.0, 1.0); + break; + } + pickPlaneActor->GetProperty()->SetLineWidth(1.0); + pickPlaneActor->GetProperty()->SetRepresentationToWireframe(); + + //-- pickPlaneActor can not be picked + pickPlaneActor->PickableOff(); + + // by default, the plane actor is always visible + pickPlaneActor->VisibilityOn(); +} // -------------------- initPixelActor -------------------- void Slice::initPixelActor() { + // create the pixel actor 3D geometry + vtkSmartPointer pixelPoints = vtkSmartPointer::New(); + pixelPoints->SetNumberOfPoints(8); + + // create zero-positionned points + for (int i = 0; i < pixelPoints->GetNumberOfPoints(); i++) { + pixelPoints->InsertPoint(i, 0.0, 0.0, 0.0); + } + + // vtkQuad is defined by the four points (0,1,2,3) in counterclockwise order. + vtkSmartPointer verticalQuad = vtkSmartPointer::New(); + verticalQuad->GetPointIds()->SetId(0, 0); + verticalQuad->GetPointIds()->SetId(1, 1); + verticalQuad->GetPointIds()->SetId(2, 2); + verticalQuad->GetPointIds()->SetId(3, 3); + + vtkSmartPointer horizontalQuad = vtkSmartPointer::New(); + horizontalQuad->GetPointIds()->SetId(0, 4); + horizontalQuad->GetPointIds()->SetId(1, 5); + horizontalQuad->GetPointIds()->SetId(2, 6); + horizontalQuad->GetPointIds()->SetId(3, 7); + + // Create the unstructured grid that includes the two quads + pixelActorPointSet = vtkSmartPointer::New(); + pixelActorPointSet->Allocate(2); + pixelActorPointSet->InsertNextCell(horizontalQuad->GetCellType(), horizontalQuad->GetPointIds()); + pixelActorPointSet->InsertNextCell(verticalQuad->GetCellType(), verticalQuad->GetPointIds()); + pixelActorPointSet->SetPoints(pixelPoints); + + // Create the corresponding mapper + vtkSmartPointer pixelMapper = vtkSmartPointer::New(); + pixelMapper->SetInputData(pixelActorPointSet); + + // instantiate the actor + pixelActor = vtkSmartPointer::New(); + pixelActor->SetMapper(pixelMapper); + pixelActor->GetProperty()->SetAmbient(1.0); + pixelActor->GetProperty()->SetDiffuse(1.0); + + // Update pixel actor properties + switch (sliceOrientation) { + case ARBITRARY: + pixelActor->GetProperty()->SetColor(1.0, 1.0, 0.0); + break; + case AXIAL_NEURO: + pixelActor->GetProperty()->SetColor(0.0, 0.0, 1.0); + break; + case AXIAL: + pixelActor->GetProperty()->SetColor(0.0, 0.0, 1.0); + break; + case CORONAL: + pixelActor->GetProperty()->SetColor(0.0, 1.0, 0.0); + break; + case SAGITTAL: + pixelActor->GetProperty()->SetColor(1.0, 0.0, 0.0); + break; + default: + pixelActor->GetProperty()->SetColor(1.0, 1.0, 1.0); + break; + } + pixelActor->GetProperty()->SetLineWidth(1.0); + pixelActor->GetProperty()->SetRepresentationToWireframe(); + + //-- pixelActor can not be picked + pixelActor->PickableOff(); + + // by default, the pixel actor is not visible (it should be only visible when user Ctrl-Click on the image) + pixelActor->VisibilityOff(); +} + +// -------------------- updatePickPlane -------------------- +void Slice::updatePickPlane() { + // Be careful, the center of the first voxel (0,0,0) is displayed at coordinates (0.0, 0.0, 0.0). + // Pixels within borders are represented (in 2D) only by their half, quarter or eigth depending on their coordinates. + // see also bug #65 "Unexpected image behaviors in Viewers" + + // update the point positions + double sliceBackPlane, sliceFrontPlane; + switch (sliceOrientation) { + case AXIAL_NEURO: + case AXIAL: + sliceBackPlane = currentSliceIndex*originalSpacing[2] - originalSpacing[2] / 2.0; + sliceFrontPlane = currentSliceIndex*originalSpacing[2] + originalSpacing[2] / 2.0; + pickPlaneActorPointSet->GetPoints()->SetPoint(0, 0.0, 0.0, sliceBackPlane); + pickPlaneActorPointSet->GetPoints()->SetPoint(1, 0.0, 0.0, sliceFrontPlane); + pickPlaneActorPointSet->GetPoints()->SetPoint(2, 0.0, originalSize[1], sliceFrontPlane); + pickPlaneActorPointSet->GetPoints()->SetPoint(3, 0.0, originalSize[1], sliceBackPlane); + pickPlaneActorPointSet->GetPoints()->SetPoint(4, originalSize[0], 0.0, sliceBackPlane); + pickPlaneActorPointSet->GetPoints()->SetPoint(5, originalSize[0], 0.0, sliceFrontPlane); + pickPlaneActorPointSet->GetPoints()->SetPoint(6, originalSize[0], originalSize[1], sliceFrontPlane); + pickPlaneActorPointSet->GetPoints()->SetPoint(7, originalSize[0], originalSize[1], sliceBackPlane); + break; + + case CORONAL: + sliceBackPlane = currentSliceIndex*originalSpacing[1] - originalSpacing[1] / 2.0; + sliceFrontPlane = currentSliceIndex*originalSpacing[1] + originalSpacing[1] / 2.0; + pickPlaneActorPointSet->GetPoints()->SetPoint(0, 0.0, sliceBackPlane, 0.0); + pickPlaneActorPointSet->GetPoints()->SetPoint(1, 0.0, sliceFrontPlane, 0.0); + pickPlaneActorPointSet->GetPoints()->SetPoint(2, 0.0, sliceFrontPlane, originalSize[2]); + pickPlaneActorPointSet->GetPoints()->SetPoint(3, 0.0, sliceBackPlane, originalSize[2]); + pickPlaneActorPointSet->GetPoints()->SetPoint(4, originalSize[0], sliceBackPlane, 0.0); + pickPlaneActorPointSet->GetPoints()->SetPoint(5, originalSize[0], sliceFrontPlane, 0.0); + pickPlaneActorPointSet->GetPoints()->SetPoint(6, originalSize[0], sliceFrontPlane, originalSize[2]); + pickPlaneActorPointSet->GetPoints()->SetPoint(7, originalSize[0], sliceBackPlane, originalSize[2]); + break; + + case SAGITTAL: + sliceBackPlane = currentSliceIndex*originalSpacing[0] - originalSpacing[0] / 2.0; + sliceFrontPlane = currentSliceIndex*originalSpacing[0] + originalSpacing[0] / 2.0; + pickPlaneActorPointSet->GetPoints()->SetPoint(0, sliceBackPlane, 0.0, 0.0); + pickPlaneActorPointSet->GetPoints()->SetPoint(1, sliceFrontPlane, 0.0, 0.0); + pickPlaneActorPointSet->GetPoints()->SetPoint(2, sliceFrontPlane, 0.0, originalSize[2]); + pickPlaneActorPointSet->GetPoints()->SetPoint(3, sliceBackPlane, 0.0, originalSize[2]); + pickPlaneActorPointSet->GetPoints()->SetPoint(4, sliceBackPlane, originalSize[1], 0.0); + pickPlaneActorPointSet->GetPoints()->SetPoint(5, sliceFrontPlane, originalSize[1], 0.0); + pickPlaneActorPointSet->GetPoints()->SetPoint(6, sliceFrontPlane, originalSize[1], originalSize[2]); + pickPlaneActorPointSet->GetPoints()->SetPoint(7, sliceBackPlane, originalSize[1], originalSize[2]); + break; + default: + break; + } + // Needed to notify the vtk pipeline of the change in the geometry (and therefore update the actor) + pickPlaneActorPointSet->Modified(); +} + +// ----------------- updatePixelActorPosition ----------------- +void Slice::updatePixelActor() { double xMin = 0.0; double xMax = originalSize[0]; double yMin = 0.0; double yMax = originalSize[1]; double zMin = 0.0; double zMax = originalSize[2]; - // instantiate the actor - pixelActor = vtkSmartPointer::New(); - // Draw the bounding box around the center of the slice - updatePixelActorPosition(xMin + (xMax - xMin) / 2, yMin + (yMax - yMin) / 2, zMin + (zMax - zMin) / 2); + updatePixelActor(xMin + (xMax - xMin) / 2, yMin + (yMax - yMin) / 2, zMin + (zMax - zMin) / 2); } -// ----------------- updatePixelActorPosition ----------------- -void Slice::updatePixelActorPosition(double x, double y, double z) { - // if the interactive viewer is not refreshed/displayed, the pixelActor is null. In this case, no point to update it - if (pixelActor) { - // Create the cloud points used to describe the bounding box around - vtkSmartPointer pixelPoints = vtkSmartPointer::New(); - pixelPoints->SetNumberOfPoints(4); - // Those points describes the (red / blue / green) cross around the picked pixel. - double planeZCoord, planeYCoord, planeXCoord; - - switch (sliceOrientation) { - case AXIAL: - planeZCoord = z - (originalSpacing[2] / 2.0); - // 0 -> Anterior Point - pixelPoints->InsertPoint(0, x, 0.0, planeZCoord); - // 1 -> Posterior Point - pixelPoints->InsertPoint(1, x, originalSize[1], planeZCoord); - // 2 -> Right Point - pixelPoints->InsertPoint(2, 0.0, y, planeZCoord); - // 3 -> Left Point - pixelPoints->InsertPoint(3, originalSize[0], y, planeZCoord); - break; - - case CORONAL: - planeYCoord = y - (originalSpacing[1] / 2.0); - // 0 -> Superior Point - pixelPoints->InsertPoint(0, x, planeYCoord, originalSize[2]); - // 1 -> Inferior Point - pixelPoints->InsertPoint(1, x, planeYCoord, 0.0); - // 2 -> Right Point - pixelPoints->InsertPoint(2, 0.0, planeYCoord, z); - // 3 -> Left Point - pixelPoints->InsertPoint(3, originalSize[0], planeYCoord, z); - break; - - case SAGITTAL: - planeXCoord = x + (originalSpacing[0] / 2.0); - // 0 -> Superior Point - pixelPoints->InsertPoint(0, planeXCoord, y, originalSize[2]); - // 1 -> Inferior Point - pixelPoints->InsertPoint(1, planeXCoord, y, 0.0); - // 2 -> Anterior Point - pixelPoints->InsertPoint(2, planeXCoord, 0.0, z); - // 3 -> Posterior Point - pixelPoints->InsertPoint(3, planeXCoord, originalSize[1], z); - break; - - default: - pixelPoints->InsertPoint(0, 0.0, 0.0, 0.0); - pixelPoints->InsertPoint(1, 0.0, 0.0, 0.0); - pixelPoints->InsertPoint(2, 0.0, 0.0, 0.0); - pixelPoints->InsertPoint(3, 0.0, 0.0, 0.0); - break; - } +void Slice::updatePixelActor(double x, double y, double z) { + // update the point positions + double sliceHalfThickness; + switch (sliceOrientation) { + case AXIAL_NEURO: + case AXIAL: + sliceHalfThickness = originalSpacing[2] / 2.0; + // vertical quad + pixelActorPointSet->GetPoints()->SetPoint(0, x, 0.0, z - sliceHalfThickness); + pixelActorPointSet->GetPoints()->SetPoint(1, x, 0.0, z + sliceHalfThickness); + pixelActorPointSet->GetPoints()->SetPoint(2, x, originalSize[1], z + sliceHalfThickness); + pixelActorPointSet->GetPoints()->SetPoint(3, x, originalSize[1], z - sliceHalfThickness); + // horizontal quad + pixelActorPointSet->GetPoints()->SetPoint(4, 0.0, y, z - sliceHalfThickness); + pixelActorPointSet->GetPoints()->SetPoint(5, 0.0, y, z + sliceHalfThickness); + pixelActorPointSet->GetPoints()->SetPoint(6, originalSize[0], y, z + sliceHalfThickness); + pixelActorPointSet->GetPoints()->SetPoint(7, originalSize[0], y, z - sliceHalfThickness); + break; - // Create the lines crossing the picked pixel - vtkSmartPointer vLine = vtkSmartPointer::New(); - vLine->GetPointIds()->SetId(0, 0); - vLine->GetPointIds()->SetId(1, 1); - - vtkSmartPointer hLine = vtkSmartPointer::New(); - hLine->GetPointIds()->SetId(0, 2); - hLine->GetPointIds()->SetId(1, 3); - - // Create the unstructured grid according to these 2 lines and cloup points - vtkSmartPointer aPixelGrid = vtkSmartPointer::New(); - aPixelGrid->Allocate(2, 2); - aPixelGrid->InsertNextCell(hLine->GetCellType(), hLine->GetPointIds()); - aPixelGrid->InsertNextCell(vLine->GetCellType(), vLine->GetPointIds()); - aPixelGrid->SetPoints(pixelPoints); - - // Create the corresponding mapper - vtkSmartPointer aPixelMapper = vtkSmartPointer::New(); - aPixelMapper->SetInputData(aPixelGrid); - - pixelActor->SetMapper(aPixelMapper); - pixelActor->GetProperty()->SetAmbient(1.0); - pixelActor->GetProperty()->SetDiffuse(1.0); - - // Update pixel actor properties - switch (sliceOrientation) { - default: - case AXIAL_NEURO: - pixelActor->GetProperty()->SetColor(1.0, 1.0, 0.0); - break; - case AXIAL: - pixelActor->GetProperty()->SetColor(0.0, 0.0, 1.0); - break; - case CORONAL: - pixelActor->GetProperty()->SetColor(0.0, 1.0, 0.0); - break; - case SAGITTAL: - pixelActor->GetProperty()->SetColor(1.0, 0.0, 0.0); - break; - } + case CORONAL: + sliceHalfThickness = originalSpacing[1] / 2.0; + // vertical quad + pixelActorPointSet->GetPoints()->SetPoint(0, x, y - sliceHalfThickness, originalSize[2]); + pixelActorPointSet->GetPoints()->SetPoint(1, x, y + sliceHalfThickness, originalSize[2]); + pixelActorPointSet->GetPoints()->SetPoint(2, x, y + sliceHalfThickness, 0.0); + pixelActorPointSet->GetPoints()->SetPoint(3, x, y - sliceHalfThickness, 0.0); + // horizontal quad + pixelActorPointSet->GetPoints()->SetPoint(4, 0.0, y - sliceHalfThickness, z); + pixelActorPointSet->GetPoints()->SetPoint(5, 0.0, y + sliceHalfThickness, z); + pixelActorPointSet->GetPoints()->SetPoint(6, originalSize[0], y + sliceHalfThickness, z); + pixelActorPointSet->GetPoints()->SetPoint(7, originalSize[0], y - sliceHalfThickness, z); + break; - pixelActor->GetProperty()->SetLineWidth(1.0); -// pixelActor->SetPosition( 0.0, 0.0, 0.0 ); - //-- pixelActor can not be picked - pixelActor->PickableOff(); + case SAGITTAL: + sliceHalfThickness = originalSpacing[0] / 2.0; + // vertical quad + pixelActorPointSet->GetPoints()->SetPoint(0, x - sliceHalfThickness, y, originalSize[2]); + pixelActorPointSet->GetPoints()->SetPoint(1, x + sliceHalfThickness, y, originalSize[2]); + pixelActorPointSet->GetPoints()->SetPoint(2, x + sliceHalfThickness, y, 0.0); + pixelActorPointSet->GetPoints()->SetPoint(3, x - sliceHalfThickness, y, 0.0); + // horizontal quad + pixelActorPointSet->GetPoints()->SetPoint(4, x - sliceHalfThickness, 0.0, z); + pixelActorPointSet->GetPoints()->SetPoint(5, x + sliceHalfThickness, 0.0, z); + pixelActorPointSet->GetPoints()->SetPoint(6, x + sliceHalfThickness, originalSize[1], z); + pixelActorPointSet->GetPoints()->SetPoint(7, x - sliceHalfThickness, originalSize[1], z); + break; + default: + break; } - // else no point to update the pixelActor + // Needed to notify the vtk pipeline of the change in the geometry (and therefore update the actor) + pixelActorPointSet->Modified(); } -// -------------------- -------------------- -vtkSmartPointer Slice::getPixelActor() { - if (!pixelActor) { - initPixelActor(); - } - return pixelActor; -} - -// -------------------- -------------------- -void Slice::init2DAxesActor() { - axes2DActor = vtkSmartPointer::New(); - axes2DActor->SetShaftTypeToCylinder(); - axes2DActor->SetXAxisLabelText("x"); - axes2DActor->SetYAxisLabelText("y"); - axes2DActor->SetZAxisLabelText("z"); - axes2DActor->SetTotalLength(20.0, 20.0, 20.0); // 10 unit length, mm ? - vtkSmartPointer axeXTextProp = vtkSmartPointer::New(); - axeXTextProp->SetFontSize(10); - axeXTextProp->BoldOn(); - axeXTextProp->ItalicOn(); - axeXTextProp->ShadowOff(); - axeXTextProp->SetFontFamilyToArial(); - // remove the autoscaling so that the font size is smaller than default autoscale - axes2DActor->GetXAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone(); - // set the color - axes2DActor->GetXAxisCaptionActor2D()->SetCaptionTextProperty(axeXTextProp); - // set the color - axes2DActor->GetXAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(1, 0.3, 0.3); - // make sure the label can be hidden by any geometry, like the axes -// axes2DActor->GetXAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground(); - - vtkSmartPointer axeYTextProp = vtkSmartPointer::New(); - axeYTextProp->ShallowCopy(axeXTextProp); - axes2DActor->GetYAxisCaptionActor2D()->SetCaptionTextProperty(axeYTextProp); - axes2DActor->GetYAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone(); - axes2DActor->GetYAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(0.3, 1, 0.3); -// axes2DActor->GetYAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground(); - - vtkSmartPointer axeZTextProp = vtkSmartPointer::New(); - axeZTextProp->ShallowCopy(axeXTextProp); - axes2DActor->GetZAxisCaptionActor2D()->SetCaptionTextProperty(axeZTextProp); - axes2DActor->GetZAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone(); - axes2DActor->GetZAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(0.3, 0.3, 1.0); -// axes2DActor->GetZAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground(); - // add the axes (not visible) - - update2DAxesActorPosition(); -} - -// -------------------- -------------------- -void Slice::update2DAxesActorPosition() { - vtkSmartPointer transform = vtkSmartPointer::New(); - transform->Identity(); - - if (axes2DActor) { - double currentX, currentY, currentZ; - // Update pixel actor properties - switch (sliceOrientation) { - - default: - case AXIAL_NEURO: - currentZ = currentSliceIndex * originalSpacing[2] - 0.001; - transform->Translate(0.0, 0.0, currentZ); - axes2DActor->SetUserTransform(transform); - break; - case AXIAL: - currentZ = (currentSliceIndex - 1) * originalSpacing[2]; - transform->Translate(0.0, 0.0, currentZ); - axes2DActor->SetUserTransform(transform); - break; - case CORONAL: - currentY = (currentSliceIndex - 1) * originalSpacing[1]; - transform->Translate(0.0, currentY, 0.0); - axes2DActor->SetUserTransform(transform); - break; - case SAGITTAL: - currentX = (currentSliceIndex + 1) * originalSpacing[0]; - transform->Translate(currentX, 0.0, 0.0); - axes2DActor->SetUserTransform(transform); - break; - } - } - // else no point to update the pixelActor -} -// -------------------- -------------------- -vtkSmartPointer Slice::get2DAxesActor() { - if (! axes2DActor) { - init2DAxesActor(); - } - return axes2DActor; -} -// TODO : put those methods into a dedicated interface //------------------------------- addProp ---------------------------------------- bool Slice::addProp(const QString& name, vtkSmartPointer< vtkProp > prop) { if (!extraProp.contains(name)) { @@ -685,7 +652,6 @@ bool Slice::addProp(const QString& name, vtkSmartPointer< vtkProp > prop) { //------------------------------- getProp ---------------------------------------- vtkSmartPointer< vtkProp > Slice::getProp(const QString& name) { - if (extraProp.contains(name)) { return extraProp.value(name); } diff --git a/sdk/libraries/core/component/Slice.h b/sdk/libraries/core/component/Slice.h index c292dd25d8fe4204738735ca4a73ebfdbcc33f68..e2540e98a0ae6f5949745cfb9a3098f744ba93ed 100644 --- a/sdk/libraries/core/component/Slice.h +++ b/sdk/libraries/core/component/Slice.h @@ -31,38 +31,50 @@ #include "InterfaceBitMap.h" // -- vtk stuff -#include +#include #include -#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include namespace camitk { /** * @ingroup group_sdk_libraries_core_component * * @brief - * Display a slice (i.e. an image or BitMap) of an @ref camitk::ImageComponent "ImageComponent" + * Display a slice (i.e. an image or BitMap) of an @ref camitk::ImageComponent "ImageComponent". Helper class. + * + * This class manages the visual representation of one slice of a volume image. + * The slice depends on the orientation and the currently selected slice index. + * + * A slice is represented in 2D and 3D thanks to: + * - image2DActor that provides a 2D representation (the slice in the world reference frame) + * - image3DActor that provides a 3D representation (the slice at this actual 3D position) + * Both are updated thanks to vtkImageActor::SetDisplayExtent. + * + * \note the setImageWorldTransform(...) methods is used to set the user transform of the image 3D actor. * - * Uses vtkImageActor::SetDisplayExtent for 3D and 2D Axial, Coronal and Sagittal representaiton. - * Re-position the camera for the 2D views. + * Slice also manages two other actors to visualize user picking inside the image: + * - the picked plane actor (a wireframe box made of 4 quads around the picked plane) that shows the image border + * - the picked pixel actor (two wireframe quads centered on the currently picked pixel) that shows the position + * of the currently picked pixel. + * These two actors are build using unstructured grids. + * + * The picked plane actor is defined by 8 points surrounding the current image plane. + * The points are set at the coordinates of the (red / blue / green) border of the current image plane. + * To make sure that the borders are visible in any specific orientation, four quads are build from this points. + * Each quad goes out of the image plane on both side (back and front). + * + * The picked pixel actor is also made of 8 points that describes the cross centered on the currently picked pixel. + * The points are set at the coordinates of the (red / blue / green) cross around the picked pixel. + * To make sure that the cross is visible in any specific orientation, two quads are build from this 8 points + * Each quad goes out of the image plane on both side * + * \note (easter egg) hit "t" on any slice viewer and move the camera around to see the quad geometries and + * how these extra actors are build. * + * * \verbatim * 3D Volume 2D Slice * ________ /|\ @@ -101,12 +113,6 @@ namespace camitk { class CAMITK_API Slice : public InterfaceBitMap { public: - /* -------------------------------------------------------------------- */ - /**@{ */ - /**@name Constructors / Destructors */ - /**@{ */ - /* -------------------------------------------------------------------- */ - /** Common slices orientation: axial, sagittal, coronal axial_neuro. * Axial, Sagittal and Coronal orientation are given in Radiologist point of view. * The neurologist point of view is displayed in axial_neuro. @@ -137,17 +143,17 @@ public: ARBITRARY }; + /// @name Constructors / Destructors + /// @{ /// Constructor Slice(vtkSmartPointer volume, SliceOrientation AXIAL_ORIENTATION, vtkSmartPointer lookupTable = nullptr); /// virtual destructor ~Slice() override; - - /* -------------------------------------------------------------------- */ - /**@} */ - /**@name InterfaceBitMap implementation */ - /**@{ */ - /* -------------------------------------------------------------------- */ + /// @} + + /// @name InterfaceBitMap implementation + /// @{ /// set the original volume image data (the source vtkImageData before any reslice) and refresh the vtk pipeline void setOriginalVolume(vtkSmartPointer img) override; @@ -161,31 +167,18 @@ public: /** Return the vtkImageActor (vtkProp) representing a slice to be displayed in the 3D viewers. */ vtkSmartPointer get3DImageActor() const override; - /** Return the vtkActor used to pick pixels in the slices. */ + /** Return the vtkActor visualizing the plane of the slices. */ vtkSmartPointer getPickPlaneActor() const override; - /** Return the vtkActor used to pick pixels in the slices. */ + /** Return the vtkActor visualizing the picked pixels in the slices. */ vtkSmartPointer getPixelActor() override; - /** Return 2D Axes at the proper slice origin */ - virtual vtkSmartPointer get2DAxesActor(); - /** This method is called when the associated plane has been picked in the InteractiveViewer, * the given coordinates is position where the plane was picked. */ void pixelPicked(double, double, double) override; - /** Compute the volume coordinates (xyz) from the resliced coordinates (ijk) - * @param ijk: given resliced coordiantes (generally from a pixel picked in the pickPlane) - * @param xyz: output (should be allocated before calling this function) - * volume coordinates (with image at the origin in RAI convention) computed from the input - */ - /// Todo: put this method in abstract slice - void reslicedToVolumeCoords(const double* ijk, double* xyz); - - /// Todo, idem... - void volumeToReslicedCoords(const double* xyz, double* ijk); - + /// update the position of the plane surrounding the currently selected slice void updatePickPlane() override; /** Return the number of slices in the image data set. */ @@ -211,18 +204,28 @@ public: /// move the pixel selection green indicator (pixelActor) to the given real position void setPixelRealPosition(double, double, double) override; - void updateReslice() override; + /** For an arbitrary slice: update the image depending on the current value of the reslice transform */ + virtual void updateReslice() override; - void setReslicerTransform(vtkSmartPointer) override; - - void updateLocalTransformation(); + /** Set the pointer to the image transformation. + * this should be done once, at initialization, using the frame transformation (getTransform) + */ + virtual void setReslicerTransform(vtkSmartPointer) override; /// get the current image data vtkSmartPointer getImageData() const override; - // BEGIN TODO : put all of this into a dedicated interface - /// The additional map for prop (include at least "label" and "glyph") - QMap > extraProp; + // @} + + /// @name manage extra prop associated with a Slice + /// @{ + /// TODO + /// - put all this management into a dedicated interface + /// - remove it from InterfaceBitMap and InterfaceGeometry + /// - remove it from Slice and Geometry helper classes + /// - create a new associated helper class + /// - update Component class and all other code using it (if needed) + /// Note : beware that Geometry requires this to manage to at least "label" and "glyph" extra actors /// Return the vtkProp (actors, volumes and annotations) corresponding to the given name vtkSmartPointer getProp(const QString&) override; @@ -242,15 +245,14 @@ public: * @return true if effictively done */ bool removeProp(const QString&) override; - // END TODO + + /// @} protected: - /* -------------------------------------------------------------------- */ - /**@} */ - /**@name Protected utility methods */ - /**@{ */ - /* -------------------------------------------------------------------- */ + + /// @name Protected utility methods + /// @{ /** Initialize Attributes */ virtual void init(); @@ -258,11 +260,20 @@ protected: /** Initialize actors and everything they need.*/ virtual void initActors(); - /* -------------------------------------------------------------------- */ - /**@} */ - /**@name Attributes / Members of the class */ - /**@{ */ - /* ---------------------------------------------------------------------*/ + /** Compute the volume coordinates (xyz) from the resliced coordinates (ijk) + * @param ijk: given resliced coordinates (generally from a pixel picked in the pickPlane) + * @param xyz: output (should be allocated before calling this function) volume coordinates (with image at the origin in RAI convention) computed from the input + */ + void reslicedToVolumeCoords(const double* ijk, double* xyz); + + /// Compute the resliced coordinates (ijk) from the volume coordinates (xyz) + /// @param xyz: given volume coordinates (with image at the origin in RAI convention) + /// @param ijk: output (should be allocated before calling this function) resliced coordinates computed from the input + void volumeToReslicedCoords(const double* xyz, double* ijk); + /// @} + + /// @name Attributes / Members of the class + /// @{ /** Direction of the reslice */ SliceOrientation sliceOrientation; @@ -270,18 +281,11 @@ protected: /** Smart pointer to the original volume to reslice (input of the vtk pipeline) */ vtkSmartPointer originalVolume; - /// Table containing first and last indices of the image in each direction - /// 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z - int extent[6]; - - /** Keep track of the slice number */ + /** Keep track of the slice number */ int currentSliceIndex; /// Common lookup table - vtkSmartPointer lut; - - /** Original volume dimensions in number of voxels (x, y and z) */ - int originalDimensions[3]; + vtkSmartPointer lut; /** Voxel size of the original image volume. Used to compute point coordinates between real world and index world.*/ double originalSpacing[3]; @@ -289,39 +293,43 @@ protected: /** Real size (originalDimension * originalSpacing in x, y and z) of the original image */ double originalSize[3]; - vtkSmartPointer transformReslicer; - vtkSmartPointer resliceTransform; - - /// To be able to extract a slice - vtkSmartPointer imgToMapFilter; - /// 3D actor - vtkSmartPointer image3DActor; + vtkSmartPointer image3DActor; /// 2D actor - vtkSmartPointer image2DActor; - /**@} */ - - vtkSmartPointer image2DChangeInfo; - - vtkSmartPointer image2DReslicer; - - - /**@} */ - /**@name Uses for picking */ - /**@{ */ + vtkSmartPointer image2DActor; + /// @} + + /// @name Management of the arbitrary slice + /// @{ + + /// Transformations required to compute the arbitrary slice inside the volume + /// this can be any transform, unlike the image orientation + vtkSmartPointer transformReslicer; + + /// Transformation relative to the 3D image actor (it is not parallel to one + /// of the main axe, but has a specific rotation) + vtkSmartPointer resliceTransform; - /** A plane used for picking. This plane has the same size and position as the displayed slice. However, it is a - * real vtkActor that can be easily picked (using 'p' ot 'Ctrl+left click'). */ - vtkSmartPointer pickPlane; + /// update the 2D reslicer + void updateLocalTransformation(); + + /// This is required for the arbitrary slice. + /// It builds a simple image from the original image + /// that has default origin, extent and spacing + vtkSmartPointer image2DChangeInfo; - /** Mapper of the the pickPlane. */ - vtkSmartPointer pickPlaneMapper; + /// The image reslicer computes the arbitrary slice pixels + vtkSmartPointer image2DReslicer; - /** Actor representing the pickPlane. */ - vtkSmartPointer pickPlaneActor; + /// @} + /// @name Used to visualize the current picking + /// @{ + /// init the pick plane actor + void initPickPlaneActor(); + /** * Init the pixel actor for pixel picking */ @@ -337,18 +345,32 @@ protected: * The depth value of the selected pixel. In the plane, it's always +/- 0.01 in order to the pixel actor to be * visible over the slice. **/ - void updatePixelActorPosition(double x, double y, double z); + void updatePixelActor(double x, double y, double z); + + /// update the pixel actor to the middle of the current slice + void updatePixelActor(); + /** Actor representing the pickPlane. */ + vtkSmartPointer pickPlaneActor; + /// the pick plane actor unstructured grid + vtkSmartPointer pickPlaneActorPointSet; + /** Actor representing a pixel, displayed over the image. */ vtkSmartPointer pixelActor; + /// the pixel actor unstructured grid + vtkSmartPointer pixelActorPointSet; + /// @} + + /// @name manage extra prop associated with a Slice + /// @{ + /// TODO see extra prop management method section - void update2DAxesActorPosition(); - void init2DAxesActor(); - vtkSmartPointer axes2DActor; - - + /// The additional map for prop + QMap > extraProp; + + /// @} }; } diff --git a/sdk/libraries/core/viewer/InteractiveViewer.cpp b/sdk/libraries/core/viewer/InteractiveViewer.cpp index 9c48731e8eb2d00ac07050e028fe97110a12bf03..4e459bd837b34fbf40e325b61dce85e255c10cb6 100644 --- a/sdk/libraries/core/viewer/InteractiveViewer.cpp +++ b/sdk/libraries/core/viewer/InteractiveViewer.cpp @@ -77,7 +77,7 @@ #include #include #include - +#include namespace camitk { // ---------------------- singleton ---------------------------- @@ -435,6 +435,7 @@ QWidget* InteractiveViewer::getWidget(QWidget* parent) { addWhatsThisItem("Alt+L", "Toggle light follows camera"); addWhatsThisItem("Alt+P", "Toggle point rendering"); addWhatsThisItem("P", "Pick (flip selection flag for point/cell)"); + addWhatsThisItem("R", "Reset Image Interactor Look Up Table"); addWhatsThisItem("S", "Take a screenshot"); addWhatsThisItem("Alt+S", "Toggle surfacerendering"); addWhatsThisItem("T", "Trackball interaction mode"); @@ -1009,26 +1010,33 @@ void InteractiveViewer::initActions() { // ----------------------initWhatsThis ---------------------------- void InteractiveViewer::initWhatsThis() { - whatsThis = "
3D view interaction Shortcuts

"; + QColor bgColor = frame->palette().color(QPalette::Button); + QColor textColor = frame->palette().color(QPalette::ButtonText); + + whatsThis = "
3D view interaction Shortcuts

"; } // ----------------------startWhatsThisSection ---------------------------- void InteractiveViewer::startWhatsThisSection(const QString& title) { - whatsThis += "
" + title + "
\ - "; + QColor bgColor = frame->palette().color(QPalette::Button); + QColor textColor = frame->palette().color(QPalette::ButtonText); + + whatsThis += "
" + title + "
\ +
"; oddWhatsThis = true; } // ----------------------endWhatsThisSection ---------------------------- void InteractiveViewer::endWhatsThisSection() { - whatsThis += "
\ + whatsThis += "\
"; } // ----------------------addWhatsThisItem ---------------------------- void InteractiveViewer::addWhatsThisItem(const QString& key, const QString& description) { if (oddWhatsThis) { - whatsThis += ""; + QColor bgColor = frame->palette().color(QPalette::Midlight); + whatsThis += ""; } else { whatsThis += ""; @@ -1153,6 +1161,12 @@ void InteractiveViewer::keyPressEvent(QKeyEvent* e) { break; + case Qt::Key_R: + if (myType == SLICE_VIEWER) { + resetLUT(); + } + break; + case Qt::Key_S: // addWhatsThisItem("Alt+S", "Toggle surface rendering"); @@ -1411,9 +1425,6 @@ void InteractiveViewer::keyPressEvent(QKeyEvent* e) { if (c->getPickPlaneActor() == p) { found = true; debugStream << "\t- Picked Plane Actor" << endl; - /* } else if (c->get2DAxesActor() == p) { - found = true; - debugStream << "\t- 2D Axes Actor" << endl; */ } break; @@ -1670,6 +1681,23 @@ void InteractiveViewer::toggleInterpolation() { } } +//------------------------- resetLUT ---------------------------- +void InteractiveViewer::resetLUT() { + foreach (Component* comp, actorMap.keys()) { + if (comp->getRepresentation() == Component::SLICE) { + // reset property LUT + vtkSmartPointer initProp = vtkImageProperty::New(); + initProp->SetInterpolationTypeToLinear(); + initProp->SetAmbient(1.0); + initProp->SetDiffuse(0.0); + comp->get2DImageActor()->SetProperty(initProp); + + // the Component was modified inside the viewer, refresh all its other viewer + comp->refresh(); + } + } +} + //---------------------updateSelectionDisplay------------------------ void InteractiveViewer::updateSelectionDisplay(Component* comp) { InterfaceGeometry::EnhancedModes m = InterfaceGeometry::Normal; diff --git a/sdk/libraries/core/viewer/InteractiveViewer.h b/sdk/libraries/core/viewer/InteractiveViewer.h index 7672e758a814ea731e9e905fc48429ece385e972..c13ff617d60482fe350619d8dc960bdba3b6d9e5 100644 --- a/sdk/libraries/core/viewer/InteractiveViewer.h +++ b/sdk/libraries/core/viewer/InteractiveViewer.h @@ -390,6 +390,9 @@ protected: /// for InterfaceBitMap, toggle the interpolation mode (intern method, not a property because it can only be modified by the keyboard interaction) void toggleInterpolation(); + + /// for InterfaceBitMap, reset the lut that was changed by the image interactor (window and level) + void resetLUT(); /// Update the display of the given Component, according to its selection state and the current HighlightMode. void updateSelectionDisplay(Component*);