Port of 3D Slicer to Qt
description
Transcript of Port of 3D Slicer to Qt
Port of 3D Slicer to Qt
Julien FinetKitware Inc.Dec. 16th 2009
Background
• 3D Slicer version 3.x use KWWidgets– VTK-style interface to Tk– 3D Slicer 1.x, 2.x used Tk directly
• Qt– Embedded Linux, Mac OS X, Windows, Linux/X11,
Windows CE/Mobile, Symbian, Maemo– LGPL– 600+ classes– Tens of thousands of applications– 15+ millions of users
• NIH Supplement to help with port– 9/17/2009 - 9/16/2011
Qt – How to get Qt
• Required version: Qt 4.6• Building Slicer with Qt
– http://wiki.slicer.org/slicerWiki/index.php/Slicer3:Developers:Projects:QtSlicer/Tutorials/CompileWithQt
• Links: – Doc: http://qt.nokia.com/doc/4.6/index.html– Tutorials:
http://qt.nokia.com/doc/4.6/tutorials.html
Qt – First steps
• Hello World• Signals / Slots• Events vs Signals/Slots• Layouts• What QObject does for you ?
– Parent / child relationship
• Designer• Not only GUI widgets in Qt• Documentation
Qt in Slicer
• Events with KWWidgets1. Fire event
2. Connect
3. Process
void vtkSlicerCamerasGUI::AddGUIObservers(){ … this->ViewSelectorWidget->AddObserver( vtkSlicerNodeSelectorWidget::NodeSelectedEvent, (vtkCommand *)this->GUICallbackCommand ); …}
void vtkSlicerCamerasGUI::ProcessGUIEvents( vtkObject *caller, unsigned long event, void *callData ){ if (vtkSlicerNodeSelectorWidget::SafeDownCast(caller)) { if (event == vtkSlicerNodeSelectorWidget::NodeSelectedEvent) { …}
void vtkSlicerNodeSelectorWidget::ProcessCommand(char *selectedID){ … this->InvokeEvent(vtkSlicerNodeSelectorWidget::NodeSelectedEvent, NULL); …} vtkSlicerNodeSelectorWidget.cxx
vtkSlicerCamerasGUI.cxx
vtkSlicerCamerasGUI.cxx
Qt in Slicer
• Events with Qt1. Fire event
2. Connect
3. Process
void qSlicerCamerasModuleWidget::setup(){ … connect(d->ViewNodeSelector, SIGNAL(currentNodeChanged(vtkMRMLNode*)), this, SLOT(onCurrentViewNodeChanged(vtkMRMLNode*))); …}
void qSlicerCamerasModuleWidget::onCurrentViewNodeChanged(vtkMRMLNode* mrmlNode){ vtkMRMLViewNode* currentViewNode = vtkMRMLViewNode::SafeDownCast(mrmlNode); …}
void qMRMLNodeSelector::nodeIdSelected(int index){ … emit currentNodeChanged(d->MRMLCurrentNode);}
qSlicerCamerasModuleWidget.cxx
qSlicerCamerasModuleWidget.cxx
qMRMLNodeSelector.cxx
Qt and VTK
• qVTKConnect() – Based on
VTK/GUISupport/Qt/vtkEventQtSlotConnect
void qMRMLNodeSelector::addNode(vtkMRMLNode* mrmlNode){ … this->qvtkConnect(mrmlNode, vtkCommand::ModifiedEvent, this, SLOT(onMRMLNodeModified(vtkObject*, void*))); …}
// qVTK includes#include <qVTKObject.h>…class QMRML_WIDGETS_EXPORT qMRMLNodeSelector : public qCTKAddRemoveComboBox{ Q_OBJECT QVTK_OBJECTpublic: … qMRMLNodeSelector.h
qMRMLNodeSelector.cxx
QVTK_OBJECT adds the function qvtkConnect()
Porting Slicer to Qt
• Create Qt/KWW co-existence layer (done!)• Prototype a few modules (done!)• Create an architecture for Qt based modules
(in process)• Train developers (first session at January
2010 Project Week)• Port modules (ongoing through 2010…)• Turn off KWW (by end of 2010?)• Continual Improvement (through end of
supplement and beyond…)
Porting Slicer to Qt
• Slicer: Qt only
• Slicer: KWWidgets + Qt
Executable: Slicer3
Executable: SlicerQT
Modules
• Core Modules: Slicer3/Base/QTCoreModules
– Transforms qSlicerTransformsModule
– …• Loadable Modules: Slicer3/QTModules
– Measurements libqSlicerMeasurementsModule.so
– Volumes libqSlicerVolumesModule.so– …
• CLI Modules– …
Plugin Mechanism
• Previously: itksys::DynamicLoader(dlopen)
• Now: Use the QT Plugins framework…class Q_SLICER_QTMODULES_VOLUMES_EXPORT qSlicerVolumesModule : public qSlicerAbstractLoadableModule{ Q_INTERFACES(qSlicerAbstractLoadableModule);public: …};
…Q_EXPORT_PLUGIN2(qSlicerVolumesModule, qSlicerVolumesModule);…
QPluginLoader loader;loader.setFileName(pluginPath);loader.load();QObject * object = this->Loader.instance();qSlicerAbstractLoadableModule* module = qobject_cast<qSlicerAbstractLoadableModule*>(object); Plugin Loader
Plugin implementation
Plugin header
Modules: Logic + UI
Logic(qSlicerAbstractModuleLogic)
create()create() create()create()
UI (qSlicerAbstractModuleWidget)
Module(qSlicerAbstractModule)
vtkMRMLScene
QWidget
QObject
qSlicer…ModuleWidget.ui
Designer UI file
How to write a loadable module
• Create directories in Slicer3/QTModules– MyModule– MyModule/Resources– MyModule/Resources/UI– MyModule/Resources/Icons (optional)
• Create the files– MyModule/CMakeLists.txt– MyModule/qSlicerMyModule.[h/cxx]– MyModule/qSlicerMyModuleWidget.[h/cxx]– MyModule/qSlicerMyModuleLogic.[h/cxx] (optional)– MyModule/Resources/qSlicerMyModule.qrc (optional)– MyModule/Resources/UI/qSlicerMyModule.ui
MyModule UI – 1 / 4
• Open Qt Designer with QT_PLUGIN_PATH set to “Slicer3-build/bin” – Designing a module UI requires the plugins:
libqCTKWidgets, libqVTKWidgets, libqMRMLWidgets and libqSlicerBaseQTGUI
– Warning: plugins must be compiled under the same mode than Qt (Release vs. Debug)
– On Linux: cd Slicer3-build; python Designer.py– More info on
http://wiki.slicer.org/slicerWiki/index.php/Slicer3:Developers:Projects:QtSlicer/Tutorials/QtDesigner
My Module UI – 2 / 4
• Create a UI form:– MyModule/Resources/UI/qSlicerMyModule.ui
• qSlicerWidget has the signal mrmlSceneChanged()
Qt Designer
MyModule UI – 3 / 4
Drag widgets on the form
Names are important Qt Designer
My Module UI – 4 / 4
Connect widgets together with the signals/slots
Here the MRMLScene of the module is propagated to the NodeSelector
Qt Designer
MyModule Resources
• If icons are used, they should be in – MyModule/Resources/Icons/
• Update the resource .qrc file– MyModule/Resources/qSlicerMyModule.qrc
<!DOCTYPE RCC><RCC version="1.0"> <qresource> <file>Icons/MyIcon.png</file> …</qresource></RCC> qSlicerMyModule.qrc
qSlicerMyModule.h#ifndef __qSlicerMyModule_h#define __qSlicerMyModule_h
// SlicerQT includes#include "qSlicerAbstractLoadableModule.h“
#include "qSlicerMyModuleWin32Header.h“ // generated by CMakeclass qSlicerMyModulePrivate;
class Q_SLICER_QTMODULES_MYMODULE_EXPORT qSlicerMyModule : public qSlicerAbstractLoadableModule{ Q_OBJECTpublic: qSlicerTransformsModule(QObject *parent=0);
virtual QString title()const { return “Transforms”; }
virtual QString helpText()const; virtual QString acknowledgementText()const;
protected: // Create and return a widget representation of the object virtual qSlicerAbstractModuleWidget * createWidgetRepresentation(); virtual qSlicerAbstractModuleLogic* createLogic();};#endif qSlicerMyModule.h
qSlicerMyModule.cxx#include "qSlicerMyModule.h"
// SlicerQT includes#include "qSlicerMyModuleWidget.h"
// QT includes#include <QtPlugin>//-----------------------------------------------------------------------------Q_EXPORT_PLUGIN2(qSlicerMyModule, qSlicerMyModule);//-----------------------------------------------------------------------------qSlicerWelcomeModule(QObject* parent) :public qSlicerAbstractLoadableModule(parent){}//-----------------------------------------------------------------------------qSlicerAbstractModuleWidget * qSlicerWelcomeModule::createWidgetRepresentation(){ return new qSlicerWelcomeModuleWidget;}//-----------------------------------------------------------------------------qSlicerAbstractModuleWidget * qSlicerTransformsModule::createWidgetRepresentation(){ return new qSlicerMyModuleWidget;}//-----------------------------------------------------------------------------qSlicerAbstractModuleLogic* qSlicerTransformsModule::createLogic(){ return 0;}
Instantiate the UI widget
qSlicerMyModule.cxx
qSlicerMyModuleWidget.h
#ifndef __qSlicerMyModuleWidget_h#define __qSlicerMyModuleWidget_h
// SlicerQT includes#include "qSlicerAbstractModuleWidget.h"
// qCTK includes#include <qCTKPimpl.h>
#include "qSlicerMyModuleExport.h"
class qSlicerMyModuleWidgetPrivate;
class Q_SLICER_QTMODULES_MYMODULE_EXPORT qSlicerMyModuleWidget : public qSlicerAbstractModuleWidget{ Q_OBJECTpublic: typedef qSlicerAbstractModuleWidget Superclass; qSlicerMyModuleWidget(QWidget *parent=0);
protected: virtual void setup();
private: QCTK_DECLARE_PRIVATE(qSlicerMyModuleWidget);};
#endif
Configured by CMake
Setup the UI
qSlicerMyModuleWidget.h
qSlicerMyModuleWidget.cxx
#include "qSlicerMyModuleWidget.h"#include "ui_qSlicerMyModule.h"
//-----------------------------------------------------------------------------struct qSlicerMyModuleWidgetPrivate: public qCTKPrivate<qSlicerMyModuleWidget>, public Ui_qSlicerMyModule{};
//-----------------------------------------------------------------------------qSlicerMyModuleWidget ::qSlicerMyModuleWidget( QWidget* parent) :qSlicerAbstractModuleWidget(parent){ QCTK_INIT_PRIVATE(qSlicerMyModuleWidget);}
//-----------------------------------------------------------------------------void qSlicerWelcomeModuleWidget::setup(){ QCTK_D(qSlicerWelcomeModuleWidget); d->setupUi(this);}
Generated by CMake (via uic) from qSlicerMyModule.ui
Creates the QWidgets
…void setupUi(qSlicerWidget *qSlicerMyModule){ … verticalLayout = new QVBoxLayout(qSlicerMyModule); CTKCollapsibleButton = new qCTKCollapsibleButton(qSlicerMyModule); CTKCollapsibleButton->setCollapsed(true);
horizontalLayout = new QHBoxLayout(CTKCollapsibleButton); label = new QLabel(CTKCollapsibleButton); horizontalLayout->addWidget(label);
horizontalSlider = new QSlider(CTKCollapsibleButton); …} Ui_qSlicerMyModule.h
qSlicerMyModuleWidget.cxx
MyModule project
• Create a CMakeLists.txt in MyModule
• Add your module in QTModules/CMakeLists.txt
SET(qt_module_SRCS qSlicerMyModule.cxx qSlicerMyModule.h qSlicerMyModuleWidget.cxx qSlicerMyModuleWidget.h )Slicer3_build_qtmodule( NAME “MyModule” TITLE “My Module” EXPORT_DIRECTIVE "Q_SLICER_QTMODULES_MYMODULE_EXPORT“ SRCS ${qt_module_SRCS} MOC_SRCS qSlicerMyModuleWidget.h UI_SRCS Resources/UI/qSlicerMyModule.ui TARGET_LIBRARIES ${qt_module_target_libraries} RESOURCES Resources/qSlicerMyModule.qrc ) QTModules/MyModule/CMakeLists.txt
Tadam !
MyModule Panelhere
MyModule
Slicer3
Module: with a logic and slots:qSlicerTransformsModuleWidget
class … qSlicerTransformsModuleWidget : public qSlicerAbstractModuleWidget{ Q_OBJECT …public slots: void loadTransform();…};
struct qSlicerTransformsModuleWidgetPrivate: public qCTKPrivate<qSlicerTransformsModuleWidget>, public Ui_qSlicerTransformsModule{ qSlicerTransformsModuleLogic* logic() const;};
void qSlicerTransformsModuleWidget::loadTransform(){ QCTK_D(qSlicerTransformsModuleWidget); QString fileName = QFileDialog::getOpenFileName(this); d->logic()->AddTransform(fileName);}
void qSlicerTransformsModuleWidget::setup(){ QCTK_D(qSlicerTransformsModuleWidget); d->setupUi(this); …this->connect(d->LoadTransformPushButton, SIGNAL(clicked()), SLOT(loadTransform()));}
Called by the “Load Transform” pushbutton
Widgets
Library QT VTK MRML
qCTKWidgets Yes
qVTKWidgets Yes Yes
qMRMLWidgets Yes Yes Yes
qSlicerBaseQTGUI Yes Yes Yes
qCTKWidgets
• Common Toolkit (CTK)
• Currently hosted on the Slicer repository
qCTKCollapsibleButton
qCTKCollapsibleGroupBox
qCTKColorPickerButton qCTKTreeComboBox
qCTKFixedTitleComboBox
qMRMLWidgets• Depends on QT and MRML• Usually contains the slot
setMRMLScene(vtkMRMLScene*)
qMRMLNodeSelector
qMRMLMatrixWidget
qMRMLTreeWidget
qMRMLListWidget
qCTKPimpl
• Hide the implementation details of an interface
• http://en.wikipedia.org/wiki/Opaque_pointer
// qCTK includes#include "qCTKPimpl.h"
// QT includes#include <QAbstractButton>
class qCTKCollapsibleButtonPrivate;
class QCTK_WIDGETS_EXPORT qCTKCollapsibleButton : public QAbstractButton{ Q_OBJECT public: qCTKCollapsibleButton(QWidget *parent = 0);…private: QCTK_DECLARE_PRIVATE(qCTKCollapsibleButton);};
#endif
friend class qCTKCollapsibleButtonPrivate; qCTKPrivateInterface<qCTKCollapsibleButton, qCTKCollapsibleButtonPrivate> qctk_d;
Don’t forget to declare the private class
qCTKPimpl//-----------------------------------------------------------------------------class qCTKCollapsibleButtonPrivate : public qCTKPrivate<qCTKCollapsibleButton>{public: QCTK_DECLARE_PUBLIC(qCTKCollapsibleButton); void init();
bool Collapsed; …};
//-----------------------------------------------------------------------------void qCTKCollapsibleButtonPrivate::init(){ QCTK_P(qCTKCollapsibleButton); p->setCheckable(true); // checked and Collapsed are synchronized: checked != Collapsed p->setChecked(true);
this->Collapsed = false;}
//-----------------------------------------------------------------------------qCTKCollapsibleButton::qCTKCollapsibleButton(QWidget* parent) :QAbstractButton(parent){ QCTK_INIT_PRIVATE(qCTKCollapsibleButton); qctk_d()->init();}
//-----------------------------------------------------------------------------void qCTKCollapsibleButton::collapse(bool c){ QCTK_D(qCTKCollapsibleButton); if (c == d->Collapsed) { return; } …}
friend class qCTKCollapsibleButton
qctk_d.setPublic(this)
qCTKCollapsibleButtonPrivate* d = qctk_d()
qCTKCollapsibleButton* p = qctk_p()
Widgets in Qt Designer
• A plugin must be created– Slicer3/Libs/qCTKWidgets/Plugins/
qMRMLNodeSelectorPlugin.[h|cxx]– Slicer3/Libs/qMRMLWidgets/Plugins/
qMRMLNodeSelectorPlugin.[h|cxx]
• More info on– http://wiki.slicer.org/slicerWiki/index.php/
Slicer3:Developers:Projects:QtSlicer/Tutorials/WidgetWriting
Widget ExampleQt Designer
class QCTK_WIDGETS_EXPORT qCTKCollapsibleButton : public QAbstractButton{ Q_OBJECT Q_PROPERTY(bool collapsed READ collapsed WRITE setCollapsed) Q_PROPERTY(int collapsedHeight READ collapsedHeight WRITE setCollapsedHeight) …
public: void setCollapsed(bool); bool collapsed()const;
void setCollapsedHeight(int); int collapsedHeight()const;
qCTKCollapsibleButton.h
void qCTKCollapsibleButtonPrivate::init(){ QCTK_P(qCTKCollapsibleButton); … this->Collapsed = false; … this->CollapsedHeight = 10; …} qCTKCollapsibleButton.cxx
10 = default value
QTCLI
• Same idea than KWWidgets– Parse XML to build UI
• Support– Shared Libraries– Executables– Python
UI panel in Slicer
QTCLI: Example…<parameters> <label>Registration Parameters</label> <description>Parameters used for registration</description> <integer> <name>HistogramBins</name> <flag>b</flag> <longflag>histogrambins</longflag> <description>Number of histogram bins to use for Mattes Mutual Information. </description> <label>Histogram Bins</label> <default>30</default> <constraints> <minimum>1</minimum> <maximum>500</maximum> <step>5</step> </constraints> </integer>…
…qCTKCollapsibleButton* registrationParameters = new qCTKCollapsibleButton(“Registration Parameters”, this);QLabel* histogramBinLabel = new QLabel(“Histogram Bins”, registrationParameters);QSlider* histogramBin = new QSlider(registrationParameters);histogramBin->setMinimum(1);histogramBin->setMaximum(500);histogramBin->setStep(5);histogramBin->setValue(30);QObject::connect(histogramBin, SIGNAL(valueChanged(int)), this, SIGNAL(onHistogramValueChanged(int)));…
UI panel in SlicerXml description
Generated code
Slicer Architecture
QTBaseqSlicerAbstractModule, qSlicerIOManager
QTCLIqSlicerCLIModule
QTCoreModulesqSlicerCamerasModules, qSlicerTransformsModule
QTCoreqSlicerModuleFactory
QTGUIqSlicerModulePanel, qSlicerApplication, qSlicerIOManager
What’s coming soon ?
• CLI modules
• Node tree widgets
• 3D view widget
• Lookup table editor
• Slice view widget
• …
What’s in the Pipeline ?
• Wizards
• Python
• More widgets – help from the CTK community– http://www.commontk.org/cgi-bin/trac.cgi/
wiki/WidgetPlans
Questions
• More info: http://wiki.slicer.org/slicerWiki/index.php/Slicer3:Developers:Projects:QtSlicer