Commit 20c7b693 authored by Emmanuel Promayon's avatar Emmanuel Promayon

NEW set angle action is back + code cleaning

GUI is clearly separated from the action itself thanks to action Property
+ few improvement in the GUI itself
parent 7b3979d5
......@@ -26,10 +26,8 @@
#include "AnglesSetterWidget.h"
// CamiTK stuff
#include <InteractiveViewer.h>
#include <Application.h>
#include <MainWindow.h>
#include <ImageComponent.h>
#include <Property.h>
#include <SetAnglesAction.h>
// Qt stuff
#include <QSpinBox>
......@@ -37,86 +35,108 @@
using namespace camitk;
// -------------------- constructor --------------------
AnglesSetterWidget::AnglesSetterWidget(QWidget* parent): QWidget(parent) {
AnglesSetterWidget::AnglesSetterWidget(SetAnglesAction* 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.sliceSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateSlider(int)));
connect(ui.sliceSlider, SIGNAL(valueChanged(int)), this, SLOT(updateSlider(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());
}
else {
// TODO display error message, log ...
}
connect(ui.showArbitraryViewer, SIGNAL(clicked(bool)), this, SLOT(showArbitraryViewer(bool)));
}
// -------------------- destructor --------------------
AnglesSetterWidget::~AnglesSetterWidget() {}
// -------------------- updateGUI --------------------
void AnglesSetterWidget::updateGUI() {
// get values
Property * sliceIndexProperty = myAction->getProperty("Slice Index");
int max = sliceIndexProperty->getAttribute("maximum").toInt();
int currentValue = myAction->property("Slice Index").toInt();
// set ui
ui.sliceSlider->blockSignals(true);
ui.sliceSlider->setMaximum(max);
ui.sliceSlider->setValue(currentValue);
ui.sliceSlider->blockSignals(false);
ui.sliceSpinBox->blockSignals(true);
ui.sliceSpinBox->setMaximum(max);
ui.sliceSpinBox->setValue(currentValue);
ui.sliceSpinBox->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 AnglesSetterWidget::showArbitraryViewer(bool buttonState) {
myAction->showArbitraryViewer(buttonState);
}
// -------------------- updateSlice --------------------
void AnglesSetterWidget::updateSlice(int sliceNumber) {
void AnglesSetterWidget::updateSlider(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();
myAction->setProperty("Slice Index",sliceNumber);
}
// -------------------- xAngleDialValueChanged --------------------
void AnglesSetterWidget::xAngleDialValueChanged(int value) {
InteractiveViewer::getArbitraryViewer()->xAngleChanged(value);
updateAngleSlider(ui.xAngleDial, ui.xAngleValue);
InteractiveViewer::getArbitraryViewer()->refresh();
InteractiveViewer::get3DViewer()->refresh();
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();
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();
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);
if (label == ui.xAngleValue) {
label->setText("Angle X : <tt>" + QString("%1").arg(dial->value(), 3) + "</tt>" + QChar(0x00B0));
// -------------------- updateAngleSliderLabel --------------------
void AnglesSetterWidget::updateAngleSliderLabel(QDial* dial) {
QString angleLetter;
QLabel *label;
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();
}
......@@ -29,6 +29,7 @@
#include "ui_AnglesSetterWidget.h"
#include <QWidget>
class SetAnglesAction;
/**
* @ingroup group_sdk_actions_image_arbitraryslice
......@@ -47,28 +48,36 @@ class AnglesSetterWidget : public QWidget {
public:
/// Default construtor
AnglesSetterWidget(QWidget* parent = 0);
AnglesSetterWidget(SetAnglesAction *, QWidget* parent = 0);
/// Destructor
virtual ~AnglesSetterWidget();
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);
void updateSlider(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);
/// change the visibility of the axial viewer
void showArbitraryViewer(bool);
private:
/// update the angle dialog slider label using the current dial value
void updateAngleSliderLabel(QDial*);
// the GUI class itself
Ui::AnglesSetterWidget ui;
/// the action to refer to
SetAnglesAction* myAction;
};
#endif // AnglesSetterWidget_h
......@@ -64,6 +64,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="maximum">
<number>999999999</number>
</property>
</widget>
</item>
<item>
......@@ -91,57 +97,53 @@
<item>
<layout class="QVBoxLayout" name="rightLayout">
<item>
<layout class="QHBoxLayout" name="xAngleLayout">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="xAngleValue">
<property name="text">
<string>Angle X : 0°</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<string>X Angle: 0°</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</spacer>
</widget>
</item>
<item>
<widget class="QDial" name="xAngleDial">
<property name="maximum">
<number>360</number>
</property>
<property name="notchesVisible">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="yAngleLayout">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="yAngleValue">
<property name="text">
<string>Angle Y : 0°</string>
<string>Y Angle: 0°</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</widget>
</item>
<item>
<widget class="QDial" name="yAngleDial">
......@@ -153,26 +155,29 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="zAngleLayout">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="zAngleValue">
<property name="text">
<string>Angle Z : 0°</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<string>Z Angle: 0°</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</spacer>
</widget>
</item>
<item>
<widget class="QDial" name="zAngleDial">
......@@ -193,6 +198,22 @@
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="showArbitraryViewer">
<property name="text">
<string>Show Arbitrary Viewer</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
......
......@@ -52,7 +52,7 @@ public:
/// Method returning the action extension descrption
virtual QString getDescription() {
return "This extension gathers actions for 2D images arbitrary slice.";
return "This extension gathers actions for manipulating 2D images arbitrary slice.";
};
/// initialize all the actions
......
# Disable this action as long as the Interactive viewer does not consider the arbitrary viewer back.
camitk_extension(ACTION_EXTENSION
DEFINES COMPILE_ARBITRARY_SLICE_API
DEFINES COMPILE_ARBITRARY_SLICE_API
CEP_NAME SDK
DESCRIPTION "Control of the arbitrary slice on image component"
INSTALL_ALL_HEADERS
TEST_APPLICATION ${APPLICATION_TEST_ACTION}
TEST_FILES bassin.msh brain.mha head1.STL liver-smooth.obj Mesh.off mixed_3D_beam.msh ramp1.gipl robot.wrl scissors.obj simple.vtk sinus_skin.vtk sinus_skull.vtk skull1.stl structured.vtk sinus.mhd
ENABLE_AUTO_TEST
TEST_FILES Mesh.off brain.mha sinus.mhd
)
# Recursively update the shiboken path variable containing the CamiTK SDK tree structure
......
......@@ -24,8 +24,12 @@
****************************************************************************/
#include "SetAnglesAction.h"
// includes from CamiTK
// includes from CamiTK
#include <Application.h>
#include <Property.h>
#include <MedicalImageViewer.h>
#include <InteractiveViewer.h>
#include <ImageComponent.h>
#include <Log.h>
#include "AnglesSetterWidget.h"
......@@ -44,21 +48,126 @@ SetAnglesAction::SetAnglesAction(ActionExtension* extension) : Action(extension)
addTag(tr("arbitrary slice"));
addTag(tr("arbitrary"));
addTag(tr("angle"));
setEmbedded(true);
theWidget = nullptr;
currentImageComp = nullptr;
setEmbedded(true);
// the AnglesSetterWidget 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
Property* actionProperty = new Property(tr("Slice Index"), 1, tr("Current selected slice index"), "");
actionProperty->setAttribute("minimum", 0);
actionProperty->setAttribute("maximum", 99);
actionProperty->setAttribute("singleStep", 1);
addParameter(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);
// be notified automatically when the parameters change
setAutoUpdateProperties(true);
}
// --------------- destructor -------------------
SetAnglesAction::~SetAnglesAction() {
delete actionWidget;
}
// --------------- getWidget -------------------
QWidget* SetAnglesAction::getWidget() {
if (!actionWidget) {
theWidget = new AnglesSetterWidget();
actionWidget = new AnglesSetterWidget(this);
// first call : force visibility of the arbitrary viewer
MedicalImageViewer::getInstance()->setVisibleViewer(MedicalImageViewer::VIEWER_ARBITRARY);
}
// reset current pointer so that nothing will happen in the event(..) method
currentImageComp = nullptr;
SingleImageComponent* newCurrentImageComp = dynamic_cast<ImageComponent*>(getTargets().last())->getArbitrarySlices();
// update current action properties safely (i.e. without event(..) to be doing anything on the component
Property* sliceIndexProperty = getProperty("Slice Index");
sliceIndexProperty->setAttribute("maximum", newCurrentImageComp->getNumberOfSlices() - 1);
setProperty("Slice Index", newCurrentImageComp->getSlice());
double rotation[3];
newCurrentImageComp->getTransform()->GetOrientation(rotation);
setProperty("X Angle", rotation[0]);
setProperty("Y Angle", rotation[1]);
setProperty("Z Angle", rotation[2]);
CAMITK_INFO(QString("Z changed to [%1]").arg(rotation[2]))
// now set currentImageComp to the proper value to keep it up-to-date when the GUI will change
currentImageComp = newCurrentImageComp;
// update widget
dynamic_cast<AnglesSetterWidget*>(actionWidget)->updateGUI();
// connect the arbitrary viewer so that a change to its slider can update the "Slice Index" attribute
connect(InteractiveViewer::getArbitraryViewer(), SIGNAL(selectionChanged()), this, SLOT(viewerSliderChanged()));
return actionWidget;
}
// ---------------------- showArbitraryViewer ----------------------------
void SetAnglesAction::showArbitraryViewer(bool visible) {
if (visible) {
// force visibility of the arbitrary viewer
MedicalImageViewer::getInstance()->setVisibleViewer(MedicalImageViewer::VIEWER_ARBITRARY);
}
else {
// hide the arbitrary viewer
MedicalImageViewer::getInstance()->setVisibleViewer(MedicalImageViewer::VIEWER_ALL);
}
}
// ---------------------- viewerSliderChanged ----------------------------
void SetAnglesAction::viewerSliderChanged() {
setProperty("Slice Index", currentImageComp->getSlice());
// update widget
dynamic_cast<AnglesSetterWidget*>(actionWidget)->updateGUI();
}
// ---------------------- event ----------------------------
bool SetAnglesAction::event(QEvent* e) {
if (currentImageComp == nullptr)
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
CAMITK_INFO(QString("[") + changeEvent->propertyName() + QString("] changed to [%2]").arg(property(changeEvent->propertyName()).toInt()))
if (changeEvent->propertyName() == "Slice Index") {
currentImageComp->setSlice(property(changeEvent->propertyName()).toInt());
}
else {
currentImageComp->setTransformRotation(property("X Angle").toInt(), property("Y Angle").toInt(), property("Z Angle").toInt());
}
currentImageComp->refresh();
return true;
}
return theWidget;
// this is important to continue the process if the event is a different one
return QObject::event(e);
}
......@@ -28,7 +28,7 @@
// CamiTK stuff
#include <QObject>
#include <Action.h>
#include <ImageComponent.h>
#include <SingleImageComponent.h>
#include "ArbitrarySliceAPI.h"
......@@ -53,6 +53,12 @@ public:
/// 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);
/// show or hide the arbitrary viewer
void showArbitraryViewer(bool);
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.
......@@ -61,9 +67,13 @@ public slots:
return SUCCESS;
}
protected slots:
/// called when the arbitrary viewer slicer has changed directly
virtual void viewerSliderChanged();
protected:
/// The widget will be filled with parameters
QWidget* theWidget;
// block event during initialization
camitk::SingleImageComponent* currentImageComp;
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment