Commit 4f37ebc3 authored by Emmanuel Promayon's avatar Emmanuel Promayon

Merge branch 'bug/arbitrary-slice' into 'develop'

Bug/arbitrary slice

Closes #61

See merge request !136
parents ee47453f 9d9aabc7
......@@ -34,6 +34,7 @@
// VTK includes
#include <vtkStringArray.h>
#include <vtkPointData.h>
#include <vtkImageChangeInformation.h>
// GDCM includes
#include <vtkGDCMImageReader.h>
......
......@@ -75,7 +75,6 @@ void ApplicationActionExtension::init() {
registerNewAction(ShowSagittalViewer);
registerNewAction(Show3DViewer);
registerNewAction(ShowAllViewers);
// disabled as long as this viewer is not working
// registerNewAction(ShowArbitraryViewer);
registerNewAction(ShowArbitraryViewer);
registerNewAction(LoggerParameters);
}
/*****************************************************************************
* $CAMITK_LICENCE_BEGIN$
*
* CamiTK - Computer Assisted Medical Intervention ToolKit
* (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
*
* Visit http://camitk.imag.fr for more information
*
* This file is part of CamiTK.
*
* CamiTK is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* CamiTK is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License version 3 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with CamiTK. If not, see <http://www.gnu.org/licenses/>.
*
* $CAMITK_LICENCE_END$
****************************************************************************/
#include "AnglesAndTranslationAction.h"
// includes from CamiTK
#include <Application.h>
#include <Property.h>
#include <MedicalImageViewer.h>
#include <InteractiveViewer.h>
#include <ImageComponent.h>
#include <Log.h>
// to get the enum as a string
#include <QMetaEnum>
#include "AnglesAndTranslationWidget.h"
using namespace camitk;
// --------------- Constructor -------------------
AnglesAndTranslationAction::AnglesAndTranslationAction(ActionExtension* extension) : Action(extension) {
// Setting name, description and input component
setName("Modify Angles And Translation");
setDescription(tr("This action allows user to manually modify the orientation and translation of the arbitrary slice."));
setComponent("ImageComponent");
// Setting classification family and tags
setFamily("Arbitrary Slice");
addTag(tr("arbitrary slice"));
addTag(tr("arbitrary"));
addTag(tr("angle"));
setEmbedded(true);
currentImageComp = nullptr;
blockEvent = true;
// the AnglesAndTranslationWidget is going to modify these parameters using the GUI
// but defining them as the action parameters also allows to set them programmatically
// (for instance using another GUI or in a pipeline)
// Setting parameters default values by using properties
addParameter(new Property(tr("Translation"), 0.0, tr("Current translation inside the image"), ""));
Property* actionProperty = actionProperty = new Property(tr("X Angle"), 0, tr("X Angle"), "degree");
actionProperty->setAttribute("minimum", 0);
actionProperty->setAttribute("maximum", 360);
addParameter(actionProperty);
actionProperty = new Property(tr("Y Angle"), 0, tr("Y Angle"), "degree");
actionProperty->setAttribute("minimum", 0);
actionProperty->setAttribute("maximum", 360);
addParameter(actionProperty);
actionProperty = new Property(tr("Z Angle"), 0, tr("Z Angle"), "degree");
actionProperty->setAttribute("minimum", 0);
actionProperty->setAttribute("maximum", 360);
addParameter(actionProperty);
actionProperty = new Property("Visible Viewer", MedicalImageViewer::VIEWER_ARBITRARY, "Visible viewer in the main window", "");
// To set the enum type, use the same name of the enum as declared in the class
actionProperty->setEnumTypeName("LayoutVisibility");
// Add the property as an action parameter
addParameter(actionProperty);
// be notified automatically when the parameters change
setAutoUpdateProperties(true);
}
// --------------- destructor -------------------
AnglesAndTranslationAction::~AnglesAndTranslationAction() {
delete actionWidget;
}
// --------------- getWidget -------------------
QWidget* AnglesAndTranslationAction::getWidget() {
if (!actionWidget) {
actionWidget = new AnglesAndTranslationWidget(this);
}
// update the pointer
currentImageComp = dynamic_cast<ImageComponent*>(getTargets().last())->getArbitrarySlices();
// make sure the arbitrary slice is visible in 3D
currentImageComp->setViewSliceIn3D(true);
// update the properties
update();
return actionWidget;
}
// ---------------------- updateParameters ----------------------------
void AnglesAndTranslationAction::update() {
// update current action properties safely (i.e. without event(..) to be doing anything on the component)
blockEvent = true;
// current translation along the z axis of the arbitrary slice
setProperty("Translation", currentImageComp->getTranslationInVolume() * 100.0);
// update widget
dynamic_cast<AnglesAndTranslationWidget*>(actionWidget)->updateGUI();
// update central viewer
MedicalImageViewer::LayoutVisibility visibleViewer = static_cast<MedicalImageViewer::LayoutVisibility>(property("Visible Viewer").toInt());
MedicalImageViewer::getInstance()->setVisibleViewer(visibleViewer);
// keep property up-to-date when the GUI will change
blockEvent = false;
}
// ---------------------- resetTransform ----------------------------
void AnglesAndTranslationAction::resetTransform() {
currentImageComp->resetTransform();
// Reset the angle to zero
blockEvent = true;
setProperty("X Angle", 0);
setProperty("Y Angle", 0);
setProperty("Z Angle", 0);
blockEvent = false;
update();
currentImageComp->refresh();
}
// ---------------------- event ----------------------------
bool AnglesAndTranslationAction::event(QEvent* e) {
if (currentImageComp == nullptr || blockEvent)
return QObject::event(e);
if (e->type() == QEvent::DynamicPropertyChange) {
e->accept();
QDynamicPropertyChangeEvent* changeEvent = dynamic_cast<QDynamicPropertyChangeEvent*>(e);
if (!changeEvent)
return false;
// do something depending of the property that has changed
if (changeEvent->propertyName() != "Visible Viewer") {
// only translation and rotation angle are of interest here (the "Visible Viewer" changes will automatically be
// taken into account in the update() method
if (changeEvent->propertyName() == "Translation") {
currentImageComp->setTransformTranslation(0.0, 0.0, property(changeEvent->propertyName()).toDouble() / 100.0);
}
else {
currentImageComp->setTransformRotation(property("X Angle").toInt(), property("Y Angle").toInt(), property("Z Angle").toInt());
}
}
// needed as rotation might change the translation value or translation might have been rejected
update();
currentImageComp->refresh();
return true;
}
// this is important to continue the process if the event is a different one
return QObject::event(e);
}
......@@ -22,14 +22,16 @@
*
* $CAMITK_LICENCE_END$
****************************************************************************/
#ifndef SETANGLESACTION_H
#define SETANGLESACTION_H
#ifndef ANGLESANDTRANSLATIONACTION_H
#define ANGLESANDTRANSLATIONACTION_H
// CamiTK stuff
#include <QObject>
#include <Action.h>
#include <ImageComponent.h>
#include <ArbitrarySingleImageComponent.h>
#include <MedicalImageViewer.h>
#include "AnglesSetterWidget.h"
#include "ArbitrarySliceAPI.h"
/**
* @ingroup group_sdk_actions_image_arbitraryslice
......@@ -37,21 +39,27 @@
* @brief
* This action simply display the widget allowing the user to select an angle to orientate the arbitrary slice.
*/
class SetAnglesAction : public camitk::Action {
class ARBITRARY_SLICE_API AnglesAndTranslationAction : public camitk::Action {
Q_OBJECT
public:
/// Default Constructor
SetAnglesAction(camitk::ActionExtension*);
AnglesAndTranslationAction(camitk::ActionExtension*);
/// Default Destructor
virtual ~SetAnglesAction();
virtual ~AnglesAndTranslationAction();
/// Return the arbitrary slice angles setter widget
virtual QWidget* getWidget();
/// manage change in the action parameters (angles and slice number)
virtual bool event(QEvent* e);
// reset transform to identity
void resetTransform();
public slots:
/** This method returns always SUCCESS as the action aims at displaying its widget to be used in order to control the
* arbitrary slice display.
......@@ -59,8 +67,18 @@ public slots:
virtual ApplyStatus apply() {
return SUCCESS;
}
private:
/// update action's parameter using the current image state + update widget GUI
/// + update the central viewer depending on the current value of the "Visible Viewer" property
void update();
/// currently controled image
camitk::ArbitrarySingleImageComponent* currentImageComp;
/// block property changed event (temporarily)
bool blockEvent;
};
#endif // SETANGLESACTION_H
#endif // ANGLESANDTRANSLATIONACTION_H
......@@ -23,102 +23,138 @@
* $CAMITK_LICENCE_END$
****************************************************************************/
#include "AnglesSetterWidget.h"
#include "AnglesAndTranslationWidget.h"
// CamiTK stuff
#include <InteractiveViewer.h>
#include <Application.h>
#include <MainWindow.h>
#include <ImageComponent.h>
// Qt stuff
#include <QSpinBox>
#include <Property.h>
#include <AnglesAndTranslationAction.h>
using namespace camitk;
// -------------------- constructor --------------------
AnglesSetterWidget::AnglesSetterWidget(QWidget* parent): QWidget(parent) {
AnglesAndTranslationWidget::AnglesAndTranslationWidget(AnglesAndTranslationAction* a, QWidget* parent): QWidget(parent) {
myAction = a;
ui.setupUi(this);
// Ui slice number change slot connection
connect(ui.sliceSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateSlice(int)));
connect(ui.sliceSlider, SIGNAL(valueChanged(int)), this, SLOT(updateSlice(int))) ;
connect(ui.translationSpinBox, SIGNAL(valueChanged(double)), this, SLOT(translationSpinBoxChanged(double)));
connect(ui.translationSlider, SIGNAL(valueChanged(int)), this, SLOT(translationSliderChanged(int)));
// Ui angles slot connection
connect(ui.xAngleDial, SIGNAL(valueChanged(int)), this, SLOT(xAngleDialValueChanged(int)));
connect(ui.yAngleDial, SIGNAL(valueChanged(int)), this, SLOT(yAngleDialValueChanged(int)));
connect(ui.zAngleDial, SIGNAL(valueChanged(int)), this, SLOT(zAngleDialValueChanged(int)));
// Initialize the slider
// The action calling this widget must be applyied on only one IMAGE component.
// Therefore, selectedComponents are counted to 5, the top image level component and its slices (axial, coronal, sagital and arbitrary).
// selectedComponent[1] => arbitrary slice of the top level component
ComponentList selectedComponents = Application::getSelectedComponents();
if (selectedComponents.size() > 0) {
ui.sliceSlider->setMaximum(selectedComponents[1]->getNumberOfSlices() - 1);
ui.sliceSlider->setValue(selectedComponents[1]->getSlice());
ui.sliceSpinBox->setMaximum(selectedComponents[1]->getNumberOfSlices() - 1);
ui.sliceSpinBox->setValue(selectedComponents[1]->getSlice());
connect(ui.showArbitraryRadio, SIGNAL(clicked(bool)), this, SLOT(showArbitraryViewer(bool)));
connect(ui.show3DRadio, SIGNAL(clicked(bool)), this, SLOT(show3DViewer(bool)));
connect(ui.showAllRadio, SIGNAL(clicked(bool)), this, SLOT(showAllViewer(bool)));
connect(ui.resetButton, SIGNAL(clicked()), this, SLOT(resetTransform()));
}
// -------------------- destructor --------------------
AnglesAndTranslationWidget::~AnglesAndTranslationWidget() {
}
// -------------------- updateGUI --------------------
void AnglesAndTranslationWidget::updateGUI() {
ui.translationSpinBox->blockSignals(true);
ui.translationSlider->blockSignals(true);
ui.translationSpinBox->setValue(myAction->property("Translation").toDouble());
ui.translationSlider->setValue(myAction->property("Translation").toDouble()*10);
ui.translationSpinBox->blockSignals(false);
ui.translationSlider->blockSignals(false);
ui.xAngleDial->blockSignals(true);
ui.xAngleDial->setValue(myAction->property("X Angle").toInt());
ui.xAngleDial->blockSignals(false);
updateAngleSliderLabel(ui.xAngleDial);
ui.yAngleDial->blockSignals(true);
ui.yAngleDial->setValue(myAction->property("Y Angle").toInt());
ui.yAngleDial->blockSignals(false);
updateAngleSliderLabel(ui.yAngleDial);
ui.zAngleDial->blockSignals(true);
ui.zAngleDial->setValue(myAction->property("Z Angle").toInt());
ui.zAngleDial->blockSignals(false);
updateAngleSliderLabel(ui.zAngleDial);
}
// -------------------- showArbitraryViewer --------------------
void AnglesAndTranslationWidget::showArbitraryViewer(bool buttonState) {
if (buttonState) {
myAction->setProperty("Visible Viewer", camitk::MedicalImageViewer::VIEWER_ARBITRARY);
}
else {
// TODO display error message, log ...
}
// -------------------- show3DViewer --------------------
void AnglesAndTranslationWidget::show3DViewer(bool buttonState) {
if (buttonState) {
myAction->setProperty("Visible Viewer", camitk::MedicalImageViewer::VIEWER_3D);
}
}
// -------------------- destructor --------------------
AnglesSetterWidget::~AnglesSetterWidget() {
;
// -------------------- showAllViewer --------------------
void AnglesAndTranslationWidget::showAllViewer(bool buttonState) {
if (buttonState) {
myAction->setProperty("Visible Viewer", camitk::MedicalImageViewer::VIEWER_ALL);
}
}
// -------------------- updateAngleSliderLabel --------------------
void AnglesAndTranslationWidget::resetTransform() {
myAction->resetTransform();
}
// -------------------- updateSlice --------------------
void AnglesSetterWidget::updateSlice(int sliceNumber) {
// Ui slider and spin box are mapped and thus automatically updated when a change occurs.
InteractiveViewer::getArbitraryViewer()->sliderChanged(sliceNumber);
InteractiveViewer::getArbitraryViewer()->refresh();
InteractiveViewer::get3DViewer()->refresh();
// -------------------- translationSpinBoxChanged --------------------
void AnglesAndTranslationWidget::translationSpinBoxChanged(double value) {
myAction->setProperty("Translation", value);
}
// -------------------- translationSliderChanged --------------------
void AnglesAndTranslationWidget::translationSliderChanged(int value) {
// the slide is from 0 to 1000
myAction->setProperty("Translation", value/10);
}
// -------------------- xAngleDialValueChanged --------------------
void AnglesSetterWidget::xAngleDialValueChanged(int value) {
InteractiveViewer::getArbitraryViewer()->xAngleChanged(value);
updateAngleSlider(ui.xAngleDial, ui.xAngleValue);
InteractiveViewer::getArbitraryViewer()->refresh();
InteractiveViewer::get3DViewer()->refresh();
void AnglesAndTranslationWidget::xAngleDialValueChanged(int value) {
myAction->setProperty("X Angle", value);
updateAngleSliderLabel(ui.xAngleDial);
}
// -------------------- yAngleDialValueChanged --------------------
void AnglesSetterWidget::yAngleDialValueChanged(int value) {
InteractiveViewer::getArbitraryViewer()->yAngleChanged(value);
updateAngleSlider(ui.yAngleDial, ui.yAngleValue);
InteractiveViewer::getArbitraryViewer()->refresh();
InteractiveViewer::get3DViewer()->refresh();
void AnglesAndTranslationWidget::yAngleDialValueChanged(int value) {
myAction->setProperty("Y Angle", value);
updateAngleSliderLabel(ui.yAngleDial);
}
// -------------------- zAngleDialValueChanged --------------------
void AnglesSetterWidget::zAngleDialValueChanged(int value) {
InteractiveViewer::getArbitraryViewer()->zAngleChanged(value);
updateAngleSlider(ui.zAngleDial, ui.zAngleValue);
InteractiveViewer::getArbitraryViewer()->refresh();
InteractiveViewer::get3DViewer()->refresh();
void AnglesAndTranslationWidget::zAngleDialValueChanged(int value) {
myAction->setProperty("Z Angle", value);
updateAngleSliderLabel(ui.zAngleDial);
}
// -------------------- updateAngleSlider --------------------
void AnglesSetterWidget::updateAngleSlider(QDial* dial, QLabel* label) {
dial->blockSignals(true);
dial->setMinimum(0);
dial->setMaximum(360);
dial->blockSignals(false);
// -------------------- updateAngleSliderLabel --------------------
void AnglesAndTranslationWidget::updateAngleSliderLabel(QDial* dial) {
QString angleLetter;
QLabel *label;
if (label == ui.xAngleValue) {
label->setText("Angle X : <tt>" + QString("%1").arg(dial->value(), 3) + "</tt>" + QChar(0x00B0));
if (dial == ui.xAngleDial) {
angleLetter = "X";
label = ui.xAngleValue;
}
else if (label == ui.yAngleValue) {
label->setText("Angle Y : <tt>" + QString("%1").arg(dial->value(), 3) + "</tt>" + QChar(0x00B0));
else if (dial == ui.yAngleDial) {
angleLetter = "Y";
label = ui.yAngleValue;
}
else if (label == ui.zAngleValue) {
label->setText("Angle Z : <tt>" + QString("%1").arg(dial->value(), 3) + "</tt>" + QChar(0x00B0));
else {
// dial == ui.zAngleDial
angleLetter = "Z";
label = ui.zAngleValue;
}
label->setText(angleLetter + QString(" Angle: <tt>%1</tt>").arg(dial->value(), 3) + QChar(0x00B0));
label->update();
}
......@@ -23,12 +23,13 @@
* $CAMITK_LICENCE_END$
****************************************************************************/
#ifndef AnglesSetterWidget_h
#define AnglesSetterWidget_h
#ifndef ANGLESANDTRANSLATIONWIDGET_H
#define ANGLESANDTRANSLATIONWIDGET_H
#include "ui_AnglesSetterWidget.h"
#include "ui_AnglesAndTranslationWidget.h"
#include <QWidget>
class AnglesAndTranslationAction;
/**
* @ingroup group_sdk_actions_image_arbitraryslice
......@@ -42,33 +43,46 @@
*
* Use corresponding .ui file created with Qt Designer.
*/
class AnglesSetterWidget : public QWidget {
class AnglesAndTranslationWidget : public QWidget {
Q_OBJECT
public:
/// Default construtor
AnglesSetterWidget(QWidget* parent = 0);
AnglesAndTranslationWidget(AnglesAndTranslationAction *, QWidget* parent = 0);
/// Destructor
virtual ~AnglesSetterWidget();
virtual ~AnglesAndTranslationWidget();
private:
Ui::AnglesSetterWidget ui;
private:
/// update the angle dialog slider (text + value)
void updateAngleSlider(QDial* dial, QLabel* label);
/// update the UI depending on the action
void updateGUI();
private slots:
/// Update slice number to be displayed
void updateSlice(int sliceNumber);
/// Update the translation value
void translationSpinBoxChanged(double);
void translationSliderChanged(int);
/// Method that update the angle dialog slider (text + value)
void xAngleDialValueChanged(int value);
void yAngleDialValueChanged(int value);
void zAngleDialValueChanged(int value);
void xAngleDialValueChanged(int);
void yAngleDialValueChanged(int);
void zAngleDialValueChanged(int);
/// switch to a specific viewer
void showArbitraryViewer(bool);
void show3DViewer(bool);
void showAllViewer(bool);
void resetTransform();
private:
/// update the angle dialog slider label using the current dial value
void updateAngleSliderLabel(QDial*);
// the GUI class itself
Ui::AnglesAndTranslationWidget ui;
/// the action to refer to
AnglesAndTranslationAction* myAction;
};
#endif // AnglesSetterWidget_h
#endif // ANGLESANDTRANSLATIONWIDGET_H
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AnglesSetterWidget</class>
<widget class="QWidget" name="AnglesSetterWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>289</width>
<height>500</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>500</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>289</width>
<height>538</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="leftLayout">
<item>
<layout class="QHBoxLayout" name="labelLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>