Medical Imaging Interaction Toolkit  2023.12.00
Medical Imaging Interaction Toolkit
Adding functionality: Data Manager, Render Window, File Opening and DICOM Import

Up to now, we have developed a bulk custom viewer application, i.e. a runnable BlueBerry application showing an open file button and two perspectives switched by a custom tab-bar like perspectives bar. Now, we will add the desired functionality for our custom viewer. We want to integrate:

  • A Data Manager, managing Data Nodes related to loaded or DICOM-imported images
  • A Render Window to visualize the Data Nodes
  • File Opening functionality connected to the Open-File-Button
  • DICOM Import functionality

Except for the File Opening functionality, which is already GUI-represented, we need to integrate proper views to our perspectives in order to make the according functionality accessible. Concerning the design of our example application, two options appear straight-forward:

  1. Integrate the view-class source-code to the main-application-plugin (the custom viewer plugin)
  2. Create a proper plugin for the views

Taking into account the plugin dependencies, it can be revealed that the first solution is not an option. Without going into detail, that solution would result in a cyclic dependency scenario, so an adequate plugin activation order would not be achievable at runtime. So we will create a proper plugin for the views we intend to use. This is straightforward as shown in Creating the CustomViewer plugin.

For Data Manager functionality we will make use of the QmitkDataManagerView which - being a berry::IVewPart - can externally be integrated to our viewer perspective. The only thing we have to do is add the QMitkDataManagerView to the viewer perspective's CreateInitialLayout()-method:

void ViewerPerspective::CreateInitialLayout(berry::IPageLayout::Pointer layout)
{
QString editorArea = layout->GetEditorArea();
layout->SetEditorAreaVisible(false);
layout->AddStandaloneView(
"org.mitk.views.datamanager", false, berry::IPageLayout::LEFT, 0.3f, layout->GetEditorArea());

For the rendering functionality we have to create a proper view class. We derive that view class called SimpleRenderWindowView from QmitkAbstractView (for direct DataStorage access) and from mitk::IRenderWindowPart:

class SimpleRenderWindowView : public QmitkAbstractView, public mitk::IRenderWindowPart

Concrete implementations can for example be adapted from QmitkAbstractRenderEditor. The AbstractRenderWindowViewPrivate helper class is modified with regard to the views-Plugin-Activator:

class AbstractRenderWindowViewPrivate
{
public:
AbstractRenderWindowViewPrivate()
: m_RenderingManagerInterface(mitk::MakeRenderingManagerInterface(mitk::RenderingManager::GetInstance()))

In CreateQtPartControl() we can now lay out the view controls. For that, we create a QmitkRenderWindow whose Renderer is subsequently be given the DataStorage:

void SimpleRenderWindowView::CreateQtPartControl(QWidget *parent)
{
QVBoxLayout *layout = new QVBoxLayout(parent);
layout->setContentsMargins(0, 0, 0, 0);
m_RenderWindow = new QmitkRenderWindow(parent);
layout->addWidget(m_RenderWindow);
mitk::DataStorage::Pointer ds = this->GetDataStorage();
m_RenderWindow->GetRenderer()->SetDataStorage(ds);
this->RequestUpdate();
}

Finally we add the SimpleRenderWindowView in ViewerPerspective::CreateInitialLayout():

void ViewerPerspective::CreateInitialLayout(berry::IPageLayout::Pointer layout)
{
QString editorArea = layout->GetEditorArea();
layout->SetEditorAreaVisible(false);
layout->AddStandaloneView(
"org.mitk.views.datamanager", false, berry::IPageLayout::LEFT, 0.3f, layout->GetEditorArea());
layout->AddStandaloneView("org.mitk.customviewer.views.simplerenderwindowview",
false,
0.7f,
layout->GetEditorArea());
}

For the DICOM import functionality we derive the DicomView class from QmitkAbstractView:

class DicomView : public QmitkAbstractView

In CreateQtPartControl(), we add a QmitkDicomExternalDataWidget to our view controls (this time e.g. via ui-File):

void DicomView::CreateQtPartControl(QWidget *parent)
{
// create GUI widgets
m_Parent = parent;
m_Controls.setupUi(parent);
// remove unused widgets
QPushButton *downloadButton = parent->findChild<QPushButton *>("downloadButton");
downloadButton->setVisible(false);
connect(m_Controls.importButton, SIGNAL(clicked()), m_Controls.widget, SLOT(OnFolderCDImport()));
connect(m_Controls.widget,
SIGNAL(SignalDicomToDataManager(const QHash<QString, QVariant> &)),
this,
SLOT(AddDataNodeFromDICOM(const QHash<QString, QVariant> &)));
m_Parent->setEnabled(true);
}

The QmitkDicomExternalDataWidget yields a tree view for DICOM data, as well as a signal for Dicom transfer to the data manager and a slot for DICOM import to the tree view. With the Dicom transfer signal a string containing information about the DICOM series currently selected in the tree view is piggybacked. We use this information in an AddDataNodeFromDICOM slot defined in the DicomView class following the example of the DicomEventHandler class:

void DicomView::AddDataNodeFromDICOM(QHash<QString, QVariant> eventProperties)
{
QStringList listOfFilesForSeries;
std::vector<std::string> seriesToLoad;
listOfFilesForSeries = eventProperties["FilesForSeries"].toStringList();
if (!listOfFilesForSeries.isEmpty())
{
QStringListIterator it(listOfFilesForSeries);
while (it.hasNext())
{
seriesToLoad.push_back(it.next().toStdString());
}
auto results = mitk::IOUtil::Load(seriesToLoad, *(this->GetDataStorage().GetPointer()));
if (results->empty())
{
MITK_ERROR << "Error loading Dicom series";
}
// //! [DicomViewCreateAddDataNodeLoadSeries]
else
{
// //! [DicomViewCreateAddDataNode]
mitk::DataStorage::Pointer ds = this->GetDataStorage();
// //! [DicomViewCreateAddDataNode]
auto geometry = ds->ComputeBoundingGeometry3D(ds->GetAll());
// //! [DicomViewCreateAddDataNodeActivatePersp]
berry::IWorkbenchWindow::Pointer window = this->GetSite()->GetWorkbenchWindow();
QString perspectiveId = "org.mitk.example.viewerperspective";
window->GetWorkbench()->ShowPerspective(perspectiveId, berry::IWorkbenchWindow::Pointer(window));
// //! [DicomViewCreateAddDataNodeActivatePersp]
}
// //! [DicomViewCreateAddDataNodeLoadSeries]
}
}

The file path and seriesUID information are used to load the selected DICOM series into a mitk::DataNode:

else
{
// //! [DicomViewCreateAddDataNode]
mitk::DataStorage::Pointer ds = this->GetDataStorage();
// //! [DicomViewCreateAddDataNode]
auto geometry = ds->ComputeBoundingGeometry3D(ds->GetAll());
// //! [DicomViewCreateAddDataNodeActivatePersp]
berry::IWorkbenchWindow::Pointer window = this->GetSite()->GetWorkbenchWindow();
QString perspectiveId = "org.mitk.example.viewerperspective";
window->GetWorkbench()->ShowPerspective(perspectiveId, berry::IWorkbenchWindow::Pointer(window));
// //! [DicomViewCreateAddDataNodeActivatePersp]
}

which can then be added to the DataStorage:

mitk::DataStorage::Pointer ds = this->GetDataStorage();

After that, we activate the viewer perspective to examine the data in the rendering window view.

berry::IWorkbenchWindow::Pointer window = this->GetSite()->GetWorkbenchWindow();
QString perspectiveId = "org.mitk.example.viewerperspective";
window->GetWorkbench()->ShowPerspective(perspectiveId, berry::IWorkbenchWindow::Pointer(window));

Having a look back to the QmitkDicomExternalDataWidget, while there is already a view button present that triggers Dicom transfer signal emission, we still have to bind DICOM import functionality to a proper import button. We will do this once again in the CreateQtPartControl method (refer to the above snippet). After setting up the view controls containing the QmitkDicomExternalDataWidget and an import button, we render the unused widgets invisible. After connecting the Dicom transfer signal to the AddDataNodeFromDICOM slot and our import button to the DICOM import slot of the QmitkDicomExternalDataWidget, the DicomView is ready for use. Finally, the DicomView is added inside the DicomPerspective::CreateInitialLayout() method:

void DicomPerspective::CreateInitialLayout(berry::IPageLayout::Pointer layout)
{
QString editorArea = layout->GetEditorArea();
layout->SetEditorAreaVisible(false);
layout->AddStandaloneView(
"org.mitk.customviewer.views.dicomview", false, berry::IPageLayout::LEFT, 1.0f, layout->GetEditorArea());
layout->GetViewLayout("org.mitk.customviewer.views.dicomview")->SetCloseable(false);
layout->GetViewLayout("org.mitk.customviewer.views.dicomview")->SetMoveable(false);
}

The following images show the Dicom import functionality.

The DICOM file import dialog


Imported DICOM data shown in the tree view


Imported DICOM data presented in the render window view


Now we implement the file open slot already defined earlier (see Main Window Layout: ViewerPerspective and DicomPerspective). While it appears that we could simply assign a QmitkFileOpenAction to a QToolButton, this is not possible due to the fact, that by default, the WorkbenchUtil::LoadFiles() method invoked by the QmitkFileOpenAction awaits an editor to be present in the current application. To prevent the method from throwing an exception, we made a workaround by giving the LoadFiles() method an additional parameter that determines whether an editor is to be opened or not:

void WorkbenchUtil::LoadFiles(const QStringList &fileNames, berry::IWorkbenchWindow::Pointer window, bool openEditor)

Hence, we have to invoke that method manually, e.g. inside an OpenFile-slot implemented inside the WorkbenchWindowAdvisor:

void CustomViewerWorkbenchWindowAdvisor::OpenFile()
{
QStringList fileNames = QFileDialog::getOpenFileNames(
nullptr,
QStringLiteral("Open"),
QString(),
if (fileNames.empty())
return;
mitk::WorkbenchUtil::LoadFiles(fileNames, this->GetWindowConfigurer()->GetWindow(), false);

In it, a dialog is opened that asks for the user for a number of files to open. If any files are given, these are being loaded by the WorkbenchUtil::LoadFiles method. Finally, the viewer perspective is activated:

berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow();
QString perspectiveId = "org.mitk.example.viewerperspective";
window->GetWorkbench()->ShowPerspective(perspectiveId, berry::IWorkbenchWindow::Pointer(window));

Before we can examine the loaded data, we have to manually invoke a reinit on it. The render window concept in mitk is actually undergoing some work, where this inconvenience will also be adressed. The images below show the resulting file opening functionality.

The open file dialog


Opened file shown in the render window view


Go to the previous page Main Window Layout: ViewerPerspective and DicomPerspective. Or proceed to the next page Customizing the Main Window using Qt-Stylesheets.

mitk::RenderingManager::GetInstance
static RenderingManager * GetInstance()
berry::IPageLayout::LEFT
static const int LEFT
Definition: berryIPageLayout.h:142
mitk::RenderingManager::InitializeViews
virtual bool InitializeViews(const BaseGeometry *geometry, RequestType type=REQUEST_UPDATE_ALL, bool resetCamera=true)
Initialize the render windows specified by "type" to the given geometry.
mitk::WorkbenchUtil::LoadFiles
static void LoadFiles(const QStringList &fileNames, berry::IWorkbenchWindow::Pointer wnd, bool openEditor=true)
mitk::MakeRenderingManagerInterface
MITK_GUI_COMMON_PLUGIN IRenderingManager * MakeRenderingManagerInterface(RenderingManager::Pointer manager)
MITK_ERROR
#define MITK_ERROR
Definition: mitkLog.h:211
berry::SmartPointer< Self >
QmitkAbstractView
A convenient base class for MITK related BlueBerry Views.
Definition: QmitkAbstractView.h:89
itk::SmartPointer< Self >
QmitkRenderWindow
MITK implementation of the QVTKWidget.
Definition: QmitkRenderWindow.h:38
QmitkIOUtil::GetFileOpenFilterString
static QString GetFileOpenFilterString()
GetFilterString.
mitk::RenderingManager::RequestUpdateAll
void RequestUpdateAll(RequestType type=REQUEST_UPDATE_ALL)
mitk
Find image slices visible on a given plane.
Definition: RenderingTests.dox:1
mitk::IRenderWindowPart
Interface for a MITK Workbench Part providing a render window.
Definition: mitkIRenderWindowPart.h:54
berry::IPageLayout::RIGHT
static const int RIGHT
Definition: berryIPageLayout.h:148
mitk::RenderingManager::SetDataStorage
void SetDataStorage(DataStorage *storage)
Setter for internal DataStorage.
mitk::IOUtil::Load
static DataStorage::SetOfObjects::Pointer Load(const std::string &path, DataStorage &storage, const ReaderOptionsFunctorBase *optionsCallback=nullptr)
Load a file into the given DataStorage.