390 likes | 711 Views
Port of 3D Slicer to Qt. Julien Finet Kitware Inc. Dec. 16 th 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
E N D
Port of 3D Slicer to Qt JulienFinet Kitware 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 KWWidgets • Fire event • Connect • Process void vtkSlicerNodeSelectorWidget::ProcessCommand(char *selectedID) { … this->InvokeEvent(vtkSlicerNodeSelectorWidget::NodeSelectedEvent, NULL); … } vtkSlicerNodeSelectorWidget.cxx void vtkSlicerCamerasGUI::AddGUIObservers() { … this->ViewSelectorWidget->AddObserver( vtkSlicerNodeSelectorWidget::NodeSelectedEvent, (vtkCommand *)this->GUICallbackCommand ); … } vtkSlicerCamerasGUI.cxx void vtkSlicerCamerasGUI::ProcessGUIEvents( vtkObject *caller, unsigned long event, void *callData ) { if (vtkSlicerNodeSelectorWidget::SafeDownCast(caller)) { if (event == vtkSlicerNodeSelectorWidget::NodeSelectedEvent) { … } vtkSlicerCamerasGUI.cxx
Qt in Slicer • Events with Qt • Fire event • Connect • Process void qMRMLNodeSelector::nodeIdSelected(int index) { … emit currentNodeChanged(d->MRMLCurrentNode); } qMRMLNodeSelector.cxx void qSlicerCamerasModuleWidget::setup() { … connect(d->ViewNodeSelector, SIGNAL(currentNodeChanged(vtkMRMLNode*)), this, SLOT(onCurrentViewNodeChanged(vtkMRMLNode*))); … } qSlicerCamerasModuleWidget.cxx void qSlicerCamerasModuleWidget::onCurrentViewNodeChanged(vtkMRMLNode* mrmlNode) { vtkMRMLViewNode* currentViewNode = vtkMRMLViewNode::SafeDownCast(mrmlNode); … } qSlicerCamerasModuleWidget.cxx
Qt and VTK • qVTKConnect() • Based on VTK/GUISupport/Qt/vtkEventQtSlotConnect // qVTK includes #include <qVTKObject.h> … class QMRML_WIDGETS_EXPORT qMRMLNodeSelector : public qCTKAddRemoveComboBox { Q_OBJECT QVTK_OBJECT public: … qMRMLNodeSelector.h QVTK_OBJECT adds the function qvtkConnect() void qMRMLNodeSelector::addNode(vtkMRMLNode* mrmlNode) { … this->qvtkConnect(mrmlNode, vtkCommand::ModifiedEvent, this, SLOT(onMRMLNodeModified(vtkObject*, void*))); … } qMRMLNodeSelector.cxx
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 Executable: SlicerQT • Slicer: Qt only Executable: Slicer3 • Slicer: KWWidgets + Qt
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: … }; Plugin header … Q_EXPORT_PLUGIN2(qSlicerVolumesModule, qSlicerVolumesModule); … Plugin implementation QPluginLoader loader; loader.setFileName(pluginPath); loader.load(); QObject * object = this->Loader.instance(); qSlicerAbstractLoadableModule* module = qobject_cast<qSlicerAbstractLoadableModule*>(object); Plugin Loader
Modules: Logic + UI Module (qSlicerAbstractModule) QObject create() create() UI (qSlicerAbstractModuleWidget) Logic (qSlicerAbstractModuleLogic) QWidget qSlicer…ModuleWidget.ui vtkMRMLScene 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 CMake class qSlicerMyModulePrivate; class Q_SLICER_QTMODULES_MYMODULE_EXPORT qSlicerMyModule : public qSlicerAbstractLoadableModule { Q_OBJECT public: qSlicerTransformsModule(QObject *parent=0); virtual QString title()const { return “Transforms”; } virtual QStringhelpText()const; virtual QStringacknowledgementText()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_OBJECT public: typedefqSlicerAbstractModuleWidgetSuperclass; qSlicerMyModuleWidget(QWidget *parent=0); protected: virtual void setup(); private: QCTK_DECLARE_PRIVATE(qSlicerMyModuleWidget); }; #endif Configured by CMake Setup the UI qSlicerMyModuleWidget.h
qSlicerMyModuleWidget.cxx qSlicerMyModuleWidget.cxx #include "qSlicerMyModuleWidget.h" #include "ui_qSlicerMyModule.h" //----------------------------------------------------------------------------- structqSlicerMyModuleWidgetPrivate: 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 … 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); … } Creates the QWidgets Ui_qSlicerMyModule.h
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 MyModule Panel here Slicer3
Module: with a logic and slots:qSlicerTransformsModuleWidget class … qSlicerTransformsModuleWidget : public qSlicerAbstractModuleWidget { Q_OBJECT … public slots: void loadTransform(); … }; structqSlicerTransformsModuleWidgetPrivate: public qCTKPrivate<qSlicerTransformsModuleWidget>, publicUi_qSlicerTransformsModule { qSlicerTransformsModuleLogic* logic() const; }; void qSlicerTransformsModuleWidget::setup() { QCTK_D(qSlicerTransformsModuleWidget); d->setupUi(this); … this->connect(d->LoadTransformPushButton, SIGNAL(clicked()), SLOT(loadTransform())); } Called by the “Load Transform” pushbutton void qSlicerTransformsModuleWidget::loadTransform() { QCTK_D(qSlicerTransformsModuleWidget); QStringfileName = QFileDialog::getOpenFileName(this); d->logic()->AddTransform(fileName); }
qCTKWidgets • Common Toolkit (CTK) • Currently hosted on the Slicer repository qCTKFixedTitleComboBox qCTKCollapsibleGroupBox qCTKCollapsibleButton qCTKTreeComboBox qCTKColorPickerButton
qMRMLWidgets • Depends on QT and MRML • Usually contains the slot setMRMLScene(vtkMRMLScene*) qMRMLNodeSelector qMRMLListWidget qMRMLMatrixWidget qMRMLTreeWidget
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 Don’t forget to declare the private class friend class qCTKCollapsibleButtonPrivate; qCTKPrivateInterface<qCTKCollapsibleButton, qCTKCollapsibleButtonPrivate> qctk_d;
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; } friend class qCTKCollapsibleButton qctk_d.setPublic(this) //----------------------------------------------------------------------------- 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; } … } qCTKCollapsibleButton* p = qctk_p() qCTKCollapsibleButtonPrivate* d = qctk_d()
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 Example class QCTK_WIDGETS_EXPORT qCTKCollapsibleButton : public QAbstractButton { Q_OBJECT Q_PROPERTY(bool collapsed READ collapsed WRITE setCollapsed) Q_PROPERTY(intcollapsedHeight READ collapsedHeight WRITE setCollapsedHeight) … public: void setCollapsed(bool); bool collapsed()const; void setCollapsedHeight(int); intcollapsedHeight()const; Qt Designer qCTKCollapsibleButton.h 10 = default value void qCTKCollapsibleButtonPrivate::init() { QCTK_P(qCTKCollapsibleButton); … this->Collapsed = false; … this->CollapsedHeight = 10; … } qCTKCollapsibleButton.cxx
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> … UI panel in Slicer Xml description … 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))); … Generated code
Slicer Architecture QTCLI qSlicerCLIModule QTCoreModules qSlicerCamerasModules, qSlicerTransformsModule QTGUI qSlicerModulePanel, qSlicerApplication, qSlicerIOManager QTCore qSlicerModuleFactory QTBase qSlicerAbstractModule, 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