diff --git a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/colourimageprocessing/QmitkColourImageProcessingView.cpp b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/colourimageprocessing/QmitkColourImageProcessingView.cpp index d8b468fc90..f335db40d2 100644 --- a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/colourimageprocessing/QmitkColourImageProcessingView.cpp +++ b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/colourimageprocessing/QmitkColourImageProcessingView.cpp @@ -1,261 +1,265 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkColourImageProcessingView.h" #include "ui_QmitkColourImageProcessingViewControls.h" #include "mitkColourImageProcessor.h" #include "mitkDataNodeObject.h" #include "mitkTransferFunction.h" #include "mitkTransferFunctionProperty.h" #include "QmitkColorTransferFunctionCanvas.h" #include "QmitkPiecewiseFunctionCanvas.h" #include #include #include #include #include #include #include const std::string QmitkColourImageProcessingView::VIEW_ID = "org.mitk.views.colourimageprocessing"; QmitkColourImageProcessingView::QmitkColourImageProcessingView() : m_Controls(nullptr) { m_Color[0] = 255; m_Color[1] = 0; m_Color[2] = 0; } void QmitkColourImageProcessingView::SetFocus() { m_Controls->m_ConvertImageToRGBA->setFocus(); } void QmitkColourImageProcessingView::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { m_Controls = new Ui::QmitkColourImageProcessingViewControls; m_Controls->setupUi(parent); connect(m_Controls->m_ConvertImageToRGBA, SIGNAL(clicked(bool)), this, SLOT(OnConvertToRGBAImage())); connect(m_Controls->m_ConvertImageMaskToRGBA, SIGNAL(clicked(bool)), this, SLOT(OnConvertToRGBAImage())); connect(m_Controls->m_ConvertImageMaskColorToRGBA, SIGNAL(clicked(bool)), this, SLOT(OnConvertImageMaskColorToRGBAImage())); connect(m_Controls->m_ColorButton, SIGNAL(clicked(bool)), this, SLOT(OnChangeColor())); connect(m_Controls->m_CombineRGBAButton, SIGNAL(clicked(bool)), this, SLOT(OnCombineRGBA())); m_Controls->m_ImageSelectedLabel->hide(); m_Controls->m_NoImageSelectedLabel->show(); } } void QmitkColourImageProcessingView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList &nodes) { if (!nodes.isEmpty()) { QList selectedNodes; foreach (const mitk::DataNode::Pointer node, nodes) { if (node.IsNotNull()) { mitk::Image *image = dynamic_cast(node->GetData()); if (image->GetDimension() >= 3) { selectedNodes.push_back(node); } } } mitk::DataNode::Pointer node; if (selectedNodes.size() > 0) { node = selectedNodes[0]; m_SelectedNode = node; m_Controls->m_NoImageSelectedLabel->hide(); m_Controls->m_ImageSelectedLabel->show(); std::string infoText = std::string("Selected Image: ") + node->GetName(); if (selectedNodes.size() > 1) { mitk::DataNode::Pointer node2; node2 = selectedNodes[1]; m_SelectedNode2 = node2; infoText = infoText + " and " + node2->GetName(); } else { m_SelectedNode2 = nullptr; } m_Controls->m_ImageSelectedLabel->setText(QString(infoText.c_str())); } else { m_Controls->m_ImageSelectedLabel->hide(); m_Controls->m_NoImageSelectedLabel->show(); m_SelectedNode = nullptr; m_SelectedNode2 = nullptr; } } } void QmitkColourImageProcessingView::OnConvertToRGBAImage() { - if (m_SelectedNode.IsNull()) + if (m_SelectedNode.IsExpired()) return; + auto selectedNode = m_SelectedNode.Lock(); + mitk::TransferFunctionProperty::Pointer transferFunctionProp = - dynamic_cast(m_SelectedNode->GetProperty("TransferFunction")); + dynamic_cast(selectedNode->GetProperty("TransferFunction")); if (transferFunctionProp.IsNull()) return; mitk::TransferFunction::Pointer tf = transferFunctionProp->GetValue(); if (tf.IsNull()) return; mitk::mitkColourImageProcessor CImageProcessor; mitk::Image::Pointer RGBAImageResult; - if (m_SelectedNode2.IsNotNull()) + if (!m_SelectedNode2.IsExpired()) { RGBAImageResult = - CImageProcessor.convertWithBinaryToRGBAImage(dynamic_cast(m_SelectedNode->GetData()), - dynamic_cast(m_SelectedNode2->GetData()), + CImageProcessor.convertWithBinaryToRGBAImage(dynamic_cast(selectedNode->GetData()), + dynamic_cast(selectedNode2.Lock()->GetData()), tf); } else { - RGBAImageResult = CImageProcessor.convertToRGBAImage(dynamic_cast(m_SelectedNode->GetData()), tf); + RGBAImageResult = CImageProcessor.convertToRGBAImage(dynamic_cast(selectedNode->GetData()), tf); } if (!RGBAImageResult) { QMessageBox::warning(nullptr, "Warning", QString("Unsupported pixeltype")); return; } mitk::DataNode::Pointer dtn = mitk::DataNode::New(); dtn->SetData(RGBAImageResult); - dtn->SetName(m_SelectedNode->GetName() + "_RGBA"); + dtn->SetName(selectedNode->GetName() + "_RGBA"); this->GetDataStorage()->Add(dtn); // add as a child, because the segmentation "derives" from the original MITK_INFO << "convertToRGBAImage finish"; } void QmitkColourImageProcessingView::OnConvertImageMaskColorToRGBAImage() { - if (m_SelectedNode.IsNull()) + if (m_SelectedNode.IsExpired()) return; + auto selectedNode = m_SelectedNode.Lock(); + mitk::TransferFunctionProperty::Pointer transferFunctionProp = - dynamic_cast(m_SelectedNode->GetProperty("TransferFunction")); + dynamic_cast(selectedNode->GetProperty("TransferFunction")); if (transferFunctionProp.IsNull()) return; mitk::TransferFunction::Pointer tf = transferFunctionProp->GetValue(); if (tf.IsNull()) return; mitk::mitkColourImageProcessor CImageProcessor; mitk::Image::Pointer RGBAImageResult; - if (m_SelectedNode2.IsNotNull()) + if (!m_SelectedNode2.IsExpired()) { RGBAImageResult = - CImageProcessor.convertWithBinaryAndColorToRGBAImage(dynamic_cast(m_SelectedNode->GetData()), - dynamic_cast(m_SelectedNode2->GetData()), + CImageProcessor.convertWithBinaryAndColorToRGBAImage(dynamic_cast(selectedNode->GetData()), + dynamic_cast(m_SelectedNode2.Lock()->GetData()), tf, m_Color); } else { - RGBAImageResult = CImageProcessor.convertToRGBAImage(dynamic_cast(m_SelectedNode->GetData()), tf); + RGBAImageResult = CImageProcessor.convertToRGBAImage(dynamic_cast(selectedNode->GetData()), tf); } if (!RGBAImageResult) { QMessageBox::warning(nullptr, "Warning", QString("Unsupported pixeltype")); return; } mitk::DataNode::Pointer dtn = mitk::DataNode::New(); dtn->SetData(RGBAImageResult); - dtn->SetName(m_SelectedNode->GetName() + "_RGBA"); + dtn->SetName(selectedNode->GetName() + "_RGBA"); this->GetDataStorage()->Add(dtn); // add as a child, because the segmentation "derives" from the original } void QmitkColourImageProcessingView::OnChangeColor() { QColor color = QColorDialog::getColor(); if (color.spec() == 0) { color.setRed(255); color.setGreen(0); color.setBlue(0); } m_Color[0] = color.red(); m_Color[1] = color.green(); m_Color[2] = color.blue(); m_Controls->m_ColorButton->setStyleSheet( QString("background-color:rgb(%1,%2, %3)").arg(color.red()).arg(color.green()).arg(color.blue())); } void QmitkColourImageProcessingView::OnCombineRGBA() { - if (m_SelectedNode.IsNull()) + if (m_SelectedNode.IsExpired()) return; - if (m_SelectedNode2.IsNull()) + if (m_SelectedNode2.IsExpired()) return; mitk::mitkColourImageProcessor CImageProcessor; mitk::Image::Pointer RGBAImageResult; - RGBAImageResult = CImageProcessor.combineRGBAImage(dynamic_cast(m_SelectedNode->GetData()), - dynamic_cast(m_SelectedNode2->GetData())); + RGBAImageResult = CImageProcessor.combineRGBAImage(dynamic_cast(m_SelectedNode.Lock()->GetData()), + dynamic_cast(m_SelectedNode2.Lock()->GetData())); MITK_INFO << "RGBAImage Result"; mitk::DataNode::Pointer dtn = mitk::DataNode::New(); dtn->SetData(RGBAImageResult); this->GetDataStorage()->Add(dtn); // add as a child, because the segmentation "derives" from the original } diff --git a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/volumetry/QmitkVolumetryView.cpp b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/volumetry/QmitkVolumetryView.cpp index 316564e60a..051df5b053 100644 --- a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/volumetry/QmitkVolumetryView.cpp +++ b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/volumetry/QmitkVolumetryView.cpp @@ -1,248 +1,250 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkVolumetryView.h" #include "ui_QmitkVolumetryViewControls.h" #include "mitkImageStatisticsHolder.h" #include "mitkVolumeCalculator.h" #include #include #include #include const std::string QmitkVolumetryView::VIEW_ID = "org.mitk.views.volumetry"; QmitkVolumetryView::QmitkVolumetryView() : m_Controls(nullptr), m_ParentWidget(nullptr) { } void QmitkVolumetryView::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { m_ParentWidget = parent; // create GUI widgets m_Controls = new Ui::QmitkVolumetryViewControls; m_Controls->setupUi(parent); this->CreateConnections(); } } void QmitkVolumetryView::SetFocus() { m_Controls->m_CalcButton->setFocus(); } void QmitkVolumetryView::CreateConnections() { if (m_Controls) { connect( (QObject *)(m_Controls->m_ThresholdSlider), SIGNAL(valueChanged(int)), this, SLOT(OnThresholdSliderChanged(int))); connect((QObject *)(m_Controls->m_CalcButton), SIGNAL(clicked()), this, SLOT(OnCalculateVolume())); connect((QObject *)(m_Controls->m_TimeSeriesButton), SIGNAL(clicked()), this, SLOT(OnTimeSeriesButtonClicked())); connect((QObject *)(m_Controls->m_SaveCsvButton), SIGNAL(clicked()), this, SLOT(OnSaveCsvButtonClicked())); } } void QmitkVolumetryView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList &nodes) { m_SelectedDataNode = nullptr; if (!nodes.isEmpty() && dynamic_cast(nodes.front()->GetData())) { m_SelectedDataNode = nodes.front(); m_ParentWidget->setEnabled(true); } - if (m_SelectedDataNode.IsNull() || m_SelectedDataNode.GetPointer() == m_OverlayNode.GetPointer()) + if (m_SelectedDataNode.IsExpired() || m_SelectedDataNode == m_OverlayNode) { m_SelectedDataNode = nullptr; m_ParentWidget->setEnabled(false); return; } if (m_OverlayNode) { this->GetDataStorage()->Remove(m_OverlayNode); m_OverlayNode = nullptr; } this->CreateOverlayChild(); m_Controls->m_CalcButton->setEnabled(false); m_Controls->m_TimeSeriesButton->setEnabled(false); m_Controls->m_SaveCsvButton->setEnabled(false); m_Controls->m_TextEdit->clear(); - mitk::Image *image = dynamic_cast(m_SelectedDataNode->GetData()); + mitk::Image *image = dynamic_cast(m_SelectedDataNode.Lock()->GetData()); image->Update(); if (image && image->IsInitialized()) { if (image->GetDimension() == 4) { m_Controls->m_TimeSeriesButton->setEnabled(true); } else { m_Controls->m_CalcButton->setEnabled(true); } int minVal = (int)image->GetStatistics()->GetScalarValue2ndMin(); int maxVal = (int)image->GetStatistics()->GetScalarValueMaxNoRecompute(); if (minVal == maxVal) --minVal; m_Controls->m_ThresholdSlider->setMinimum(minVal); m_Controls->m_ThresholdSlider->setMaximum(maxVal); m_Controls->m_ThresholdSlider->setEnabled(true); this->UpdateSlider(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkVolumetryView::OnCalculateVolume() { - if (m_SelectedDataNode.IsNotNull()) + if (!m_SelectedDataNode.IsExpired()) { - mitk::Image *image = dynamic_cast(m_SelectedDataNode->GetData()); + mitk::Image *image = dynamic_cast(m_SelectedDataNode.Lock()->GetData()); std::cout << "Dimension:" << image->GetDimension() << std::endl; std::cout << "Dimension[3]:" << image->GetDimension(3) << std::endl; mitk::VolumeCalculator::Pointer volCalc = mitk::VolumeCalculator::New(); volCalc->SetImage(image); volCalc->SetThreshold(m_Controls->m_ThresholdSlider->value()); volCalc->ComputeVolume(); std::stringstream vs; vs << volCalc->GetVolume() << " ml"; m_Controls->m_Result->setText(vs.str().c_str()); } } void QmitkVolumetryView::OnTimeSeriesButtonClicked() { - if (m_SelectedDataNode.IsNotNull()) + if (!m_SelectedDataNode.IsExpired()) { - mitk::Image *image = dynamic_cast(m_SelectedDataNode->GetData()); + mitk::Image *image = dynamic_cast(m_SelectedDataNode.Lock()->GetData()); mitk::VolumeCalculator::Pointer volCalc = mitk::VolumeCalculator::New(); volCalc->SetImage(image); volCalc->SetThreshold(m_Controls->m_ThresholdSlider->value()); volCalc->ComputeVolume(); std::vector volumes = volCalc->GetVolumes(); std::stringstream vs; int timeStep = 0; for (auto it = volumes.begin(); it != volumes.end(); it++) { vs << timeStep++ << "\t" << *it << std::endl; } m_Controls->m_TextEdit->setText(vs.str().c_str()); m_Controls->m_TextEdit->setTabStopWidth(20); m_Controls->m_SaveCsvButton->setEnabled(true); } } const mitk::DataNode *QmitkVolumetryView::GetImageNode() const { - return m_SelectedDataNode; + return m_SelectedDataNode.Lock(); } void QmitkVolumetryView::UpdateSlider() { - if (m_SelectedDataNode.IsNotNull() && dynamic_cast(m_SelectedDataNode->GetData())) + if (!m_SelectedDataNode.IsExpired() && dynamic_cast(m_SelectedDataNode.Lock()->GetData())) { int intSliderValue = (int)m_Controls->m_ThresholdSlider->value(); QString stringSliderValue; stringSliderValue.setNum(intSliderValue); m_Controls->m_ThresholdLineEdit->setText(stringSliderValue); } } void QmitkVolumetryView::UpdateSliderLabel() { int sliderValue = atoi(m_Controls->m_ThresholdLineEdit->text().toLatin1()); m_Controls->m_ThresholdSlider->setValue(sliderValue); this->UpdateSlider(); } void QmitkVolumetryView::OnThresholdSliderChanged(int value) { if (m_OverlayNode) { m_OverlayNode->SetLevelWindow(mitk::LevelWindow(value, 1)); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdateSlider(); } } void QmitkVolumetryView::CreateOverlayChild() { - if (m_SelectedDataNode.IsNotNull()) + if (!m_SelectedDataNode.IsExpired()) { + auto selectedDataNode = m_SelectedDataNode.Lock(); + m_OverlayNode = mitk::DataNode::New(); mitk::StringProperty::Pointer nameProp = mitk::StringProperty::New("volume threshold overlay image"); - m_OverlayNode->SetProperty("reslice interpolation", m_SelectedDataNode->GetProperty("reslice interpolation")); + m_OverlayNode->SetProperty("reslice interpolation", selectedDataNode->GetProperty("reslice interpolation")); m_OverlayNode->SetProperty("name", nameProp); - m_OverlayNode->SetData(m_SelectedDataNode->GetData()); + m_OverlayNode->SetData(selectedDataNode->GetData()); m_OverlayNode->SetColor(0.0, 1.0, 0.0); m_OverlayNode->SetOpacity(.25); int layer = 0; - m_SelectedDataNode->GetIntProperty("layer", layer); + selectedDataNode->GetIntProperty("layer", layer); m_OverlayNode->SetIntProperty("layer", layer + 1); m_OverlayNode->SetLevelWindow(mitk::LevelWindow(m_Controls->m_ThresholdSlider->value(), 1)); this->GetDataStorage()->Add(m_OverlayNode); } } mitk::DataNode *QmitkVolumetryView::GetOverlayNode() const { return m_OverlayNode; } mitk::Image *QmitkVolumetryView::GetImage() const { - return dynamic_cast(m_SelectedDataNode->GetData()); + return dynamic_cast(m_SelectedDataNode.Lock()->GetData()); } void QmitkVolumetryView::OnSaveCsvButtonClicked() { static QString lastSavePath = QDir::homePath(); QString s = QFileDialog::getSaveFileName(this->m_ParentWidget, "Save as..", lastSavePath, "CSV Files (*.csv)"); /*"Save file dialog" "Choose a filename to save under" );*/ if (!s.isEmpty()) { lastSavePath = s; QFile saveFile(s); if (saveFile.open(QIODevice::WriteOnly)) { QTextStream stream(&saveFile); stream << m_Controls->m_TextEdit->toPlainText().replace('\t', ';'); saveFile.close(); } else { // QMessageBox::critical(nullptr,"Save Error!",QString("Saving of CSV failed! Couldn't open output file \"") + // saveFile + QString("\""),QMessageBox:Ok,QMessageBox::NoButton); // QMessageBox::critical(nullptr,"Save Error!","Saving of CSV failed! Couldn't open output file \"" + saveFile.name() // +"\"",QMessageBox::Ok,QMessageBox::NoButton); } } } diff --git a/Modules/AlgorithmsExt/src/mitkNonBlockingAlgorithm.cpp b/Modules/AlgorithmsExt/src/mitkNonBlockingAlgorithm.cpp index 726910a162..7eb4797589 100644 --- a/Modules/AlgorithmsExt/src/mitkNonBlockingAlgorithm.cpp +++ b/Modules/AlgorithmsExt/src/mitkNonBlockingAlgorithm.cpp @@ -1,207 +1,207 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkNonBlockingAlgorithm.h" #include "mitkCallbackFromGUIThread.h" #include "mitkDataStorage.h" #include namespace mitk { NonBlockingAlgorithm::NonBlockingAlgorithm() : m_ThreadID(-1), m_UpdateRequests(0), m_KillRequest(false) { m_ParameterListMutex = itk::FastMutexLock::New(); m_Parameters = PropertyList::New(); m_MultiThreader = itk::MultiThreader::New(); } NonBlockingAlgorithm::~NonBlockingAlgorithm() {} void mitk::NonBlockingAlgorithm::SetDataStorage(DataStorage &storage) { m_DataStorage = &storage; } - DataStorage *mitk::NonBlockingAlgorithm::GetDataStorage() { return m_DataStorage; } + DataStorage *mitk::NonBlockingAlgorithm::GetDataStorage() { return m_DataStorage.Lock(); } void NonBlockingAlgorithm::Initialize(const NonBlockingAlgorithm *itkNotUsed(other)) { // define one input, one output basedata object // some basedata input - image, surface, whatever BaseData::Pointer input; SetPointerParameter("Input", input); // some basedata output BaseData::Pointer output; SetPointerParameter("Output", output); } void NonBlockingAlgorithm::SetPointerParameter(const char *parameter, BaseData *value) { m_ParameterListMutex->Lock(); m_Parameters->SetProperty(parameter, SmartPointerProperty::New(value)); m_ParameterListMutex->Unlock(); } void NonBlockingAlgorithm::DefineTriggerParameter(const char *parameter) { BaseProperty *value = m_Parameters->GetProperty(parameter); if (value && m_TriggerPropertyConnections.find(parameter) == m_TriggerPropertyConnections.end()) { itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &NonBlockingAlgorithm::TriggerParameterModified); m_TriggerPropertyConnections[parameter] = value->AddObserver(itk::ModifiedEvent(), command); } } void NonBlockingAlgorithm::UnDefineTriggerParameter(const char *parameter) { auto iter = m_TriggerPropertyConnections.find(parameter); if (iter != m_TriggerPropertyConnections.end()) { BaseProperty *value = m_Parameters->GetProperty(parameter); MITK_ERROR(!value) << "NonBlockingAlgorithm::UnDefineTriggerProperty() in bad state." << std::endl; ; value->RemoveObserver(m_TriggerPropertyConnections[parameter]); m_TriggerPropertyConnections.erase(iter); } } void NonBlockingAlgorithm::Reset() { Initialize(); } void NonBlockingAlgorithm::StartBlockingAlgorithm() { StartAlgorithm(); StopAlgorithm(); } void NonBlockingAlgorithm::StartAlgorithm() { if (!ReadyToRun()) return; // let algorithm check if all input/parameters are ok if (m_KillRequest) return; // someone wants us to die m_ParameterListMutex->Lock(); m_ThreadParameters.m_Algorithm = this; ++m_UpdateRequests; m_ParameterListMutex->Unlock(); if (m_ThreadID != -1) // thread already running. But something obviously wants us to recalculate the output { return; // thread already running } // spawn a thread that calls ThreadedUpdateFunction(), and ThreadedUpdateFinished() on us itk::ThreadFunctionType fpointer = &StaticNonBlockingAlgorithmThread; m_ThreadID = m_MultiThreader->SpawnThread(fpointer, &m_ThreadParameters); } void NonBlockingAlgorithm::StopAlgorithm() { if (m_ThreadID == -1) return; // thread not running m_MultiThreader->TerminateThread(m_ThreadID); // waits for the thread to terminate on its own } // a static function to call a member of NonBlockingAlgorithm from inside an ITK thread ITK_THREAD_RETURN_TYPE NonBlockingAlgorithm::StaticNonBlockingAlgorithmThread(void *param) { // itk::MultiThreader provides an itk::MultiThreader::ThreadInfoStruct as parameter auto *itkmttis = static_cast(param); // we need the UserData part of that structure auto *flsp = static_cast(itkmttis->UserData); NonBlockingAlgorithm::Pointer algorithm = flsp->m_Algorithm; // this UserData tells us, which BubbleTool's method to call if (!algorithm) { return ITK_THREAD_RETURN_VALUE; } algorithm->m_ParameterListMutex->Lock(); while (algorithm->m_UpdateRequests > 0) { algorithm->m_UpdateRequests = 0; algorithm->m_ParameterListMutex->Unlock(); // actually call the methods that do the work if (algorithm->ThreadedUpdateFunction()) // returns a bool for success/failure { itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(algorithm, &NonBlockingAlgorithm::ThreadedUpdateSuccessful); CallbackFromGUIThread::GetInstance()->CallThisFromGUIThread(command); // algorithm->ThreadedUpdateSuccessful(); } else { itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(algorithm, &NonBlockingAlgorithm::ThreadedUpdateFailed); CallbackFromGUIThread::GetInstance()->CallThisFromGUIThread(command); // algorithm->ThreadedUpdateFailed(); } algorithm->m_ParameterListMutex->Lock(); } algorithm->m_ParameterListMutex->Unlock(); return ITK_THREAD_RETURN_VALUE; } void NonBlockingAlgorithm::TriggerParameterModified(const itk::EventObject &) { StartAlgorithm(); } bool NonBlockingAlgorithm::ReadyToRun() { return true; // default is always ready } bool NonBlockingAlgorithm::ThreadedUpdateFunction() { return true; } // called from gui thread void NonBlockingAlgorithm::ThreadedUpdateSuccessful(const itk::EventObject &) { ThreadedUpdateSuccessful(); m_ParameterListMutex->Lock(); m_ThreadID = -1; // tested before starting m_ParameterListMutex->Unlock(); m_ThreadParameters.m_Algorithm = nullptr; } void NonBlockingAlgorithm::ThreadedUpdateSuccessful() { // notify observers that a result is ready InvokeEvent(ResultAvailable(this)); } // called from gui thread void NonBlockingAlgorithm::ThreadedUpdateFailed(const itk::EventObject &) { ThreadedUpdateFailed(); m_ParameterListMutex->Lock(); m_ThreadID = -1; // tested before starting m_ParameterListMutex->Unlock(); m_ThreadParameters.m_Algorithm = nullptr; // delete } void NonBlockingAlgorithm::ThreadedUpdateFailed() { // notify observers that something went wrong InvokeEvent(ProcessingError(this)); } } // namespace diff --git a/Modules/AlgorithmsExt/src/mitkSimpleHistogram.cpp b/Modules/AlgorithmsExt/src/mitkSimpleHistogram.cpp index a569d0f71b..d4f9fffcba 100644 --- a/Modules/AlgorithmsExt/src/mitkSimpleHistogram.cpp +++ b/Modules/AlgorithmsExt/src/mitkSimpleHistogram.cpp @@ -1,315 +1,315 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSimpleHistogram.h" #include "mitkImageReadAccessor.h" #include "mitkSimpleUnstructuredGridHistogram.h" #include "mitkUnstructuredGrid.h" namespace mitk { void SimpleImageHistogram::ComputeFromBaseData(BaseData *src) { valid = false; // check if input is valid if (src == nullptr) return; auto *source = dynamic_cast(src); if (source == nullptr) return; else if (source->IsEmpty()) return; // dummy histogram { min = 0; max = 1; first = 0; last = 1; } { int typInt = 0; { const int typ = source->GetPixelType().GetComponentType(); if (typ == itk::ImageIOBase::UCHAR) typInt = 0; else if (typ == itk::ImageIOBase::CHAR) typInt = 1; else if (typ == itk::ImageIOBase::USHORT) typInt = 2; else if (typ == itk::ImageIOBase::SHORT) typInt = 3; else if (typ == itk::ImageIOBase::INT) typInt = 4; else if (typ == itk::ImageIOBase::UINT) typInt = 5; else if (typ == itk::ImageIOBase::LONG) typInt = 6; else if (typ == itk::ImageIOBase::ULONG) typInt = 7; else if (typ == itk::ImageIOBase::FLOAT) typInt = 8; else if (typ == itk::ImageIOBase::DOUBLE) typInt = 9; else { MITK_WARN << "Pixel type not supported by SimpleImageHistogram"; return; } } first = -32768; last = 65535; // support at least full signed and unsigned short range if (histogram) delete histogram; histogram = new CountType[last - first + 1]; memset(histogram, 0, sizeof(CountType) * (last - first + 1)); highest = 0; max = first - 1; min = last + 1; unsigned int num = 1; for (unsigned int r = 0; r < source->GetDimension(); r++) num *= source->GetDimension(r); // MITK_INFO << "building histogramm of integer image: 0=" << source->GetDimension(0) << " 1=" << // source->GetDimension(1) << " 2=" << source->GetDimension(2) << " 3=" << source->GetDimension(3); ImageReadAccessor sourceAcc(source); const void *src = sourceAcc.GetData(); do { int value = 0; switch (typInt) { case 0: { auto *t = (unsigned char *)src; value = *t++; src = (void *)t; } break; case 1: { auto *t = (signed char *)src; value = *t++; src = (void *)t; } break; case 2: { auto *t = (unsigned short *)src; value = *t++; src = (void *)t; } break; case 3: { auto *t = (signed short *)src; value = *t++; src = (void *)t; } break; case 4: { auto *t = (signed int *)src; value = *t++; src = (void *)t; } break; case 5: { auto *t = (unsigned int *)src; value = *t++; src = (void *)t; } break; case 6: { auto *t = (signed long *)src; value = *t++; src = (void *)t; } break; case 7: { auto *t = (unsigned long *)src; value = *t++; src = (void *)t; } break; case 8: { auto *t = (float *)src; value = *t++; src = (void *)t; } break; case 9: { auto *t = (double *)src; value = *t++; src = (void *)t; } break; } if (value >= first && value <= last) { if (value < min) min = value; if (value > max) max = value; CountType tmp = ++histogram[value - first]; if (tmp > highest) highest = tmp; } } while (--num); MITK_INFO << "histogramm computed: min=" << min << " max=" << max << " highestBin=" << highest << " samples=" << num; } invLogHighest = 1.0 / log(double(highest)); valid = true; } bool SimpleImageHistogram::GetValid() { return valid; } float SimpleImageHistogram::GetRelativeBin(double left, double right) const { if (!valid) return 0.0f; int iLeft = floorf(left); int iRight = ceilf(right); /* double sum = 0; for( int r = 0 ; r < 256 ; r++) { int pos = left + (right-left) * r/255.0; int posInArray = floorf(pos+0.5f) - first; sum += float(log(double(histogram[posInArray]))); } sum /= 256.0; return float(sum*invLogHighest); */ CountType maximum = 0; for (int i = iLeft; i <= iRight; i++) { int posInArray = i - first; if (histogram[posInArray] > maximum) maximum = histogram[posInArray]; } return float(log(double(maximum)) * invLogHighest); } class ImageHistogramCacheElement : public SimpleHistogramCache::Element { public: void ComputeFromBaseData(BaseData *baseData) override { histogram.ComputeFromBaseData(baseData); } SimpleHistogram *GetHistogram() override { return &histogram; } SimpleImageHistogram histogram; }; class UnstructuredGridHistogramCacheElement : public SimpleHistogramCache::Element { public: void ComputeFromBaseData(BaseData *baseData) override { histogram.ComputeFromBaseData(baseData); } SimpleHistogram *GetHistogram() override { return &histogram; } SimpleUnstructuredGridHistogram histogram; }; SimpleHistogram *SimpleHistogramCache::operator[](BaseData::Pointer sp_BaseData) { BaseData *p_BaseData = sp_BaseData.GetPointer(); if (!p_BaseData) { MITK_WARN << "SimpleHistogramCache::operator[] with null base data called"; return nullptr; } Element *elementToUpdate = nullptr; bool first = true; for (auto iter = cache.begin(); iter != cache.end(); iter++) { Element *e = *iter; - BaseData *p_tmp = e->baseData.GetPointer(); + BaseData *p_tmp = e->baseData.Lock(); if (p_tmp == p_BaseData) { if (!first) { cache.erase(iter); cache.push_front(e); } if (p_BaseData->GetMTime() > e->m_LastUpdateTime.GetMTime()) { elementToUpdate = e; goto recomputeElement; } // MITK_INFO << "using a cached histogram"; return e->GetHistogram(); } first = false; } if (dynamic_cast(p_BaseData)) { elementToUpdate = new ImageHistogramCacheElement(); } else if (dynamic_cast(p_BaseData)) { elementToUpdate = new UnstructuredGridHistogramCacheElement(); } else { MITK_WARN << "not supported: " << p_BaseData->GetNameOfClass(); } elementToUpdate->baseData = p_BaseData; cache.push_front(elementToUpdate); TrimCache(); recomputeElement: // MITK_INFO << "computing a new histogram"; elementToUpdate->ComputeFromBaseData(p_BaseData); elementToUpdate->m_LastUpdateTime.Modified(); return elementToUpdate->GetHistogram(); } SimpleHistogramCache::Element::~Element() {} } diff --git a/Modules/Core/include/mitkNodePredicateDataProperty.h b/Modules/Core/include/mitkNodePredicateDataProperty.h index 1d9363ca67..0a5b530707 100644 --- a/Modules/Core/include/mitkNodePredicateDataProperty.h +++ b/Modules/Core/include/mitkNodePredicateDataProperty.h @@ -1,57 +1,56 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKNODEPREDICATEDATAPROPERTY_H_HEADER_INCLUDED_ #define MITKNODEPREDICATEDATAPROPERTY_H_HEADER_INCLUDED_ #include "mitkBaseProperty.h" #include "mitkNodePredicateBase.h" namespace mitk { /** @brief Predicate that evaluates if the data of a given DataNode has a specific property. If the second parameter is nullptr, it will only be checked whether there is a property with the specified name for the data instance of the node.*/ class MITKCORE_EXPORT NodePredicateDataProperty : public NodePredicateBase { public: mitkClassMacro(NodePredicateDataProperty, NodePredicateBase); mitkNewMacro1Param(NodePredicateDataProperty, const char *); mitkNewMacro2Param(NodePredicateDataProperty, const char *, mitk::BaseProperty *); //##Documentation //## @brief Standard Destructor virtual ~NodePredicateDataProperty(); //##Documentation //## @brief Checks, if the nodes contains a property that is equal to m_ValidProperty virtual bool CheckNode(const mitk::DataNode *node) const override; protected: //##Documentation //## @brief Constructor to check for a named property NodePredicateDataProperty(const char *propertyName, mitk::BaseProperty *p = nullptr); - // mitk::WeakPointer m_ValidProperty; mitk::BaseProperty::Pointer m_ValidProperty; // mitk::BaseProperty* m_ValidProperty; std::string m_ValidPropertyName; }; } // namespace mitk #endif /* MITKNODEPREDICATEPROPERTY_H_HEADER_INCLUDED_ */ diff --git a/Modules/Core/include/mitkNodePredicateProperty.h b/Modules/Core/include/mitkNodePredicateProperty.h index 47ff92d0a0..05359a9546 100644 --- a/Modules/Core/include/mitkNodePredicateProperty.h +++ b/Modules/Core/include/mitkNodePredicateProperty.h @@ -1,68 +1,66 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKNODEPREDICATEPROPERTY_H_HEADER_INCLUDED_ #define MITKNODEPREDICATEPROPERTY_H_HEADER_INCLUDED_ #include "mitkBaseProperty.h" #include "mitkBaseRenderer.h" #include "mitkNodePredicateBase.h" -#include "mitkWeakPointer.h" namespace mitk { //##Documentation //## @brief Predicate that evaluates if the given DataNode has a specific property. //## If the second parameter is nullptr, it will only be checked whether there is a property with the specified name. //## If a renderer is specified in the third parameter the renderer-specific property will be checked. If this //## parameter is nullptr or not specified, then the non-renderer-specific property will be checked. //## //## //## //## @ingroup DataStorage class MITKCORE_EXPORT NodePredicateProperty : public NodePredicateBase { public: mitkClassMacro(NodePredicateProperty, NodePredicateBase); mitkNewMacro1Param(NodePredicateProperty, const char *); mitkNewMacro2Param(NodePredicateProperty, const char *, mitk::BaseProperty *); mitkNewMacro3Param(NodePredicateProperty, const char *, mitk::BaseProperty *, const mitk::BaseRenderer *); //##Documentation //## @brief Standard Destructor virtual ~NodePredicateProperty(); //##Documentation //## @brief Checks, if the nodes contains a property that is equal to m_ValidProperty virtual bool CheckNode(const mitk::DataNode *node) const override; protected: //##Documentation //## @brief Constructor to check for a named property NodePredicateProperty(const char *propertyName, mitk::BaseProperty *p = nullptr, const mitk::BaseRenderer *renderer = nullptr); - // mitk::WeakPointer m_ValidProperty; mitk::BaseProperty::Pointer m_ValidProperty; // mitk::BaseProperty* m_ValidProperty; std::string m_ValidPropertyName; const mitk::BaseRenderer *m_Renderer; }; } // namespace mitk #endif /* MITKNODEPREDICATEPROPERTY_H_HEADER_INCLUDED_ */ diff --git a/Modules/Core/include/mitkPlaneGeometryDataMapper2D.h b/Modules/Core/include/mitkPlaneGeometryDataMapper2D.h index 252728cbf5..8266d727a9 100644 --- a/Modules/Core/include/mitkPlaneGeometryDataMapper2D.h +++ b/Modules/Core/include/mitkPlaneGeometryDataMapper2D.h @@ -1,147 +1,146 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkPlaneGeometryDataMapper2D_h #define mitkPlaneGeometryDataMapper2D_h #include "mitkBaseRenderer.h" #include "mitkVtkMapper.h" #include -#include #include class vtkActor2D; class vtkPropAssembly; class vtkFloatArray; class vtkCellArray; class vtkPolyDataMapper2D; namespace mitk { /** * @brief Vtk-based 2D mapper for rendering a crosshair with the plane geometry. * * This mapper uses the mitkPlaneGeometryData from the three helper objects in * the StdMultiWidget to render a crosshair in all 2D render windows. The crosshair * is assembled as lines and rendered with a vtkPolyDataMapper. The mapper * requires multiple plane geometry to compute the correct crosshair position. * The plane bounds are computed using either ReferenceGeometry if it is present or * the plane geometry itself otherwise. * The mapper offers the following properties: * \b Crosshair.Line width: The thickness of the crosshair. * \b Crosshair.Gap Size: The gap between the lines in pixels. * \b Crosshair.Orientation Decoration: Adds a PlaneOrientationProperty, which * indicates the direction of the plane normal. See mitkPlaneOrientationProperty. * * @ingroup Mapper */ class MITKCORE_EXPORT PlaneGeometryDataMapper2D : public VtkMapper { public: mitkClassMacro(PlaneGeometryDataMapper2D, VtkMapper); itkFactorylessNewMacro(Self) itkCloneMacro(Self) virtual const mitk::PlaneGeometryData *GetInput() const; /** \brief returns the a prop assembly */ virtual vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; /** Applies properties specific to this mapper */ virtual void ApplyAllProperties(BaseRenderer *renderer); virtual void UpdateVtkTransform(mitk::BaseRenderer *renderer) override; /** \brief set the default properties for this mapper */ static void SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer = nullptr, bool overwrite = false); /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ class LocalStorage : public mitk::Mapper::BaseLocalStorage { public: /* constructor */ LocalStorage(); /* destructor */ ~LocalStorage(); // actor vtkSmartPointer m_CrosshairActor; vtkSmartPointer m_CrosshairHelperLineActor; vtkSmartPointer m_ArrowActor; vtkSmartPointer m_HelperLinesmapper; vtkSmartPointer m_Arrowmapper; vtkSmartPointer m_Mapper; vtkSmartPointer m_CrosshairAssembly; }; /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ mitk::LocalStorageHandler m_LSH; protected: /* constructor */ PlaneGeometryDataMapper2D(); /* destructor */ virtual ~PlaneGeometryDataMapper2D(); /* \brief Applies the color and opacity properties and calls CreateVTKRenderObjects */ virtual void GenerateDataForRenderer(mitk::BaseRenderer *renderer) override; void CreateVtkCrosshair(BaseRenderer *renderer); static bool TestPointInPlaneGeometry(const PlaneGeometry *planeGeometry, const Point3D &point); static bool TestPointInReferenceGeometry(const BaseGeometry *referenceGeometry, const Point3D &point); static bool CutCrossLineWithPlaneGeometry(const PlaneGeometry *planeGeometry, Line3D &crossLine); static bool CutCrossLineWithReferenceGeometry(const BaseGeometry *referenceGeometry, Line3D &crossLine); /** * \brief Returns the thick slice mode for the given datanode. * * This method returns the value of the 'reslice.thickslices' property for * the given datanode. * '0': thick slice mode disabled * '1': thick slice mode enabled * * The variable 'thickSlicesNum' contains the value of the 'reslice.thickslices.num' * property that defines how many slices are shown at once. */ int DetermineThickSliceMode(DataNode *dn, int &thickSlicesNum); void DrawLine(Point3D p0, Point3D p1, vtkCellArray *lines, vtkPoints *points); // member variables holding the current value of the properties used in this mapper typedef std::vector NodesVectorType; NodesVectorType m_OtherPlaneGeometries; typedef std::set AllInstancesContainer; static AllInstancesContainer s_AllInstances; bool m_RenderOrientationArrows; bool m_ArrowOrientationPositive; mitk::ScalarType m_DepthValue; void ApplyColorAndOpacityProperties2D(BaseRenderer *renderer, vtkActor2D *actor); void DrawOrientationArrow(vtkSmartPointer triangles, vtkSmartPointer triPoints, double triangleSizeMM, Vector3D &orthogonalVector, Point3D &point1, Point3D &point2); }; } // namespace mitk #endif /* mitkPlaneGeometryDataMapper2D_h */ diff --git a/Modules/Core/include/mitkWeakPointer.h b/Modules/Core/include/mitkWeakPointer.h index ba13d6a7d7..a09ff7e94d 100644 --- a/Modules/Core/include/mitkWeakPointer.h +++ b/Modules/Core/include/mitkWeakPointer.h @@ -1,189 +1,400 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#ifndef __mitkWeakPointer_h -#define __mitkWeakPointer_h +#ifndef mitkWeakPointer_h +#define mitkWeakPointer_h -#include "mitkMessage.h" -#include - -#include #include +#include namespace mitk { - /** \class WeakPointer - * \brief Implements a weak reference to an object. - * - * Extends the standard itk WeakPointer by listening to delete events of itk::Objects. - * When an itk::Object is deleted the WeakPointer sets its internal Pointer to 0. - * This enables checking against 0 and avoids crashes by accessing changed memory. - * Furthermore it dispatches Modified events with the mitkMessageDelegate system which is - * much easier to use. - */ - template - class WeakPointer + template + class WeakPointer final { public: - /** Extract infoirmation from template parameter. */ - typedef TObjectType ObjectType; + WeakPointer() noexcept + : m_RawPointer(nullptr) + { + } - typedef Message1 itkObjectEvent; - //##Documentation - //## @brief AddEvent is emitted when the object pointed to gets deleted - itkObjectEvent ObjectDelete; + WeakPointer(T *rawPointer) + : m_RawPointer(rawPointer) + { + this->AddDeleteEventObserver(); + } - //##Documentation - //## @brief AddEvent is emitted when the object pointed to gets modified - itkObjectEvent ObjectModified; + WeakPointer(const WeakPointer &other) + : m_RawPointer(other.m_RawPointer) + { + this->AddDeleteEventObserver(); + } - /** Constructor. */ - WeakPointer() : m_DeleteObserverTag(-1), m_ModifiedObserverTag(-1), m_Pointer(nullptr) {} - /** Copy constructor. */ - WeakPointer(const WeakPointer &p) - : m_DeleteObserverTag(-1), m_ModifiedObserverTag(-1), m_Pointer(p.m_Pointer) + WeakPointer(WeakPointer &&other) + : m_RawPointer(other.m_RawPointer) { - this->AddDeleteAndModifiedObserver(); + other.RemoveDeleteEventObserver(); + other.m_RawPointer = nullptr; + this->AddDeleteEventObserver(); } - /** Constructor to pointer p. */ - WeakPointer(ObjectType *p) : m_DeleteObserverTag(-1), m_ModifiedObserverTag(-1), m_Pointer(p) + ~WeakPointer() noexcept { - this->AddDeleteAndModifiedObserver(); + try + { + this->RemoveDeleteEventObserver(); + } + catch (...) + { + // Swallow. Otherwise, the application would terminate if another + // exception is already propagating. + } } - /** Destructor. */ - ~WeakPointer() + // Prefer classic implementation to copy-and-swap idiom. Swapping is + // non-trivial for this class as the observed object is keeping references + // to its observers. + WeakPointer & operator =(const WeakPointer &other) { - this->RemoveDeleteAndModifiedObserver(); + if (this != &other) + { + this->RemoveDeleteEventObserver(); + m_RawPointer = other.m_RawPointer; + this->AddDeleteEventObserver(); + } - m_Pointer = nullptr; + return *this; } - /** Overload operator ->. */ - ObjectType *operator->() const { return m_Pointer; } - /** Return pointer to object. */ - operator ObjectType *() const { return m_Pointer; } - /** Template comparison operators. */ - template - bool operator==(R r) const + WeakPointer & operator =(WeakPointer &&other) { - return (m_Pointer == (ObjectType *)r); + // No check for self-assignment as it is allowed to assume that the + // parameter is a unique reference to this argument. + + this->RemoveDeleteEventObserver(); + m_RawPointer = other.m_RawPointer; + other.m_RawPointer = nullptr; + this->AddDeleteEventObserver(); + + return *this; } - template - bool operator!=(R r) const + + WeakPointer & operator =(std::nullptr_t) { - return (m_Pointer != (ObjectType *)r); + this->RemoveDeleteEventObserver(); + m_RawPointer = nullptr; + + return *this; } - /** Access function to pointer. */ - ObjectType *GetPointer() const { return m_Pointer; } - /** Comparison of pointers. Less than comparison. */ - bool operator<(const WeakPointer &r) const { return (void *)m_Pointer < (void *)r.m_Pointer; } - /** Comparison of pointers. Greater than comparison. */ - bool operator>(const WeakPointer &r) const { return (void *)m_Pointer > (void *)r.m_Pointer; } - /** Comparison of pointers. Less than or equal to comparison. */ - bool operator<=(const WeakPointer &r) const { return (void *)m_Pointer <= (void *)r.m_Pointer; } - /** Comparison of pointers. Greater than or equal to comparison. */ - bool operator>=(const WeakPointer &r) const { return (void *)m_Pointer >= (void *)r.m_Pointer; } - /** Test if the pointer has been initialized */ - bool IsNotNull() const { return m_Pointer != nullptr; } - bool IsNull() const { return m_Pointer == nullptr; } - /** Overload operator assignment. */ - WeakPointer &operator=(const WeakPointer &r) { return this->operator=(r.GetPointer()); } - /** Overload operator assignment. */ - WeakPointer &operator=(ObjectType *r) + WeakPointer & operator =(T *other) { - this->RemoveDeleteAndModifiedObserver(); - m_Pointer = r; - this->AddDeleteAndModifiedObserver(); + if (m_RawPointer != other) + { + this->RemoveDeleteEventObserver(); + m_RawPointer = other; + this->AddDeleteEventObserver(); + } + return *this; } - /** Function to print object pointed to. */ - ObjectType *Print(std::ostream &os) const + explicit operator bool() const noexcept + { + return nullptr != m_RawPointer; + } + + bool IsExpired() const noexcept { - // This prints the object pointed to by the pointer - (*m_Pointer).Print(os); - return m_Pointer; + return !*this; } - /// - /// \brief Gets called when the object is deleted or modified. - /// - void OnObjectDelete(const itk::Object *caller, const itk::EventObject &) + itk::SmartPointer Lock() const { - // do not unsubscribe from this object. this would invalidate the iterator of the - // event listener vector (in itk::Object) and would lead to a crash - // instead: do nothing->object is going to be dead soon... - // this->RemoveDeleteAndModifiedObserver(); - m_Pointer = nullptr; - m_DeleteObserverTag = -1; - m_ModifiedObserverTag = -1; - ObjectDelete.Send(caller); + return m_RawPointer; } - void OnObjectModified(const itk::Object *caller, const itk::EventObject &) { ObjectModified.Send(caller); } private: - void AddDeleteAndModifiedObserver() + void AddDeleteEventObserver() { - if (m_DeleteObserverTag == -1 && m_ModifiedObserverTag == -1 && m_Pointer != nullptr) + if (nullptr != m_RawPointer) { - // add observer for delete event - typename itk::MemberCommand>::Pointer onObjectDelete = - itk::MemberCommand>::New(); + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &WeakPointer::OnDeleteEvent); + m_ObserverTag = m_RawPointer->AddObserver(itk::DeleteEvent(), command); + } + } + + void RemoveDeleteEventObserver() + { + if (nullptr != m_RawPointer) + m_RawPointer->RemoveObserver(m_ObserverTag); + } + + void OnDeleteEvent() noexcept + { + m_RawPointer = nullptr; - onObjectDelete->SetCallbackFunction(this, &WeakPointer::OnObjectDelete); - m_DeleteObserverTag = m_Pointer->AddObserver(itk::DeleteEvent(), onObjectDelete); + // Don't remove any observers from the observed object as it is about to + // die and can't handle this operation anymore. + } - // add observer for modified event - typename itk::MemberCommand>::Pointer onObjectModified = - itk::MemberCommand>::New(); + // The following comparison operators need access to class internals. + // All remaining comparison operators are implemented as non-member + // non-friend functions that use logical combinations of these non-member + // friend functions. - onObjectModified->SetCallbackFunction(this, &WeakPointer::OnObjectModified); - m_ModifiedObserverTag = m_Pointer->AddObserver(itk::ModifiedEvent(), onObjectModified); - } + friend bool operator ==(const WeakPointer &left, const WeakPointer &right) noexcept + { + return left.m_RawPointer == right.m_RawPointer; } - void RemoveDeleteAndModifiedObserver() + // Also covers comparisons to T::Pointer and T::ConstPointer as + // itk::SmartPointer can be implicitly converted to a raw pointer. + friend bool operator ==(const WeakPointer &left, const T *right) noexcept { - if (m_DeleteObserverTag >= 0 && m_ModifiedObserverTag >= 0 && m_Pointer != nullptr) - { - m_Pointer->RemoveObserver(m_DeleteObserverTag); - m_Pointer->RemoveObserver(m_ModifiedObserverTag); + return left.m_RawPointer == right; + } - m_DeleteObserverTag = -1; - m_ModifiedObserverTag = -1; - } + friend bool operator <(const WeakPointer &left, const WeakPointer &right) noexcept + { + // The specialization of std::less for any pointer type yields a total + // order, even if the built-in operator < doesn't. + return std::less()(left.m_RawPointer, right.m_RawPointer); } - long m_DeleteObserverTag; - long m_ModifiedObserverTag; + friend bool operator <(const WeakPointer &left, std::nullptr_t right) noexcept + { + return std::less()(left.m_RawPointer, right); + } - /** The pointer to the object referred to by this smart pointer. */ - ObjectType *m_Pointer; + friend bool operator <(std::nullptr_t left, const WeakPointer &right) noexcept + { + return std::less()(left, right.m_RawPointer); + } + + friend bool operator <(const WeakPointer &left, const T *right) noexcept + { + return std::less()(left.m_RawPointer, right); + } + + friend bool operator <(const T *left, const WeakPointer &right) noexcept + { + return std::less()(left, right.m_RawPointer); + } + + T *m_RawPointer; + + // m_ObserverTag is completely managed by the two methods + // AddDeleteEventObserver() and RemoveDeleteEventObserver(). There + // isn't any need to initialize or use it at all outside of these methods. + unsigned long m_ObserverTag; }; +} - template - std::ostream &operator<<(std::ostream &os, WeakPointer p) - { - p.Print(os); - return os; - } +template +bool operator !=(const mitk::WeakPointer &left, const mitk::WeakPointer &right) noexcept +{ + return !(left == right); +} + +template +bool operator <=(const mitk::WeakPointer &left, const mitk::WeakPointer &right) noexcept +{ + return !(right < left); +} + +template +bool operator >(const mitk::WeakPointer &left, const mitk::WeakPointer &right) noexcept +{ + return right < left; +} -} // end namespace mitk +template +bool operator >=(const mitk::WeakPointer &left, const mitk::WeakPointer &right) noexcept +{ + return !(left < right); +} + +template +bool operator ==(const mitk::WeakPointer &left, std::nullptr_t) noexcept +{ + return !left; +} + +template +bool operator !=(const mitk::WeakPointer &left, std::nullptr_t right) noexcept +{ + return !(left == right); +} + +template +bool operator ==(std::nullptr_t, const mitk::WeakPointer &right) noexcept +{ + return !right; +} + +template +bool operator !=(std::nullptr_t left, const mitk::WeakPointer &right) noexcept +{ + return !(left == right); +} + +template +bool operator <=(const mitk::WeakPointer &left, std::nullptr_t right) noexcept +{ + return !(right < left); +} + +template +bool operator >(const mitk::WeakPointer &left, std::nullptr_t right) noexcept +{ + return right < left; +} + +template +bool operator >=(const mitk::WeakPointer &left, std::nullptr_t right) noexcept +{ + return !(left < right); +} + +template +bool operator <=(std::nullptr_t left, const mitk::WeakPointer &right) noexcept +{ + return !(right < left); +} + +template +bool operator >(std::nullptr_t left, const mitk::WeakPointer &right) noexcept +{ + return right < left; +} + +template +bool operator >=(std::nullptr_t left, const mitk::WeakPointer &right) noexcept +{ + return !(left < right); +} + +template +bool operator !=(const mitk::WeakPointer &left, const T *right) noexcept +{ + return !(left == right); +} + +template +bool operator <=(const mitk::WeakPointer &left, const T *right) noexcept +{ + return !(right < left); +} + +template +bool operator >(const mitk::WeakPointer &left, const T *right) noexcept +{ + return right < left; +} + +template +bool operator >=(const mitk::WeakPointer &left, const T *right) noexcept +{ + return !(left < right); +} + +template +bool operator ==(const T *left, const mitk::WeakPointer &right) noexcept +{ + return right == left; +} + +template +bool operator !=(const T *left, const mitk::WeakPointer &right) noexcept +{ + return !(right == left); +} + +template +bool operator <=(const T *left, const mitk::WeakPointer &right) noexcept +{ + return !(right < left); +} + +template +bool operator >(const T *left, const mitk::WeakPointer &right) noexcept +{ + return right < left; +} + +template +bool operator >=(const T *left, const mitk::WeakPointer &right) noexcept +{ + return !(left < right); +} + +template +bool operator !=(const mitk::WeakPointer &left, itk::SmartPointer right) noexcept +{ + return !(left == right); +} + +template +bool operator <=(const mitk::WeakPointer &left, itk::SmartPointer right) noexcept +{ + return !(right < left); +} + +template +bool operator >(const mitk::WeakPointer &left, itk::SmartPointer right) noexcept +{ + return right < left; +} + +template +bool operator >=(const mitk::WeakPointer &left, itk::SmartPointer right) noexcept +{ + return !(left < right); +} + +template +bool operator ==(itk::SmartPointer left, const mitk::WeakPointer &right) noexcept +{ + return right == left; +} + +template +bool operator !=(itk::SmartPointer left, const mitk::WeakPointer &right) noexcept +{ + return !(right == left); +} + +template +bool operator <=(itk::SmartPointer left, const mitk::WeakPointer &right) noexcept +{ + return !(right < left); +} + +template +bool operator >(itk::SmartPointer left, const mitk::WeakPointer &right) noexcept +{ + return right < left; +} + +template +bool operator >=(itk::SmartPointer left, const mitk::WeakPointer &right) noexcept +{ + return !(left < right); +} #endif diff --git a/Modules/Core/src/DataManagement/mitkNodePredicateFirstLevel.cpp b/Modules/Core/src/DataManagement/mitkNodePredicateFirstLevel.cpp index 12099d8149..e239d9c322 100644 --- a/Modules/Core/src/DataManagement/mitkNodePredicateFirstLevel.cpp +++ b/Modules/Core/src/DataManagement/mitkNodePredicateFirstLevel.cpp @@ -1,37 +1,37 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkNodePredicateFirstLevel.h" mitk::NodePredicateFirstLevel::NodePredicateFirstLevel(mitk::DataStorage *ds) : NodePredicateBase(), m_DataStorage(ds) { } mitk::NodePredicateFirstLevel::~NodePredicateFirstLevel() { } bool mitk::NodePredicateFirstLevel::CheckNode(const mitk::DataNode *node) const { if (node == nullptr) throw std::invalid_argument("NodePredicateFirstLevel: invalid node"); - if (m_DataStorage.IsNull()) + if (m_DataStorage.IsExpired()) throw std::invalid_argument("NodePredicateFirstLevel: DataStorage is invalid"); - mitk::DataStorage::SetOfObjects::ConstPointer list = m_DataStorage->GetSources(node, nullptr, true); + mitk::DataStorage::SetOfObjects::ConstPointer list = m_DataStorage.Lock()->GetSources(node, nullptr, true); return (list->Size() == 0); } diff --git a/Modules/Core/src/DataManagement/mitkNodePredicateSource.cpp b/Modules/Core/src/DataManagement/mitkNodePredicateSource.cpp index 0bdd4a3580..3e9c17876d 100644 --- a/Modules/Core/src/DataManagement/mitkNodePredicateSource.cpp +++ b/Modules/Core/src/DataManagement/mitkNodePredicateSource.cpp @@ -1,39 +1,39 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkNodePredicateSource.h" mitk::NodePredicateSource::NodePredicateSource(mitk::DataNode *n, bool allsources, mitk::DataStorage *ds) : NodePredicateBase(), m_BaseNode(n), m_SearchAllSources(allsources), m_DataStorage(ds) { } mitk::NodePredicateSource::~NodePredicateSource() { } bool mitk::NodePredicateSource::CheckNode(const mitk::DataNode *childNode) const { if (m_DataStorage && m_BaseNode) { const mitk::DataStorage::SetOfObjects::STLContainerType sources = - m_DataStorage->GetSources(childNode, nullptr, !m_SearchAllSources)->CastToSTLConstContainer(); + m_DataStorage.Lock()->GetSources(childNode, nullptr, !m_SearchAllSources)->CastToSTLConstContainer(); - return std::find(sources.cbegin(), sources.cend(), m_BaseNode) != sources.cend(); + return std::find(sources.cbegin(), sources.cend(), m_BaseNode.Lock()) != sources.cend(); } return false; } diff --git a/Modules/Core/src/Interactions/mitkDataInteractor.cpp b/Modules/Core/src/Interactions/mitkDataInteractor.cpp index 7b48394a4a..2e4621aa55 100644 --- a/Modules/Core/src/Interactions/mitkDataInteractor.cpp +++ b/Modules/Core/src/Interactions/mitkDataInteractor.cpp @@ -1,100 +1,102 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDataInteractor.h" #include "mitkDataNode.h" #include "mitkStateMachineState.h" // Predefined internal events/signals const std::string mitk::DataInteractor::IntDeactivateMe = "DeactivateMe"; const std::string mitk::DataInteractor::IntLeaveWidget = "LeaveWidget"; const std::string mitk::DataInteractor::IntEnterWidget = "EnterWidget"; mitk::DataInteractor::DataInteractor() { } mitk::DataInteractor::~DataInteractor() { - if (m_DataNode.IsNotNull()) + if (!m_DataNode.IsExpired()) { - if (m_DataNode->GetDataInteractor() == this) - m_DataNode->SetDataInteractor(nullptr); + auto dataNode = m_DataNode.Lock(); + + if (dataNode->GetDataInteractor() == this) + dataNode->SetDataInteractor(nullptr); } } mitk::DataNode *mitk::DataInteractor::GetDataNode() const { - return m_DataNode; + return m_DataNode.Lock(); } void mitk::DataInteractor::SetDataNode(DataNode *dataNode) { if (dataNode == m_DataNode) return; - if (m_DataNode.IsNotNull()) - m_DataNode->SetDataInteractor(nullptr); + if (!m_DataNode.IsExpired()) + m_DataNode.Lock()->SetDataInteractor(nullptr); m_DataNode = dataNode; if (dataNode != nullptr) - m_DataNode->SetDataInteractor(this); + m_DataNode.Lock()->SetDataInteractor(this); this->DataNodeChanged(); } int mitk::DataInteractor::GetLayer() const { int layer = -1; - if (m_DataNode.IsNotNull()) - m_DataNode->GetIntProperty("layer", layer); + if (!m_DataNode.IsExpired()) + m_DataNode.Lock()->GetIntProperty("layer", layer); return layer; } void mitk::DataInteractor::ConnectActionsAndFunctions() { MITK_WARN << "DataInteractor::ConnectActionsAndFunctions() is not implemented."; } mitk::ProcessEventMode mitk::DataInteractor::GetMode() const { auto mode = this->GetCurrentState()->GetMode(); if (mode == "PREFER_INPUT") return PREFERINPUT; if (mode == "GRAB_INPUT") return GRABINPUT; return REGULAR; } void mitk::DataInteractor::NotifyStart() { this->GetDataNode()->InvokeEvent(StartInteraction()); } void mitk::DataInteractor::NotifyResultReady() { this->GetDataNode()->InvokeEvent(ResultReady()); } void mitk::DataInteractor::DataNodeChanged() { } diff --git a/Modules/Core/src/Interactions/mitkDispatcher.cpp b/Modules/Core/src/Interactions/mitkDispatcher.cpp index 66ec2b5e8c..1683621cf8 100644 --- a/Modules/Core/src/Interactions/mitkDispatcher.cpp +++ b/Modules/Core/src/Interactions/mitkDispatcher.cpp @@ -1,281 +1,284 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDispatcher.h" #include "mitkInteractionEvent.h" #include "mitkInteractionEventObserver.h" #include "mitkInternalEvent.h" #include "usGetModuleContext.h" namespace { struct cmp { - bool operator()(mitk::DataInteractor *d1, mitk::DataInteractor *d2) { return (d1->GetLayer() > d2->GetLayer()); } + bool operator()(mitk::WeakPointer d1, mitk::WeakPointer d2) + { + return (d1.Lock()->GetLayer() > d2.Lock()->GetLayer()); + } }; } mitk::Dispatcher::Dispatcher(const std::string &rendererName) : m_ProcessingMode(REGULAR) { // LDAP filter string to find all listeners specific for the renderer // corresponding to this dispatcher std::string specificRenderer = "(rendererName=" + rendererName + ")"; // LDAP filter string to find all listeners that are not specific // to any renderer std::string anyRenderer = "(!(rendererName=*))"; // LDAP filter string to find only instances of InteractionEventObserver std::string classInteractionEventObserver = "(" + us::ServiceConstants::OBJECTCLASS() + "=" + us_service_interface_iid() + ")"; // Configure the LDAP filter to find all instances of InteractionEventObserver // that are specific to this dispatcher or unspecific to any dispatchers (real global listener) us::LDAPFilter filter("(&(|" + specificRenderer + anyRenderer + ")" + classInteractionEventObserver + ")"); // Give the filter to the ObserverTracker m_EventObserverTracker = new us::ServiceTracker(us::GetModuleContext(), filter); m_EventObserverTracker->Open(); } void mitk::Dispatcher::AddDataInteractor(const DataNode *dataNode) { RemoveDataInteractor(dataNode); RemoveOrphanedInteractors(); auto dataInteractor = dataNode->GetDataInteractor().GetPointer(); if (dataInteractor != nullptr) m_Interactors.push_back(dataInteractor); } /* * Note: One DataInteractor can only have one DataNode and vice versa, * BUT the m_Interactors list may contain another DataInteractor that is still connected to this DataNode, * in this case we have to remove >1 DataInteractor. (Some special case of switching DataNodes between DataInteractors * and registering a * DataNode to a DataStorage after assigning it to an DataInteractor) */ void mitk::Dispatcher::RemoveDataInteractor(const DataNode *dataNode) { for (auto it = m_Interactors.begin(); it != m_Interactors.end();) { - if ((*it).IsNull() || (*it)->GetDataNode() == nullptr || (*it)->GetDataNode() == dataNode) + if ((*it).IsExpired() || (*it).Lock()->GetDataNode() == nullptr || (*it).Lock()->GetDataNode() == dataNode) { it = m_Interactors.erase(it); } else { ++it; } } } size_t mitk::Dispatcher::GetNumberOfInteractors() { return m_Interactors.size(); } mitk::Dispatcher::~Dispatcher() { m_EventObserverTracker->Close(); delete m_EventObserverTracker; m_Interactors.clear(); } bool mitk::Dispatcher::ProcessEvent(InteractionEvent *event) { InteractionEvent::Pointer p = event; bool eventIsHandled = false; /* Filter out and handle Internal Events separately */ auto *internalEvent = dynamic_cast(event); if (internalEvent != nullptr) { eventIsHandled = HandleInternalEvent(internalEvent); // InternalEvents that are handled are not sent to the listeners if (eventIsHandled) { return true; } } switch (m_ProcessingMode) { case CONNECTEDMOUSEACTION: // finished connected mouse action if (std::strcmp(p->GetNameOfClass(), "MouseReleaseEvent") == 0) { m_ProcessingMode = REGULAR; - if (m_SelectedInteractor.IsNotNull()) - eventIsHandled = m_SelectedInteractor->HandleEvent(event, m_SelectedInteractor->GetDataNode()); + if (!m_SelectedInteractor.IsExpired()) + eventIsHandled = m_SelectedInteractor.Lock()->HandleEvent(event, m_SelectedInteractor.Lock()->GetDataNode()); m_SelectedInteractor = nullptr; } // give event to selected interactor - if (eventIsHandled == false && m_SelectedInteractor.IsNotNull()) - eventIsHandled = m_SelectedInteractor->HandleEvent(event, m_SelectedInteractor->GetDataNode()); + if (eventIsHandled == false && !m_SelectedInteractor.IsExpired()) + eventIsHandled = m_SelectedInteractor.Lock()->HandleEvent(event, m_SelectedInteractor.Lock()->GetDataNode()); break; case GRABINPUT: - if (m_SelectedInteractor.IsNotNull()) + if (!m_SelectedInteractor.IsExpired()) { - eventIsHandled = m_SelectedInteractor->HandleEvent(event, m_SelectedInteractor->GetDataNode()); - SetEventProcessingMode(m_SelectedInteractor); + eventIsHandled = m_SelectedInteractor.Lock()->HandleEvent(event, m_SelectedInteractor.Lock()->GetDataNode()); + SetEventProcessingMode(m_SelectedInteractor.Lock()); } break; case PREFERINPUT: - if (m_SelectedInteractor.IsNotNull() && - m_SelectedInteractor->HandleEvent(event, m_SelectedInteractor->GetDataNode()) == true) + if (!m_SelectedInteractor.IsExpired() && + m_SelectedInteractor.Lock()->HandleEvent(event, m_SelectedInteractor.Lock()->GetDataNode()) == true) { - SetEventProcessingMode(m_SelectedInteractor); + SetEventProcessingMode(m_SelectedInteractor.Lock()); eventIsHandled = true; } break; case REGULAR: break; } // Standard behavior. Is executed in STANDARD mode and PREFERINPUT mode, if preferred interactor rejects event. if (m_ProcessingMode == REGULAR || (m_ProcessingMode == PREFERINPUT && eventIsHandled == false)) { if (std::strcmp(p->GetNameOfClass(), "MousePressEvent") == 0) event->GetSender()->GetRenderingManager()->SetRenderWindowFocus(event->GetSender()->GetRenderWindow()); m_Interactors.sort(cmp()); // sorts interactors by layer (descending); // copy the list to prevent iterator invalidation as executing actions // in HandleEvent() can cause the m_Interactors list to be updated const ListInteractorType tmpInteractorList(m_Interactors); ListInteractorType::const_iterator it; for (it = tmpInteractorList.cbegin(); it != tmpInteractorList.cend(); ++it) { - if ((*it).IsNotNull() && (*it)->HandleEvent(event, (*it)->GetDataNode())) + if (!(*it).IsExpired() && (*it).Lock()->HandleEvent(event, (*it).Lock()->GetDataNode())) { // Interactor can be deleted during HandleEvent(), so check it again - if ((*it).IsNotNull()) + if (!(*it).IsExpired()) { // if an event is handled several properties are checked, in order to determine the processing mode of the // dispatcher - SetEventProcessingMode(*it); + SetEventProcessingMode((*it).Lock()); } if (std::strcmp(p->GetNameOfClass(), "MousePressEvent") == 0 && m_ProcessingMode == REGULAR) { m_SelectedInteractor = *it; m_ProcessingMode = CONNECTEDMOUSEACTION; } eventIsHandled = true; break; } } } /* Notify InteractionEventObserver */ const std::vector> listEventObserver = m_EventObserverTracker->GetServiceReferences(); for (auto it = listEventObserver.cbegin(); it != listEventObserver.cend(); ++it) { InteractionEventObserver *interactionEventObserver = m_EventObserverTracker->GetService(*it); if (interactionEventObserver != nullptr) { if (interactionEventObserver->IsEnabled()) { interactionEventObserver->Notify(event, eventIsHandled); } } } // Process event queue if (!m_QueuedEvents.empty()) { InteractionEvent::Pointer e = m_QueuedEvents.front(); m_QueuedEvents.pop_front(); ProcessEvent(e); } return eventIsHandled; } /* * Checks if DataNodes associated with DataInteractors point back to them. * If not remove the DataInteractors. (This can happen when s.o. tries to set DataNodes to multiple DataInteractors) */ void mitk::Dispatcher::RemoveOrphanedInteractors() { for (auto it = m_Interactors.begin(); it != m_Interactors.end();) { - if ((*it).IsNull()) + if ((*it).IsExpired()) { it = m_Interactors.erase(it); } else { - DataNode::Pointer node = (*it)->GetDataNode(); + DataNode::Pointer node = (*it).Lock()->GetDataNode(); if (node.IsNull()) { it = m_Interactors.erase(it); } else { DataInteractor::Pointer interactor = node->GetDataInteractor(); - if (interactor != it->GetPointer()) + if (interactor != it->Lock().GetPointer()) { it = m_Interactors.erase(it); } else { ++it; } } } } } void mitk::Dispatcher::QueueEvent(InteractionEvent *event) { m_QueuedEvents.push_back(event); } void mitk::Dispatcher::SetEventProcessingMode(DataInteractor *dataInteractor) { m_ProcessingMode = dataInteractor->GetMode(); if (dataInteractor->GetMode() != REGULAR) { m_SelectedInteractor = dataInteractor; } } bool mitk::Dispatcher::HandleInternalEvent(InternalEvent *internalEvent) { if (internalEvent->GetSignalName() == DataInteractor::IntDeactivateMe && internalEvent->GetTargetInteractor() != nullptr) { internalEvent->GetTargetInteractor()->GetDataNode()->SetDataInteractor(nullptr); internalEvent->GetTargetInteractor()->SetDataNode(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return true; } return false; } diff --git a/Modules/Core/src/Interactions/mitkDisplayCoordinateOperation.cpp b/Modules/Core/src/Interactions/mitkDisplayCoordinateOperation.cpp index cca852cf88..a9e5c3e53c 100644 --- a/Modules/Core/src/Interactions/mitkDisplayCoordinateOperation.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayCoordinateOperation.cpp @@ -1,69 +1,69 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDisplayCoordinateOperation.h" mitk::DisplayCoordinateOperation::DisplayCoordinateOperation(mitk::OperationType operationType, mitk::BaseRenderer *renderer, const mitk::Point2D &startDisplayCoordinate, const mitk::Point2D &lastDisplayCoordinate, const mitk::Point2D ¤tDisplayCoordinate) : mitk::Operation(operationType), m_Renderer(renderer), m_StartDisplayCoordinate(startDisplayCoordinate), m_LastDisplayCoordinate(lastDisplayCoordinate), m_CurrentDisplayCoordinate(currentDisplayCoordinate) { } mitk::DisplayCoordinateOperation::DisplayCoordinateOperation(mitk::OperationType operationType, mitk::BaseRenderer *renderer, const mitk::Point2D &startDisplayCoordinate, const mitk::Point2D &lastDisplayCoordinate, const mitk::Point2D ¤tDisplayCoordinate, const mitk::Point2D &startCoordinateInMM) : mitk::Operation(operationType), m_Renderer(renderer), m_StartDisplayCoordinate(startDisplayCoordinate), m_LastDisplayCoordinate(lastDisplayCoordinate), m_CurrentDisplayCoordinate(currentDisplayCoordinate), m_StartCoordinateInMM(startCoordinateInMM) { } mitk::DisplayCoordinateOperation::~DisplayCoordinateOperation() { } mitk::BaseRenderer *mitk::DisplayCoordinateOperation::GetRenderer() { - return m_Renderer; + return m_Renderer.Lock(); } mitk::Vector2D mitk::DisplayCoordinateOperation::GetLastToCurrentDisplayVector() { return m_CurrentDisplayCoordinate - m_LastDisplayCoordinate; } mitk::Vector2D mitk::DisplayCoordinateOperation::GetStartToCurrentDisplayVector() { return m_CurrentDisplayCoordinate - m_StartDisplayCoordinate; } mitk::Vector2D mitk::DisplayCoordinateOperation::GetStartToLastDisplayVector() { return m_LastDisplayCoordinate - m_StartDisplayCoordinate; } diff --git a/Modules/Core/src/Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp b/Modules/Core/src/Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp index d3c4e18a87..5867760e97 100644 --- a/Modules/Core/src/Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp +++ b/Modules/Core/src/Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp @@ -1,589 +1,589 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlaneGeometryDataVtkMapper3D.h" #include "mitkImageVtkMapper2D.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateOr.h" #include "mitkSmartPointerProperty.h" #include "mitkSurface.h" #include "mitkVtkRepresentationProperty.h" #include "mitkWeakPointerProperty.h" #include "vtkMitkLevelWindowFilter.h" #include "vtkNeverTranslucentTexture.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { PlaneGeometryDataVtkMapper3D::PlaneGeometryDataVtkMapper3D() : m_NormalsActorAdded(false), m_DataStorage(nullptr) { m_EdgeTuber = vtkTubeFilter::New(); m_EdgeMapper = vtkPolyDataMapper::New(); m_SurfaceCreator = PlaneGeometryDataToSurfaceFilter::New(); m_SurfaceCreatorBoundingBox = BoundingBox::New(); m_SurfaceCreatorPointsContainer = BoundingBox::PointsContainer::New(); m_Edges = vtkFeatureEdges::New(); m_Edges->BoundaryEdgesOn(); m_Edges->FeatureEdgesOff(); m_Edges->NonManifoldEdgesOff(); m_Edges->ManifoldEdgesOff(); m_EdgeTransformer = vtkTransformPolyDataFilter::New(); m_NormalsTransformer = vtkTransformPolyDataFilter::New(); m_EdgeActor = vtkActor::New(); m_BackgroundMapper = vtkPolyDataMapper::New(); m_BackgroundActor = vtkActor::New(); m_Prop3DAssembly = vtkAssembly::New(); m_ImageAssembly = vtkAssembly::New(); m_SurfaceCreatorBoundingBox->SetPoints(m_SurfaceCreatorPointsContainer); m_Cleaner = vtkCleanPolyData::New(); m_Cleaner->PieceInvariantOn(); m_Cleaner->ConvertLinesToPointsOn(); m_Cleaner->ConvertPolysToLinesOn(); m_Cleaner->ConvertStripsToPolysOn(); m_Cleaner->PointMergingOn(); // Make sure that the FeatureEdge algorithm is initialized with a "valid" // (though empty) input vtkPolyData *emptyPolyData = vtkPolyData::New(); m_Cleaner->SetInputData(emptyPolyData); emptyPolyData->Delete(); m_Edges->SetInputConnection(m_Cleaner->GetOutputPort()); m_EdgeTransformer->SetInputConnection(m_Edges->GetOutputPort()); m_EdgeTuber->SetInputConnection(m_EdgeTransformer->GetOutputPort()); m_EdgeTuber->SetVaryRadiusToVaryRadiusOff(); m_EdgeTuber->SetNumberOfSides(12); m_EdgeTuber->CappingOn(); m_EdgeMapper->SetInputConnection(m_EdgeTuber->GetOutputPort()); m_EdgeMapper->ScalarVisibilityOff(); m_BackgroundMapper->SetInputData(emptyPolyData); m_BackgroundMapper->Update(); m_EdgeActor->SetMapper(m_EdgeMapper); m_BackgroundActor->GetProperty()->SetAmbient(0.5); m_BackgroundActor->GetProperty()->SetColor(0.0, 0.0, 0.0); m_BackgroundActor->GetProperty()->SetOpacity(0.0); m_BackgroundActor->SetMapper(m_BackgroundMapper); vtkProperty *backfaceProperty = m_BackgroundActor->MakeProperty(); backfaceProperty->SetColor(0.0, 0.0, 0.0); m_BackgroundActor->SetBackfaceProperty(backfaceProperty); backfaceProperty->Delete(); m_FrontHedgeHog = vtkHedgeHog::New(); m_BackHedgeHog = vtkHedgeHog::New(); m_FrontNormalsMapper = vtkPolyDataMapper::New(); m_FrontNormalsMapper->SetInputConnection(m_FrontHedgeHog->GetOutputPort()); m_BackNormalsMapper = vtkPolyDataMapper::New(); m_Prop3DAssembly->AddPart(m_EdgeActor); m_Prop3DAssembly->AddPart(m_ImageAssembly); m_FrontNormalsActor = vtkActor::New(); m_FrontNormalsActor->SetMapper(m_FrontNormalsMapper); m_BackNormalsActor = vtkActor::New(); m_BackNormalsActor->SetMapper(m_BackNormalsMapper); m_ImageMapperDeletedCommand = MemberCommandType::New(); m_ImageMapperDeletedCommand->SetCallbackFunction(this, &PlaneGeometryDataVtkMapper3D::ImageMapperDeletedCallback); } PlaneGeometryDataVtkMapper3D::~PlaneGeometryDataVtkMapper3D() { m_ImageAssembly->Delete(); m_Prop3DAssembly->Delete(); m_EdgeTuber->Delete(); m_EdgeMapper->Delete(); m_EdgeTransformer->Delete(); m_Cleaner->Delete(); m_Edges->Delete(); m_NormalsTransformer->Delete(); m_EdgeActor->Delete(); m_BackgroundMapper->Delete(); m_BackgroundActor->Delete(); m_FrontNormalsMapper->Delete(); m_FrontNormalsActor->Delete(); m_FrontHedgeHog->Delete(); m_BackNormalsMapper->Delete(); m_BackNormalsActor->Delete(); m_BackHedgeHog->Delete(); for (auto it = m_ImageActors.begin(); it != m_ImageActors.end(); ++it) it->second.m_Actor->ReleaseGraphicsResources(nullptr); // Delete entries in m_ImageActors list one by one m_ImageActors.clear(); m_DataStorage = nullptr; } vtkProp *PlaneGeometryDataVtkMapper3D::GetVtkProp(mitk::BaseRenderer * /*renderer*/) { if ((this->GetDataNode() != nullptr) && (m_ImageAssembly != nullptr)) { // Do not transform the entire Prop3D assembly, but only the image part // here. The colored frame is transformed elsewhere (via m_EdgeTransformer), // since only vertices should be transformed there, not the poly data // itself, to avoid distortion for anisotropic datasets. m_ImageAssembly->SetUserTransform(this->GetDataNode()->GetVtkTransform()); } return m_Prop3DAssembly; } void PlaneGeometryDataVtkMapper3D::UpdateVtkTransform(mitk::BaseRenderer * /*renderer*/) { m_ImageAssembly->SetUserTransform(this->GetDataNode()->GetVtkTransform(this->GetTimestep())); } const PlaneGeometryData *PlaneGeometryDataVtkMapper3D::GetInput() { return static_cast(GetDataNode()->GetData()); } void PlaneGeometryDataVtkMapper3D::SetDataStorageForTexture(mitk::DataStorage *storage) { if (storage != nullptr && m_DataStorage != storage) { m_DataStorage = storage; this->Modified(); } } void PlaneGeometryDataVtkMapper3D::ImageMapperDeletedCallback(itk::Object *caller, const itk::EventObject & /*event*/) { auto *imageMapper = dynamic_cast(caller); if ((imageMapper != nullptr)) { if (m_ImageActors.count(imageMapper) > 0) { m_ImageActors[imageMapper].m_Sender = nullptr; // sender is already destroying itself m_ImageActors.erase(imageMapper); } } } void PlaneGeometryDataVtkMapper3D::GenerateDataForRenderer(BaseRenderer *renderer) { SetVtkMapperImmediateModeRendering(m_EdgeMapper); SetVtkMapperImmediateModeRendering(m_BackgroundMapper); // Remove all actors from the assembly, and re-initialize it with the // edge actor m_ImageAssembly->GetParts()->RemoveAllItems(); bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); if (!visible) { // visibility has explicitly to be set in the single actors // due to problems when using cell picking: // even if the assembly is invisible, the renderer contains // references to the assemblies parts. During picking the // visibility of each part is checked, and not only for the // whole assembly. m_ImageAssembly->VisibilityOff(); m_EdgeActor->VisibilityOff(); return; } // visibility has explicitly to be set in the single actors // due to problems when using cell picking: // even if the assembly is invisible, the renderer contains // references to the assemblies parts. During picking the // visibility of each part is checked, and not only for the // whole assembly. m_ImageAssembly->VisibilityOn(); bool drawEdges = true; this->GetDataNode()->GetBoolProperty("draw edges", drawEdges, renderer); m_EdgeActor->SetVisibility(drawEdges); PlaneGeometryData::Pointer input = const_cast(this->GetInput()); if (input.IsNotNull() && (input->GetPlaneGeometry() != nullptr)) { SmartPointerProperty::Pointer surfacecreatorprop; surfacecreatorprop = dynamic_cast(GetDataNode()->GetProperty("surfacegeometry", renderer)); if ((surfacecreatorprop.IsNull()) || (surfacecreatorprop->GetSmartPointer().IsNull()) || ((m_SurfaceCreator = dynamic_cast(surfacecreatorprop->GetSmartPointer().GetPointer())) .IsNull())) { m_SurfaceCreator->PlaceByGeometryOn(); surfacecreatorprop = SmartPointerProperty::New(m_SurfaceCreator); GetDataNode()->SetProperty("surfacegeometry", surfacecreatorprop); } m_SurfaceCreator->SetInput(input); int res; if (GetDataNode()->GetIntProperty("xresolution", res, renderer)) { m_SurfaceCreator->SetXResolution(res); } if (GetDataNode()->GetIntProperty("yresolution", res, renderer)) { m_SurfaceCreator->SetYResolution(res); } double tubeRadius = 1.0; // Radius of tubular edge surrounding plane // Clip the PlaneGeometry with the reference geometry bounds (if available) if (input->GetPlaneGeometry()->HasReferenceGeometry()) { const BaseGeometry *referenceGeometry = input->GetPlaneGeometry()->GetReferenceGeometry(); BoundingBox::PointType boundingBoxMin, boundingBoxMax; boundingBoxMin = referenceGeometry->GetBoundingBox()->GetMinimum(); boundingBoxMax = referenceGeometry->GetBoundingBox()->GetMaximum(); if (referenceGeometry->GetImageGeometry()) { for (unsigned int i = 0; i < 3; ++i) { boundingBoxMin[i] -= 0.5; boundingBoxMax[i] -= 0.5; } } m_SurfaceCreatorPointsContainer->CreateElementAt(0) = boundingBoxMin; m_SurfaceCreatorPointsContainer->CreateElementAt(1) = boundingBoxMax; m_SurfaceCreatorBoundingBox->ComputeBoundingBox(); m_SurfaceCreator->SetBoundingBox(m_SurfaceCreatorBoundingBox); tubeRadius = referenceGeometry->GetDiagonalLength() / 450.0; } // If no reference geometry is available, clip with the current global // bounds - else if (m_DataStorage.IsNotNull()) + else if (!m_DataStorage.IsExpired()) { - m_SurfaceCreator->SetBoundingBox(m_DataStorage->ComputeVisibleBoundingBox(nullptr, "includeInBoundingBox")); + m_SurfaceCreator->SetBoundingBox(m_DataStorage.Lock()->ComputeVisibleBoundingBox(nullptr, "includeInBoundingBox")); tubeRadius = sqrt(m_SurfaceCreator->GetBoundingBox()->GetDiagonalLength2()) / 450.0; } // Calculate the surface of the PlaneGeometry m_SurfaceCreator->Update(); Surface *surface = m_SurfaceCreator->GetOutput(); // Check if there's something to display, otherwise return if ((surface->GetVtkPolyData() == nullptr) || (surface->GetVtkPolyData()->GetNumberOfCells() == 0)) { m_ImageAssembly->VisibilityOff(); return; } // add a graphical representation of the surface normals if requested DataNode *node = this->GetDataNode(); bool displayNormals = false; bool colorTwoSides = false; bool invertNormals = false; node->GetBoolProperty("draw normals 3D", displayNormals, renderer); node->GetBoolProperty("color two sides", colorTwoSides, renderer); node->GetBoolProperty("invert normals", invertNormals, renderer); // if we want to draw the display normals or render two sides we have to get the colors if (displayNormals || colorTwoSides) { // get colors float frontColor[3] = {0.0, 0.0, 1.0}; node->GetColor(frontColor, renderer, "front color"); float backColor[3] = {1.0, 0.0, 0.0}; node->GetColor(backColor, renderer, "back color"); if (displayNormals) { m_NormalsTransformer->SetInputData(surface->GetVtkPolyData()); m_NormalsTransformer->SetTransform(node->GetVtkTransform(this->GetTimestep())); m_FrontHedgeHog->SetInputConnection(m_NormalsTransformer->GetOutputPort()); m_FrontHedgeHog->SetVectorModeToUseNormal(); m_FrontHedgeHog->SetScaleFactor(invertNormals ? 1.0 : -1.0); m_FrontHedgeHog->Update(); m_FrontNormalsActor->GetProperty()->SetColor(frontColor[0], frontColor[1], frontColor[2]); m_BackHedgeHog->SetInputConnection(m_NormalsTransformer->GetOutputPort()); m_BackHedgeHog->SetVectorModeToUseNormal(); m_BackHedgeHog->SetScaleFactor(invertNormals ? -1.0 : 1.0); m_BackHedgeHog->Update(); m_BackNormalsActor->GetProperty()->SetColor(backColor[0], backColor[1], backColor[2]); // if there is no actor added yet, add one if (!m_NormalsActorAdded) { m_Prop3DAssembly->AddPart(m_FrontNormalsActor); m_Prop3DAssembly->AddPart(m_BackNormalsActor); m_NormalsActorAdded = true; } } // if we don't want to display normals AND there is an actor added remove the actor else if (m_NormalsActorAdded) { m_Prop3DAssembly->RemovePart(m_FrontNormalsActor); m_Prop3DAssembly->RemovePart(m_BackNormalsActor); m_NormalsActorAdded = false; } if (colorTwoSides) { if (!invertNormals) { m_BackgroundActor->GetProperty()->SetColor(backColor[0], backColor[1], backColor[2]); m_BackgroundActor->GetBackfaceProperty()->SetColor(frontColor[0], frontColor[1], frontColor[2]); } else { m_BackgroundActor->GetProperty()->SetColor(frontColor[0], frontColor[1], frontColor[2]); m_BackgroundActor->GetBackfaceProperty()->SetColor(backColor[0], backColor[1], backColor[2]); } } } // Add black background for all images (which may be transparent) m_BackgroundMapper->SetInputData(surface->GetVtkPolyData()); // m_ImageAssembly->AddPart(m_BackgroundActor); LayerSortedActorList layerSortedActors; // Traverse the data tree to find nodes resliced by ImageMapperGL2D // use a predicate to get all data nodes which are "images" or inherit from mitk::Image mitk::TNodePredicateDataType::Pointer predicateAllImages = mitk::TNodePredicateDataType::New(); - mitk::DataStorage::SetOfObjects::ConstPointer all = m_DataStorage->GetSubset(predicateAllImages); + mitk::DataStorage::SetOfObjects::ConstPointer all = m_DataStorage.Lock()->GetSubset(predicateAllImages); // process all found images for (mitk::DataStorage::SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode *node = it->Value(); if (node != nullptr) this->ProcessNode(node, renderer, surface, layerSortedActors); } // Add all image actors to the assembly, sorted according to // layer property LayerSortedActorList::iterator actorIt; for (actorIt = layerSortedActors.begin(); actorIt != layerSortedActors.end(); ++actorIt) { m_ImageAssembly->AddPart(actorIt->second); } // Configurate the tube-shaped frame: size according to the surface // bounds, color as specified in the plane's properties vtkPolyData *surfacePolyData = surface->GetVtkPolyData(); m_Cleaner->SetInputData(surfacePolyData); m_EdgeTransformer->SetTransform(this->GetDataNode()->GetVtkTransform(this->GetTimestep())); // Adjust the radius according to extent m_EdgeTuber->SetRadius(tubeRadius); // Get the plane's color and set the tube properties accordingly ColorProperty::Pointer colorProperty; colorProperty = dynamic_cast(this->GetDataNode()->GetProperty("color")); if (colorProperty.IsNotNull()) { const Color &color = colorProperty->GetColor(); m_EdgeActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); } else { m_EdgeActor->GetProperty()->SetColor(1.0, 1.0, 1.0); } m_ImageAssembly->SetUserTransform(this->GetDataNode()->GetVtkTransform(this->GetTimestep())); } VtkRepresentationProperty *representationProperty; this->GetDataNode()->GetProperty(representationProperty, "material.representation", renderer); if (representationProperty != nullptr) m_BackgroundActor->GetProperty()->SetRepresentation(representationProperty->GetVtkRepresentation()); } void PlaneGeometryDataVtkMapper3D::ProcessNode(DataNode *node, BaseRenderer *renderer, Surface *surface, LayerSortedActorList &layerSortedActors) { if (node != nullptr) { // we need to get the information from the 2D mapper to render the texture on the 3D plane auto *imageMapper = dynamic_cast(node->GetMapper(1)); // GetMapper(1) provides the 2D mapper for the data node // if there is a 2D mapper, which is not the standard image mapper... if (!imageMapper && node->GetMapper(1)) { //... check if it is the composite mapper std::string cname(node->GetMapper(1)->GetNameOfClass()); if (!cname.compare("CompositeMapper")) // string.compare returns 0 if the two strings are equal. { // get the standard image mapper. // This is a special case in MITK and does only work for the CompositeMapper. imageMapper = dynamic_cast(node->GetMapper(3)); } } if ((node->IsVisible(renderer)) && imageMapper) { WeakPointerProperty::Pointer rendererProp = dynamic_cast(GetDataNode()->GetPropertyList()->GetProperty("renderer")); if (rendererProp.IsNotNull()) { BaseRenderer::Pointer planeRenderer = dynamic_cast(rendererProp->GetWeakPointer().GetPointer()); // Retrieve and update image to be mapped const ImageVtkMapper2D::LocalStorage *localStorage = imageMapper->GetLocalStorage(planeRenderer); if (planeRenderer.IsNotNull()) { // perform update of imagemapper if needed (maybe the respective 2D renderwindow is not rendered/update // before) imageMapper->Update(planeRenderer); // If it has not been initialized already in a previous pass, // generate an actor and a texture object to // render the image associated with the ImageVtkMapper2D. vtkActor *imageActor; vtkDataSetMapper *dataSetMapper = nullptr; vtkTexture *texture; if (m_ImageActors.count(imageMapper) == 0) { dataSetMapper = vtkDataSetMapper::New(); // Enable rendering without copying the image. dataSetMapper->ImmediateModeRenderingOn(); texture = vtkNeverTranslucentTexture::New(); texture->RepeatOff(); imageActor = vtkActor::New(); imageActor->SetMapper(dataSetMapper); imageActor->SetTexture(texture); imageActor->GetProperty()->SetOpacity( 0.999); // HACK! otherwise VTK wouldn't recognize this as translucent // surface (if LUT values map to alpha < 255 // improvement: apply "opacity" property onle HERE and also in 2D image mapper. DO NOT change LUT to // achieve // translucent images (see method ChangeOpacity in image mapper 2D) // Make imageActor the sole owner of the mapper and texture // objects dataSetMapper->UnRegister(nullptr); texture->UnRegister(nullptr); // Store the actor so that it may be accessed in following // passes. m_ImageActors[imageMapper].Initialize(imageActor, imageMapper, m_ImageMapperDeletedCommand); } else { // Else, retrieve the actor and associated objects from the // previous pass. imageActor = m_ImageActors[imageMapper].m_Actor; dataSetMapper = (vtkDataSetMapper *)imageActor->GetMapper(); texture = imageActor->GetTexture(); } // Set poly data new each time its object changes (e.g. when // switching between planar and curved geometries) if ((dataSetMapper != nullptr) && (dataSetMapper->GetInput() != surface->GetVtkPolyData())) { dataSetMapper->SetInputData(surface->GetVtkPolyData()); } dataSetMapper->Update(); // Check if the m_ReslicedImage is nullptr. // This is the case when no image geometry is met by // the reslicer. In that case, the texture has to be // empty (black) and we don't have to do anything. // See fixed bug #13275 if (localStorage->m_ReslicedImage != nullptr) { texture->SetInputConnection(localStorage->m_LevelWindowFilter->GetOutputPort()); // do not use a VTK lookup table (we do that ourselves in m_LevelWindowFilter) texture->MapColorScalarsThroughLookupTableOff(); // re-use properties from the 2D image mapper imageActor->SetProperty(localStorage->m_Actor->GetProperty()); imageActor->GetProperty()->SetAmbient(0.5); // Set texture interpolation on/off bool textureInterpolation = node->IsOn("texture interpolation", renderer); texture->SetInterpolate(textureInterpolation); // Store this actor to be added to the actor assembly, sort // by layer int layer = 1; node->GetIntProperty("layer", layer); layerSortedActors.insert(std::pair(layer, imageActor)); } } } } } } void PlaneGeometryDataVtkMapper3D::ActorInfo::Initialize(vtkActor *actor, itk::Object *sender, itk::Command *command) { m_Actor = actor; m_Sender = sender; // Get informed when ImageMapper object is deleted, so that // the data structures built here can be deleted as well m_ObserverID = sender->AddObserver(itk::DeleteEvent(), command); } PlaneGeometryDataVtkMapper3D::ActorInfo::ActorInfo() : m_Actor(nullptr), m_Sender(nullptr), m_ObserverID(0) {} PlaneGeometryDataVtkMapper3D::ActorInfo::~ActorInfo() { if (m_Sender != nullptr) { m_Sender->RemoveObserver(m_ObserverID); } if (m_Actor != nullptr) { m_Actor->ReleaseGraphicsResources(nullptr); m_Actor->Delete(); } } } // namespace mitk diff --git a/Modules/Core/test/mitkWeakPointerTest.cpp b/Modules/Core/test/mitkWeakPointerTest.cpp index ee3e535ecb..19696a8ab5 100644 --- a/Modules/Core/test/mitkWeakPointerTest.cpp +++ b/Modules/Core/test/mitkWeakPointerTest.cpp @@ -1,51 +1,51 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkTestingMacros.h" #include #include int mitkWeakPointerTest(int /*argc*/, char * /*argv*/ []) { MITK_TEST_BEGIN("WeakPointer") mitk::WeakPointer weakPointer; mitk::WeakPointer weakPointer2; // Testing constructors and reference counting itk::Object::Pointer smartPointer = itk::Object::New(); mitk::WeakPointer weakPointer3(smartPointer); mitk::WeakPointer weakPointer4(weakPointer); { - itk::Object::Pointer tmpSmartPointer(weakPointer); - itk::Object::Pointer tmpSmartPointer2(weakPointer2); + itk::Object::Pointer tmpSmartPointer(weakPointer.Lock()); + itk::Object::Pointer tmpSmartPointer2(weakPointer2.Lock()); MITK_TEST_CONDITION_REQUIRED(tmpSmartPointer.GetPointer() == tmpSmartPointer2.GetPointer(), "Testing equal pointers"); } weakPointer = smartPointer; weakPointer2 = weakPointer; MITK_TEST_CONDITION_REQUIRED(1 == smartPointer->GetReferenceCount(), "Testing reference count"); smartPointer = nullptr; - MITK_TEST_CONDITION_REQUIRED(weakPointer.IsNull(), "Testing expired weak pointer (smart pointer assignment)"); - MITK_TEST_CONDITION_REQUIRED(weakPointer2.IsNull(), "Testing expired weak pointer (weak pointer assignment)"); - MITK_TEST_CONDITION_REQUIRED(weakPointer3.IsNull(), "Testing expired weak pointer (smart pointer constructor)"); - MITK_TEST_CONDITION_REQUIRED(weakPointer4.IsNull(), "Testing expired weak pointer (copy constructor)") + MITK_TEST_CONDITION_REQUIRED(weakPointer.IsExpired(), "Testing expired weak pointer (smart pointer assignment)"); + MITK_TEST_CONDITION_REQUIRED(weakPointer2.IsExpired(), "Testing expired weak pointer (weak pointer assignment)"); + MITK_TEST_CONDITION_REQUIRED(weakPointer3.IsExpired(), "Testing expired weak pointer (smart pointer constructor)"); + MITK_TEST_CONDITION_REQUIRED(weakPointer4.IsExpired(), "Testing expired weak pointer (copy constructor)") MITK_TEST_END() } diff --git a/Modules/DICOMReader/src/mitkDICOMImageBlockDescriptor.cpp b/Modules/DICOMReader/src/mitkDICOMImageBlockDescriptor.cpp index a777d79ce9..4ddcf30c79 100644 --- a/Modules/DICOMReader/src/mitkDICOMImageBlockDescriptor.cpp +++ b/Modules/DICOMReader/src/mitkDICOMImageBlockDescriptor.cpp @@ -1,863 +1,865 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMImageBlockDescriptor.h" #include "mitkStringProperty.h" #include "mitkLevelWindowProperty.h" #include mitk::DICOMImageBlockDescriptor::DICOMImageBlockDescriptor() : m_ReaderImplementationLevel( SOPClassUnknown ) , m_PropertyList( PropertyList::New() ) , m_TagCache( nullptr ) , m_PropertiesOutOfDate( true ) { m_PropertyFunctor = &mitk::DICOMImageBlockDescriptor::GetPropertyForDICOMValues; } mitk::DICOMImageBlockDescriptor::~DICOMImageBlockDescriptor() { } mitk::DICOMImageBlockDescriptor::DICOMImageBlockDescriptor( const DICOMImageBlockDescriptor& other ) : m_ImageFrameList( other.m_ImageFrameList ) , m_MitkImage( other.m_MitkImage ) , m_SliceIsLoaded( other.m_SliceIsLoaded ) , m_ReaderImplementationLevel( other.m_ReaderImplementationLevel ) , m_TiltInformation( other.m_TiltInformation ) , m_PropertyList( other.m_PropertyList->Clone() ) , m_TagCache( other.m_TagCache ) , m_PropertiesOutOfDate( other.m_PropertiesOutOfDate ) , m_AdditionalTagMap(other.m_AdditionalTagMap) , m_FoundAdditionalTags(other.m_FoundAdditionalTags) , m_PropertyFunctor(other.m_PropertyFunctor) { if ( m_MitkImage ) { m_MitkImage = m_MitkImage->Clone(); } m_PropertyFunctor = &mitk::DICOMImageBlockDescriptor::GetPropertyForDICOMValues; } mitk::DICOMImageBlockDescriptor& mitk::DICOMImageBlockDescriptor:: operator=( const DICOMImageBlockDescriptor& other ) { if ( this != &other ) { m_ImageFrameList = other.m_ImageFrameList; m_MitkImage = other.m_MitkImage; m_SliceIsLoaded = other.m_SliceIsLoaded; m_ReaderImplementationLevel = other.m_ReaderImplementationLevel; m_TiltInformation = other.m_TiltInformation; m_AdditionalTagMap = other.m_AdditionalTagMap; m_FoundAdditionalTags = other.m_FoundAdditionalTags; m_PropertyFunctor = other.m_PropertyFunctor; if ( other.m_PropertyList ) { m_PropertyList = other.m_PropertyList->Clone(); } if ( other.m_MitkImage ) { m_MitkImage = other.m_MitkImage->Clone(); } m_TagCache = other.m_TagCache; m_PropertiesOutOfDate = other.m_PropertiesOutOfDate; } return *this; } mitk::DICOMTagList mitk::DICOMImageBlockDescriptor::GetTagsOfInterest() { DICOMTagList completeList; completeList.push_back( DICOMTag( 0x0018, 0x1164 ) ); // pixel spacing completeList.push_back( DICOMTag( 0x0028, 0x0030 ) ); // imager pixel spacing completeList.push_back( DICOMTag( 0x0008, 0x0018 ) ); // sop instance UID completeList.push_back( DICOMTag( 0x0008, 0x0016 ) ); // sop class UID completeList.push_back( DICOMTag( 0x0020, 0x0011 ) ); // series number completeList.push_back( DICOMTag( 0x0008, 0x1030 ) ); // study description completeList.push_back( DICOMTag( 0x0008, 0x103e ) ); // series description completeList.push_back( DICOMTag( 0x0008, 0x0060 ) ); // modality completeList.push_back( DICOMTag( 0x0018, 0x0024 ) ); // sequence name completeList.push_back( DICOMTag( 0x0020, 0x0037 ) ); // image orientation completeList.push_back( DICOMTag( 0x0020, 0x1041 ) ); // slice location completeList.push_back( DICOMTag( 0x0020, 0x0012 ) ); // acquisition number completeList.push_back( DICOMTag( 0x0020, 0x0013 ) ); // instance number completeList.push_back( DICOMTag( 0x0020, 0x0032 ) ); // image position patient completeList.push_back( DICOMTag( 0x0028, 0x1050 ) ); // window center completeList.push_back( DICOMTag( 0x0028, 0x1051 ) ); // window width completeList.push_back( DICOMTag( 0x0008, 0x0008 ) ); // image type completeList.push_back( DICOMTag( 0x0028, 0x0004 ) ); // photometric interpretation return completeList; } void mitk::DICOMImageBlockDescriptor::SetAdditionalTagsOfInterest( const AdditionalTagsMapType& tagMap) { m_AdditionalTagMap = tagMap; } void mitk::DICOMImageBlockDescriptor::SetTiltInformation( const GantryTiltInformation& info ) { m_TiltInformation = info; } const mitk::GantryTiltInformation mitk::DICOMImageBlockDescriptor::GetTiltInformation() const { return m_TiltInformation; } void mitk::DICOMImageBlockDescriptor::SetImageFrameList( const DICOMImageFrameList& framelist ) { m_ImageFrameList = framelist; m_SliceIsLoaded.resize( framelist.size() ); m_SliceIsLoaded.assign( framelist.size(), false ); m_PropertiesOutOfDate = true; } const mitk::DICOMImageFrameList& mitk::DICOMImageBlockDescriptor::GetImageFrameList() const { return m_ImageFrameList; } void mitk::DICOMImageBlockDescriptor::SetMitkImage( Image::Pointer image ) { if ( m_MitkImage != image ) { - if ( m_TagCache.IsNull() ) + if ( m_TagCache.IsExpired() ) { MITK_ERROR << "Unable to describe MITK image with properties without a tag-cache object!"; m_MitkImage = nullptr; return; } if ( m_ImageFrameList.empty() ) { MITK_ERROR << "Unable to describe MITK image with properties without a frame list!"; m_MitkImage = nullptr; return; } // Should verify that the image matches m_ImageFrameList and m_TagCache // however, this is hard to do without re-analyzing all // TODO we should at least make sure that the number of frames is identical (plus rows/columns, // orientation) // without gantry tilt correction, we can also check image origin m_MitkImage = this->DescribeImageWithProperties( this->FixupSpacing( image ) ); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::GetMitkImage() const { return m_MitkImage; } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::FixupSpacing( Image* mitkImage ) { if ( mitkImage ) { Vector3D imageSpacing = mitkImage->GetGeometry()->GetSpacing(); ScalarType desiredSpacingX = imageSpacing[0]; ScalarType desiredSpacingY = imageSpacing[1]; this->GetDesiredMITKImagePixelSpacing( desiredSpacingX, desiredSpacingY ); // prefer pixel spacing over imager pixel spacing if ( desiredSpacingX <= 0 || desiredSpacingY <= 0 ) { return mitkImage; } MITK_DEBUG << "Loaded image with spacing " << imageSpacing[0] << ", " << imageSpacing[1]; MITK_DEBUG << "Found correct spacing info " << desiredSpacingX << ", " << desiredSpacingY; imageSpacing[0] = desiredSpacingX; imageSpacing[1] = desiredSpacingY; mitkImage->GetGeometry()->SetSpacing( imageSpacing ); } return mitkImage; } void mitk::DICOMImageBlockDescriptor::SetSliceIsLoaded( unsigned int index, bool isLoaded ) { if ( index < m_SliceIsLoaded.size() ) { m_SliceIsLoaded[index] = isLoaded; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor::IsSliceLoaded( unsigned int index ) const { if ( index < m_SliceIsLoaded.size() ) { return m_SliceIsLoaded[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor::AllSlicesAreLoaded() const { bool allLoaded = true; for ( auto iter = m_SliceIsLoaded.cbegin(); iter != m_SliceIsLoaded.cend(); ++iter ) { allLoaded &= *iter; } return allLoaded; } /* PS defined IPS defined PS==IPS 0 0 --> UNKNOWN spacing, loader will invent 0 1 --> spacing as at detector surface 1 0 --> spacing as in patient 1 1 0 --> detector surface spacing CORRECTED for geometrical magnifications: spacing as in patient 1 1 1 --> detector surface spacing NOT corrected for geometrical magnifications: spacing as at detector */ mitk::PixelSpacingInterpretation mitk::DICOMImageBlockDescriptor::GetPixelSpacingInterpretation() const { - if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) + if ( m_ImageFrameList.empty() || m_TagCache.IsExpired() ) { MITK_ERROR << "Invalid call to GetPixelSpacingInterpretation. Need to have initialized tag-cache!"; return SpacingUnknown; } const std::string pixelSpacing = this->GetPixelSpacing(); const std::string imagerPixelSpacing = this->GetImagerPixelSpacing(); if ( pixelSpacing.empty() ) { if ( imagerPixelSpacing.empty() ) { return SpacingUnknown; } else { return SpacingAtDetector; } } else // Pixel Spacing defined { if ( imagerPixelSpacing.empty() ) { return SpacingInPatient; } else if ( pixelSpacing != imagerPixelSpacing ) { return SpacingInPatient; } else { return SpacingAtDetector; } } } std::string mitk::DICOMImageBlockDescriptor::GetPixelSpacing() const { - if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) + if ( m_ImageFrameList.empty() || m_TagCache.IsExpired() ) { MITK_ERROR << "Invalid call to GetPixelSpacing. Need to have initialized tag-cache!"; return std::string( "" ); } static const DICOMTag tagPixelSpacing( 0x0028, 0x0030 ); - return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagPixelSpacing ).value; + return m_TagCache.Lock()->GetTagValue( m_ImageFrameList.front(), tagPixelSpacing ).value; } std::string mitk::DICOMImageBlockDescriptor::GetImagerPixelSpacing() const { - if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) + if ( m_ImageFrameList.empty() || m_TagCache.IsExpired() ) { MITK_ERROR << "Invalid call to GetImagerPixelSpacing. Need to have initialized tag-cache!"; return std::string( "" ); } static const DICOMTag tagImagerPixelSpacing( 0x0018, 0x1164 ); - return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagImagerPixelSpacing ).value; + return m_TagCache.Lock()->GetTagValue( m_ImageFrameList.front(), tagImagerPixelSpacing ).value; } void mitk::DICOMImageBlockDescriptor::GetDesiredMITKImagePixelSpacing( ScalarType& spacingX, ScalarType& spacingY ) const { const std::string pixelSpacing = this->GetPixelSpacing(); // preference for "in patient" pixel spacing if ( !DICOMStringToSpacing( pixelSpacing, spacingX, spacingY ) ) { const std::string imagerPixelSpacing = this->GetImagerPixelSpacing(); // fallback to "on detector" spacing if ( !DICOMStringToSpacing( imagerPixelSpacing, spacingX, spacingY ) ) { // at this point we have no hints whether the spacing is correct // do a quick sanity check and either trust in the input or set both to 1 // We assume neither spacing to be negative, zero or unexpectedly large for // medical images if (spacingX < mitk::eps || spacingX > 1000 || spacingY < mitk::eps || spacingY > 1000) { spacingX = spacingY = 1.0; } } } } void mitk::DICOMImageBlockDescriptor::SetProperty( const std::string& key, BaseProperty* value ) { m_PropertyList->SetProperty( key, value ); } mitk::BaseProperty* mitk::DICOMImageBlockDescriptor::GetProperty( const std::string& key ) const { this->UpdateImageDescribingProperties(); return m_PropertyList->GetProperty( key ); } std::string mitk::DICOMImageBlockDescriptor::GetPropertyAsString( const std::string& key ) const { this->UpdateImageDescribingProperties(); const mitk::BaseProperty::Pointer property = m_PropertyList->GetProperty( key ); if ( property.IsNotNull() ) { return property->GetValueAsString(); } else { return std::string( "" ); } } void mitk::DICOMImageBlockDescriptor::SetFlag( const std::string& key, bool value ) { m_PropertyList->ReplaceProperty( key, BoolProperty::New( value ) ); } bool mitk::DICOMImageBlockDescriptor::GetFlag( const std::string& key, bool defaultValue ) const { this->UpdateImageDescribingProperties(); BoolProperty::ConstPointer boolProp = dynamic_cast( this->GetProperty( key ) ); if ( boolProp.IsNotNull() ) { return boolProp->GetValue(); } else { return defaultValue; } } void mitk::DICOMImageBlockDescriptor::SetIntProperty( const std::string& key, int value ) { m_PropertyList->ReplaceProperty( key, IntProperty::New( value ) ); } int mitk::DICOMImageBlockDescriptor::GetIntProperty( const std::string& key, int defaultValue ) const { this->UpdateImageDescribingProperties(); IntProperty::ConstPointer intProp = dynamic_cast( this->GetProperty( key ) ); if ( intProp.IsNotNull() ) { return intProp->GetValue(); } else { return defaultValue; } } double mitk::DICOMImageBlockDescriptor::stringtodouble( const std::string& str ) const { double d; std::string trimmedstring( str ); try { trimmedstring = trimmedstring.erase( trimmedstring.find_last_not_of( " \n\r\t" ) + 1 ); } catch ( ... ) { // no last not of } std::string firstcomponent( trimmedstring ); try { firstcomponent = trimmedstring.erase( trimmedstring.find_first_of( "\\" ) ); } catch ( ... ) { // no last not of } std::istringstream converter( firstcomponent ); if ( !firstcomponent.empty() && ( converter >> d ) && converter.eof() ) { return d; } else { throw std::invalid_argument( "Argument is not a convertable number" ); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::DescribeImageWithProperties( Image* mitkImage ) { // TODO: this is a collection of properties that have been provided by the // legacy DicomSeriesReader. // We should at some point clean up this collection and name them in a more // consistent way! if ( !mitkImage ) return mitkImage; // first part: add some tags that describe individual slices // these propeties are defined at analysis time (see UpdateImageDescribingProperties()) const char* propertyKeySliceLocation = "dicom.image.0020.1041"; const char* propertyKeyInstanceNumber = "dicom.image.0020.0013"; const char* propertyKeySOPInstanceUID = "dicom.image.0008.0018"; mitkImage->SetProperty( propertyKeySliceLocation, this->GetProperty( "sliceLocationForSlices" ) ); mitkImage->SetProperty( propertyKeyInstanceNumber, this->GetProperty( "instanceNumberForSlices" ) ); mitkImage->SetProperty( propertyKeySOPInstanceUID, this->GetProperty( "SOPInstanceUIDForSlices" ) ); mitkImage->SetProperty( "files", this->GetProperty( "filenamesForSlices" ) ); // second part: add properties that describe the whole image block mitkImage->SetProperty( "dicomseriesreader.SOPClassUID", StringProperty::New( this->GetSOPClassUID() ) ); mitkImage->SetProperty( "dicomseriesreader.SOPClass", StringProperty::New( this->GetSOPClassUIDAsName() ) ); mitkImage->SetProperty( "dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New( PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() ) ) ); mitkImage->SetProperty( "dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New( this->GetPixelSpacingInterpretation() ) ); mitkImage->SetProperty( "dicomseriesreader.ReaderImplementationLevelString", StringProperty::New( ReaderImplementationLevelToString( m_ReaderImplementationLevel ) ) ); mitkImage->SetProperty( "dicomseriesreader.ReaderImplementationLevel", GenericProperty::New( m_ReaderImplementationLevel ) ); mitkImage->SetProperty( "dicomseriesreader.GantyTiltCorrected", BoolProperty::New( this->GetTiltInformation().IsRegularGantryTilt() ) ); mitkImage->SetProperty( "dicomseriesreader.3D+t", BoolProperty::New( this->GetFlag( "3D+t", false ) ) ); // level window const std::string windowCenter = this->GetPropertyAsString( "windowCenter" ); const std::string windowWidth = this->GetPropertyAsString( "windowWidth" ); try { const double level = stringtodouble( windowCenter ); const double window = stringtodouble( windowWidth ); mitkImage->SetProperty( "levelwindow", LevelWindowProperty::New( LevelWindow( level, window ) ) ); } catch ( ... ) { // nothing, no levelwindow to be predicted... } const std::string modality = this->GetPropertyAsString( "modality" ); mitkImage->SetProperty( "modality", StringProperty::New( modality ) ); mitkImage->SetProperty( "dicom.pixel.PhotometricInterpretation", this->GetProperty( "photometricInterpretation" ) ); mitkImage->SetProperty( "dicom.image.imagetype", this->GetProperty( "imagetype" ) ); mitkImage->SetProperty( "dicom.study.StudyDescription", this->GetProperty( "studyDescription" ) ); mitkImage->SetProperty( "dicom.series.SeriesDescription", this->GetProperty( "seriesDescription" ) ); mitkImage->SetProperty( "dicom.pixel.Rows", this->GetProperty( "rows" ) ); mitkImage->SetProperty( "dicom.pixel.Columns", this->GetProperty( "columns" ) ); // third part: get all found additional tags of interest for (auto tag : m_FoundAdditionalTags) { BaseProperty* prop = this->GetProperty(tag); if (prop) { mitkImage->SetProperty(tag.c_str(), prop); } } // fourth part: get something from ImageIO. BUT this needs to be created elsewhere. or not at all! return mitkImage; } void mitk::DICOMImageBlockDescriptor::SetReaderImplementationLevel( const ReaderImplementationLevel& level ) { m_ReaderImplementationLevel = level; } mitk::ReaderImplementationLevel mitk::DICOMImageBlockDescriptor::GetReaderImplementationLevel() const { return m_ReaderImplementationLevel; } std::string mitk::DICOMImageBlockDescriptor::GetSOPClassUID() const { - if ( !m_ImageFrameList.empty() && m_TagCache.IsNotNull() ) + if ( !m_ImageFrameList.empty() && !m_TagCache.IsExpired() ) { static const DICOMTag tagSOPClassUID( 0x0008, 0x0016 ); - return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagSOPClassUID ).value; + return m_TagCache.Lock()->GetTagValue( m_ImageFrameList.front(), tagSOPClassUID ).value; } else { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::GetSOPClassUID(). Need to have initialized tag-cache!"; return std::string( "" ); } } std::string mitk::DICOMImageBlockDescriptor::GetSOPClassUIDAsName() const { - if ( !m_ImageFrameList.empty() && m_TagCache.IsNotNull() ) + if ( !m_ImageFrameList.empty() && !m_TagCache.IsExpired() ) { gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( this->GetSOPClassUID().c_str() ); const char* name = uidKnowledge.GetName(); if ( name ) { return std::string( name ); } else { return std::string( "" ); } } else { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::GetSOPClassUIDAsName(). Need to have " "initialized tag-cache!"; return std::string( "" ); } } int mitk::DICOMImageBlockDescriptor::GetNumberOfTimeSteps() const { int result = 1; this->m_PropertyList->GetIntProperty("timesteps", result); return result; }; int mitk::DICOMImageBlockDescriptor::GetNumberOfFramesPerTimeStep() const { const int numberOfTimesteps = this->GetNumberOfTimeSteps(); int numberOfFramesPerTimestep = this->m_ImageFrameList.size() / numberOfTimesteps; assert(int(double((double)this->m_ImageFrameList.size() / (double)numberOfTimesteps)) == numberOfFramesPerTimestep); // this should hold return numberOfFramesPerTimestep; }; void mitk::DICOMImageBlockDescriptor::SetTagCache( DICOMTagCache* privateCache ) { // this must only be used during loading and never afterwards m_TagCache = privateCache; } #define printPropertyRange( label, property_name ) \ \ { \ const std::string first = this->GetPropertyAsString( #property_name "First" ); \ const std::string last = this->GetPropertyAsString( #property_name "Last" ); \ if ( !first.empty() || !last.empty() ) \ { \ if ( first == last ) \ { \ os << " " label ": '" << first << "'" << std::endl; \ } \ else \ { \ os << " " label ": '" << first << "' - '" << last << "'" << std::endl; \ } \ } \ \ } #define printProperty( label, property_name ) \ \ { \ const std::string first = this->GetPropertyAsString( #property_name ); \ if ( !first.empty() ) \ { \ os << " " label ": '" << first << "'" << std::endl; \ } \ \ } #define printBool( label, commands ) \ \ { \ os << " " label ": '" << ( commands ? "yes" : "no" ) << "'" << std::endl; \ \ } void mitk::DICOMImageBlockDescriptor::Print(std::ostream& os, bool filenameDetails) const { os << " Number of Frames: '" << m_ImageFrameList.size() << "'" << std::endl; os << " SOP class: '" << this->GetSOPClassUIDAsName() << "'" << std::endl; printProperty( "Series Number", seriesNumber ); printProperty( "Study Description", studyDescription ); printProperty( "Series Description", seriesDescription ); printProperty( "Modality", modality ); printProperty( "Sequence Name", sequenceName ); printPropertyRange( "Slice Location", sliceLocation ); printPropertyRange( "Acquisition Number", acquisitionNumber ); printPropertyRange( "Instance Number", instanceNumber ); printPropertyRange( "Image Position", imagePositionPatient ); printProperty( "Image Orientation", orientation ); os << " Pixel spacing interpretation: '" << PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() ) << "'" << std::endl; printBool( "Gantry Tilt", this->GetTiltInformation().IsRegularGantryTilt() ) // printBool("3D+t", this->GetFlag("3D+t",false)) // os << " MITK image loaded: '" << (this->GetMitkImage().IsNotNull() ? "yes" : "no") << "'" << // std::endl; if ( filenameDetails ) { os << " Files in this image block:" << std::endl; for ( auto frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++frameIter ) { os << " " << ( *frameIter )->Filename; if ( ( *frameIter )->FrameNo > 0 ) { os << ", " << ( *frameIter )->FrameNo; } os << std::endl; } } } #define storeTagValueToProperty( tag_name, tag_g, tag_e ) \ \ { \ const DICOMTag t( tag_g, tag_e ); \ - const std::string tagValue = m_TagCache->GetTagValue( firstFrame, t ).value; \ + const std::string tagValue = tagCache->GetTagValue( firstFrame, t ).value; \ const_cast( this ) \ ->SetProperty( #tag_name, StringProperty::New( tagValue ) ); \ \ } #define storeTagValueRangeToProperty( tag_name, tag_g, tag_e ) \ \ { \ const DICOMTag t( tag_g, tag_e ); \ - const std::string tagValueFirst = m_TagCache->GetTagValue( firstFrame, t ).value; \ - const std::string tagValueLast = m_TagCache->GetTagValue( lastFrame, t ).value; \ + const std::string tagValueFirst = tagCache->GetTagValue( firstFrame, t ).value; \ + const std::string tagValueLast = tagCache->GetTagValue( lastFrame, t ).value; \ const_cast( this ) \ ->SetProperty( #tag_name "First", StringProperty::New( tagValueFirst ) ); \ const_cast( this ) \ ->SetProperty( #tag_name "Last", StringProperty::New( tagValueLast ) ); \ \ } void mitk::DICOMImageBlockDescriptor::UpdateImageDescribingProperties() const { if ( !m_PropertiesOutOfDate ) return; if ( !m_ImageFrameList.empty() ) { - if ( m_TagCache.IsNull() ) + if ( m_TagCache.IsExpired() ) { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::UpdateImageDescribingProperties(). Need to " "have initialized tag-cache!"; return; } + auto tagCache = m_TagCache.Lock(); + const DICOMImageFrameInfo::Pointer firstFrame = m_ImageFrameList.front(); const DICOMImageFrameInfo::Pointer lastFrame = m_ImageFrameList.back(); // see macros above storeTagValueToProperty( seriesNumber, 0x0020, 0x0011 ); storeTagValueToProperty( studyDescription, 0x0008, 0x1030 ); storeTagValueToProperty( seriesDescription, 0x0008, 0x103e ); storeTagValueToProperty( modality, 0x0008, 0x0060 ); storeTagValueToProperty( sequenceName, 0x0018, 0x0024 ); storeTagValueToProperty( orientation, 0x0020, 0x0037 ); storeTagValueToProperty( rows, 0x0028, 0x0010 ); storeTagValueToProperty( columns, 0x0028, 0x0011 ); storeTagValueRangeToProperty( sliceLocation, 0x0020, 0x1041 ); storeTagValueRangeToProperty( acquisitionNumber, 0x0020, 0x0012 ); storeTagValueRangeToProperty( instanceNumber, 0x0020, 0x0013 ); storeTagValueRangeToProperty( imagePositionPatient, 0x0020, 0x0032 ); storeTagValueToProperty( windowCenter, 0x0028, 0x1050 ); storeTagValueToProperty( windowWidth, 0x0028, 0x1051 ); storeTagValueToProperty( imageType, 0x0008, 0x0008 ); storeTagValueToProperty( photometricInterpretation, 0x0028, 0x0004 ); // some per-image attributes // frames are just numbered starting from 0. timestep 1 (the second time-step) has frames starting at // (number-of-frames-per-timestep) // std::string propertyKeySliceLocation = "dicom.image.0020.1041"; // std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; // std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; StringLookupTable sliceLocationForSlices; StringLookupTable instanceNumberForSlices; StringLookupTable SOPInstanceUIDForSlices; StringLookupTable filenamesForSlices; const DICOMTag tagSliceLocation( 0x0020, 0x1041 ); const DICOMTag tagInstanceNumber( 0x0020, 0x0013 ); const DICOMTag tagSOPInstanceNumber( 0x0008, 0x0018 ); std::unordered_map additionalTagResultList; unsigned int slice(0); int timePoint(-1); const int framesPerTimeStep = this->GetNumberOfFramesPerTimeStep(); for ( auto frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++slice, ++frameIter ) { unsigned int zSlice = slice%framesPerTimeStep; if ( zSlice == 0) { timePoint++; } - const std::string sliceLocation = m_TagCache->GetTagValue( *frameIter, tagSliceLocation ).value; + const std::string sliceLocation = tagCache->GetTagValue( *frameIter, tagSliceLocation ).value; sliceLocationForSlices.SetTableValue( slice, sliceLocation ); - const std::string instanceNumber = m_TagCache->GetTagValue( *frameIter, tagInstanceNumber ).value; + const std::string instanceNumber = tagCache->GetTagValue( *frameIter, tagInstanceNumber ).value; instanceNumberForSlices.SetTableValue( slice, instanceNumber ); - const std::string sopInstanceUID = m_TagCache->GetTagValue( *frameIter, tagSOPInstanceNumber ).value; + const std::string sopInstanceUID = tagCache->GetTagValue( *frameIter, tagSOPInstanceNumber ).value; SOPInstanceUIDForSlices.SetTableValue( slice, sopInstanceUID ); const std::string filename = ( *frameIter )->Filename; filenamesForSlices.SetTableValue( slice, filename ); MITK_DEBUG << "Tag info for slice " << slice << ": SL '" << sliceLocation << "' IN '" << instanceNumber << "' SOP instance UID '" << sopInstanceUID << "'"; for (const auto& tag : m_AdditionalTagMap) { - const DICOMTagCache::FindingsListType findings = m_TagCache->GetTagValue( *frameIter, tag.first ); + const DICOMTagCache::FindingsListType findings = tagCache->GetTagValue( *frameIter, tag.first ); for (const auto& finding : findings) { if (finding.isValid) { std::string propKey = (tag.second.empty()) ? DICOMTagPathToPropertyName(finding.path) : tag.second; DICOMCachedValueInfo info{ static_cast(timePoint), zSlice, finding.value }; additionalTagResultList[propKey].SetTableValue(slice, info); } } } } // add property or properties with proper names auto* thisInstance = const_cast( this ); thisInstance->SetProperty( "sliceLocationForSlices", StringLookupTableProperty::New( sliceLocationForSlices ) ); thisInstance->SetProperty( "instanceNumberForSlices", StringLookupTableProperty::New( instanceNumberForSlices ) ); thisInstance->SetProperty( "SOPInstanceUIDForSlices", StringLookupTableProperty::New( SOPInstanceUIDForSlices ) ); thisInstance->SetProperty( "filenamesForSlices", StringLookupTableProperty::New( filenamesForSlices ) ); //add properties for additional tags of interest for ( auto iter = additionalTagResultList.cbegin(); iter != additionalTagResultList.cend(); ++iter ) { thisInstance->SetProperty( iter->first, m_PropertyFunctor( iter->second ) ); thisInstance->m_FoundAdditionalTags.insert(m_FoundAdditionalTags.cend(),iter->first); } m_PropertiesOutOfDate = false; } } mitk::BaseProperty::Pointer mitk::DICOMImageBlockDescriptor::GetPropertyForDICOMValues(const DICOMCachedValueLookupTable& cacheLookupTable) { const auto& lookupTable = cacheLookupTable.GetLookupTable(); typedef std::pair PairType; if ( std::adjacent_find( lookupTable.cbegin(), lookupTable.cend(), []( const PairType& lhs, const PairType& rhs ) { return lhs.second.Value != rhs.second.Value; } ) == lookupTable.cend() ) { return static_cast( mitk::StringProperty::New(cacheLookupTable.GetTableValue(0).Value).GetPointer()); } StringLookupTable stringTable; for (auto element : lookupTable) { stringTable.SetTableValue(element.first, element.second.Value); } return static_cast( mitk::StringLookupTableProperty::New(stringTable).GetPointer()); } void mitk::DICOMImageBlockDescriptor::SetTagLookupTableToPropertyFunctor( TagLookupTableToPropertyFunctor functor ) { if ( functor != nullptr ) { m_PropertyFunctor = functor; } } diff --git a/Modules/OpenCVVideoSupport/mitkImageToOpenCVImageFilter.cpp b/Modules/OpenCVVideoSupport/mitkImageToOpenCVImageFilter.cpp index 72f8166ca1..7204a4fa73 100644 --- a/Modules/OpenCVVideoSupport/mitkImageToOpenCVImageFilter.cpp +++ b/Modules/OpenCVVideoSupport/mitkImageToOpenCVImageFilter.cpp @@ -1,111 +1,112 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkImageToOpenCVImageFilter.h" #include #include #include namespace mitk{ ImageToOpenCVImageFilter::ImageToOpenCVImageFilter() : m_OpenCVImage(nullptr) { m_sliceSelector = ImageSliceSelector::New(); } ImageToOpenCVImageFilter::~ImageToOpenCVImageFilter() { m_OpenCVImage = nullptr; } void ImageToOpenCVImageFilter::SetImage( Image* _Image ) { m_Image = _Image; } bool ImageToOpenCVImageFilter::CheckImage( Image* image ) { if(image == nullptr) { MITK_WARN << "MITK Image is 0"; return false; } if(image->GetDimension() > 2 ) { MITK_WARN << "Only 2D Images allowed"; return false; } return true; } IplImage* ImageToOpenCVImageFilter::GetOpenCVImage() { + auto image = m_Image.Lock(); - if(!this->CheckImage( m_Image )) + if(!this->CheckImage(image)) return nullptr; m_OpenCVImage = (nullptr); try { - AccessFixedTypeByItk(m_Image.GetPointer(), ItkImageProcessing, + AccessFixedTypeByItk(image.GetPointer(), ItkImageProcessing, MITK_ACCESSBYITK_PIXEL_TYPES_SEQ // gray image (UCRGBPixelType)(USRGBPixelType)(FloatRGBPixelType)(DoubleRGBPixelType), // rgb image (2) // dimensions ) } catch (const AccessByItkException& e) { std::cout << "Caught exception [from AccessFixedTypeByItk]: \n" << e.what() << "\n"; return nullptr; } return m_OpenCVImage; } cv::Mat ImageToOpenCVImageFilter::GetOpenCVMat() { IplImage* img = this->GetOpenCVImage(); cv::Mat mat; if( img ) { // do not copy data, then release just the header mat = cv::Mat ( img, false ); cvReleaseImageHeader( &img ); } return mat; } template void ImageToOpenCVImageFilter::ItkImageProcessing( itk::Image* image ) { m_OpenCVImage = itk::OpenCVImageBridge::ITKImageToIplImage(image); } void ImageToOpenCVImageFilter::SetInputFromTimeSlice(Image::Pointer mitkImage, int timeStep, int slice) { m_sliceSelector->SetInput(mitkImage); m_sliceSelector->SetSliceNr(slice); m_sliceSelector->SetTimeNr(timeStep); m_sliceSelector->Update(); this->SetImage(m_sliceSelector->GetOutput()); } } // end namespace mitk \ No newline at end of file diff --git a/Modules/QtWidgets/include/QmitkDataStorageTreeModel.h b/Modules/QtWidgets/include/QmitkDataStorageTreeModel.h index d6c6789868..935c5a37e3 100644 --- a/Modules/QtWidgets/include/QmitkDataStorageTreeModel.h +++ b/Modules/QtWidgets/include/QmitkDataStorageTreeModel.h @@ -1,269 +1,271 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QMITKDATASTORAGETREEMODEL_H_ #define QMITKDATASTORAGETREEMODEL_H_ #include #include #include #include #include #include "QmitkCustomVariants.h" #include "QmitkEnums.h" #include #include #include /// \ingroup QmitkModule class MITKQTWIDGETS_EXPORT QmitkDataStorageTreeModel : public QAbstractItemModel { //# CONSTANTS,TYPEDEFS public: static const std::string COLUMN_NAME; static const std::string COLUMN_TYPE; static const std::string COLUMN_VISIBILITY; //# CTORS,DTOR public: QmitkDataStorageTreeModel(mitk::DataStorage *_DataStorage, bool _PlaceNewNodesOnTop = false, QObject *parent = 0); ~QmitkDataStorageTreeModel(); //# GETTER public: /// /// Get node at a specific model index. /// This function is used to get a node from a QModelIndex /// mitk::DataNode::Pointer GetNode(const QModelIndex &index) const; /// /// Returns a copy of the node-vector that is shown by this model /// virtual QList GetNodeSet() const; /// /// Get the DataStorage. /// const mitk::DataStorage::Pointer GetDataStorage() const; /// /// Get the top placement flag /// bool GetPlaceNewNodesOnTopFlag() { return m_PlaceNewNodesOnTop; } /// /// Set the top placement flag /// void SetPlaceNewNodesOnTop(bool _PlaceNewNodesOnTop); //# (Re-)implemented from QAbstractItemModel //# Read model Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; //# hierarchical model /// /// called whenever the model or the view needs to create a QModelIndex for a particular /// child item (or a top-level item if parent is an invalid QModelIndex) /// QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; //# editable model bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; Qt::DropActions supportedDropActions() const override; Qt::DropActions supportedDragActions() const override; QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; static QMimeData *mimeDataFromModelIndexList(const QModelIndexList &indexes); //# End of QAbstractItemModel //# SETTER public: /// /// Sets the DataStorage. The whole model will be resetted. /// void SetDataStorage(mitk::DataStorage *_DataStorage); /// /// Notify that the DataStorage was deleted. The whole model will be resetted. /// - void SetDataStorageDeleted(const itk::Object *_DataStorage); + void SetDataStorageDeleted(); /// /// Adds a node to this model. /// If a predicate is set (not null) the node will be checked against it.The node has to have a data object (no one /// wants to see empty nodes). /// virtual void AddNode(const mitk::DataNode *node); /// /// Removes a node from this model. Also removes any event listener from the node. /// virtual void RemoveNode(const mitk::DataNode *node); /// /// Sets a node to modfified. Called by the DataStorage /// virtual void SetNodeModified(const mitk::DataNode *node); /// /// \return an index for the given datatreenode in the tree. If the node is not found /// QModelIndex GetIndex(const mitk::DataNode *) const; /// Set whether to allow hierarchy changes by dragging and dropping void SetAllowHierarchyChange(bool allowHierarchyChange); //# MISC protected: /// /// Helper class to represent a tree structure of DataNodes /// class TreeItem { public: /// /// Constructs a new TreeItem with the given DataNode (must not be 0) /// TreeItem(mitk::DataNode *_DataNode, TreeItem *_Parent = 0); /// /// Removes itself as child from its parent-> Does not delete its children /// \sa Delete() /// virtual ~TreeItem(); /// /// Find the index of an item /// int IndexOfChild(const TreeItem *item) const; /// /// \return The child at pos index or 0 if it not exists /// TreeItem *GetChild(int index) const; /// /// Find the TreeItem containing a special tree node (recursive tree function) /// TreeItem *Find(const mitk::DataNode *_DataNode) const; /// /// Get the amount of children /// int GetChildCount() const; /// /// \return the index of this node in its parent list /// int GetIndex() const; /// /// \return the parent of this tree item /// TreeItem *GetParent() const; /// /// Return the DataNode associated with this node /// mitk::DataNode::Pointer GetDataNode() const; /// /// Get all children as vector /// std::vector GetChildren() const; /// /// add another item as a child of this (only if not already in that list) /// void AddChild(TreeItem *item); /// /// remove another item as child from this /// void RemoveChild(TreeItem *item); /// /// inserts a child at the given position. if pos is not in range /// the element is added at the end /// void InsertChild(TreeItem *item, int index = -1); /// Sets the parent on the treeitem void SetParent(TreeItem *_Parent); /// /// Deletes the whole tree branch /// void Delete(); protected: TreeItem *m_Parent; std::vector m_Children; mitk::DataNode::Pointer m_DataNode; }; QList ToTreeItemPtrList(const QMimeData *mimeData); QList ToTreeItemPtrList(const QByteArray &ba); /// /// Adjusts the LayerProperty according to the nodes position /// void AdjustLayerProperty(); /// /// invoked after m_DataStorage or m_Predicate changed /// TreeItem *TreeItemFromIndex(const QModelIndex &index) const; /// /// Gives a ModelIndex for the Tree Item /// QModelIndex IndexFromTreeItem(TreeItem *) const; /// /// Returns the first element in the nodes sources list (if available) or 0 /// mitk::DataNode *GetParentNode(const mitk::DataNode *node) const; /// /// Adds all Childs in parent to vec. Before a child is added the function is called recursively /// void TreeToVector(TreeItem *parent, std::vector &vec) const; /// /// Adds all Childs in parent to vec. Before a child is added the function is called recursively /// void TreeToNodeSet(TreeItem *parent, QList &vec) const; /// /// Update Tree Model /// void Update(); //# ATTRIBUTES protected: mitk::WeakPointer m_DataStorage; mitk::NodePredicateBase::Pointer m_Predicate; bool m_PlaceNewNodesOnTop; TreeItem *m_Root; /// Flag to block the data storage events if nodes are added/removed by this class. bool m_BlockDataStorageEvents; /// This decides whether or not it is allowed to assign a different parent to a node /// If it is false, it is not possible to change the hierarchy of nodes by dragging /// and dropping. /// If it is true, dragging nodes on another node will replace all of their parents /// with that one. bool m_AllowHierarchyChange; private: void AddNodeInternal(const mitk::DataNode *); void RemoveNodeInternal(const mitk::DataNode *); /// /// Checks if dicom properties patient name, study names and series name exists /// bool DicomPropertiesExists(const mitk::DataNode &) const; + + unsigned long m_DataStorageDeletedTag; }; #endif /* QMITKDATASTORAGETREEMODEL_H_ */ diff --git a/Modules/QtWidgets/include/QmitkNodeDescriptor.h b/Modules/QtWidgets/include/QmitkNodeDescriptor.h index 520b4e5298..d05626ad78 100644 --- a/Modules/QtWidgets/include/QmitkNodeDescriptor.h +++ b/Modules/QtWidgets/include/QmitkNodeDescriptor.h @@ -1,106 +1,105 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkNodeDescriptor_h #define QmitkNodeDescriptor_h #include #include "mitkDataNode.h" #include #include #include #include #include #include #include -#include /** * \ingroup QmitkModule * \brief Decorator class for mitk::DataNode. * * \sa QmitkNodeDescriptorManager */ class MITKQTWIDGETS_EXPORT QmitkNodeDescriptor : public QObject { Q_OBJECT public: /// /// Creates a new QmitkNodeDescriptor /// QmitkNodeDescriptor(const QString &_ClassName, const QString &_PathToIcon, mitk::NodePredicateBase *_Predicate, QObject *parent); QmitkNodeDescriptor(const QString &_ClassName, const QIcon &_Icon, mitk::NodePredicateBase *_Predicate, QObject *parent); /// /// Deletes all actions /// virtual ~QmitkNodeDescriptor() override; /// /// Returns a name for this class of DataNodes (e.g. "Image", "Image Mask", etc.) /// virtual QString GetNameOfClass() const; /// /// Returns an Icon for this class of DataNodes /// virtual QIcon GetIcon(const mitk::DataNode *node) const; /// /// Returns an Icon for this class of DataNodes /// virtual QAction *GetSeparator() const; /// /// Check if this class describes the given node /// virtual bool CheckNode(const mitk::DataNode *node) const; /// /// Create and return an action with this descriptor as owner /// virtual void AddAction(QAction *action, bool isBatchAction = true); /// /// Remove and delete (!) an action /// virtual void RemoveAction(QAction *_Action); /// /// Get all actions associated with this class of nodes /// virtual QList GetActions() const; /// /// Get all actions for this descriptor class that can be executed on multiple nodes /// (no priot knowledge abpout the node is required) /// virtual QList GetBatchActions() const; public slots: /// Called when an action was destroyed void ActionDestroyed(QObject *obj = nullptr); protected: QString m_ClassName; QIcon m_Icon; mitk::NodePredicateBase::Pointer m_Predicate; QList m_Actions; QList m_BatchActions; QAction *m_Separator; }; #endif // QmitkNodeDescriptor_h diff --git a/Modules/QtWidgets/include/QmitkPropertiesTableModel.h b/Modules/QtWidgets/include/QmitkPropertiesTableModel.h index 258e3965de..7e4426afa9 100644 --- a/Modules/QtWidgets/include/QmitkPropertiesTableModel.h +++ b/Modules/QtWidgets/include/QmitkPropertiesTableModel.h @@ -1,251 +1,253 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ /// Header guard. #ifndef QmitkPropertiesTableModel_h #define QmitkPropertiesTableModel_h #include //# Own includes #include "mitkDataNode.h" #include "mitkWeakPointer.h" //# Toolkit includes #include #include #include //# Forward declarations /** * \ingroup QmitkModule * \brief A table model for showing and editing mitk::Properties. * * \see QmitkPropertyDelegate */ class MITKQTWIDGETS_EXPORT QmitkPropertiesTableModel : public QAbstractTableModel { //# PUBLIC CTORS,DTOR,TYPEDEFS,CONSTANTS public: static const int PROPERTY_NAME_COLUMN = 0; static const int PROPERTY_VALUE_COLUMN = 1; /// /// Typedef for the complete Property Datastructure, which may be written as follows: /// Name->(mitk::BaseProperty::Pointer) /// typedef std::pair PropertyDataSet; /// /// Constructs a new QmitkDataStorageTableModel /// and sets the DataNode for this TableModel. QmitkPropertiesTableModel(QObject *parent = nullptr, mitk::PropertyList::Pointer _PropertyList = nullptr); /// /// Standard dtor. Nothing to do here. virtual ~QmitkPropertiesTableModel(); //# PUBLIC GETTER public: /// /// Returns the property list of this table model. /// mitk::PropertyList::Pointer GetPropertyList() const; /// /// Overwritten from QAbstractTableModel. Returns the flags what can be done with the items (view, edit, ...) Qt::ItemFlags flags(const QModelIndex &index) const override; /// /// Overwritten from QAbstractTableModel. Returns the flags what can be done with the items (view, edit, ...) QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; /// /// Overwritten from QAbstractTableModel. Returns the flags what can be done with the items (view, edit, ...) QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; /// /// Overwritten from QAbstractTableModel. Returns the flags what can be done with the items (view, edit, ...) int rowCount(const QModelIndex &parent = QModelIndex()) const override; /// /// Overwritten from QAbstractTableModel. Returns the number of columns. That is usually two in this model: /// the properties name and its value. int columnCount(const QModelIndex &parent) const override; //# PUBLIC SETTER public: /// /// Sets the Property List to show. Resets the whole model. If _PropertyList is nullptr the model is empty. /// void SetPropertyList(mitk::PropertyList *_PropertyList); /// /// \brief Gets called when the list is about to be deleted. /// - virtual void PropertyListDelete(const itk::Object *_PropertyList); + virtual void PropertyListDelete(); /// /// \brief Called when a single property was changed. Send a model changed event to the Qt-outer world. /// virtual void PropertyModified(const itk::Object *caller, const itk::EventObject &event); /// /// \brief Called when a single property was changed. Send a model changed event to the Qt-outer world. /// virtual void PropertyDelete(const itk::Object *caller, const itk::EventObject &event); /// /// \brief Set a keyword for filtering of properties. Only properties beginning with this string will be shown /// virtual void SetFilterPropertiesKeyWord(std::string _FilterKeyWord); /// /// Overridden from QAbstractTableModel. Sets data at index for given role. /// bool setData(const QModelIndex &index, const QVariant &value, int role) override; /// /// \brief Reimplemented sort function from QAbstractTableModel to enable sorting on the table. /// void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; //#PROTECTED INNER CLASSES protected: /// /// \struct PropertyDataSetCompareFunction /// \brief A struct that inherits from std::binary_function. You can use it in std::sort algorithm for sorting the /// property list elements. /// struct PropertyDataSetCompareFunction : public std::binary_function { /// /// \brief Specifies field of the property with which it will be sorted. /// enum CompareCriteria { CompareByName = 0, CompareByValue }; /// /// \brief Specifies Ascending/descending ordering. /// enum CompareOperator { Less = 0, Greater }; /// /// \brief Creates a PropertyDataSetCompareFunction. A CompareCriteria and a CompareOperator must be given. /// PropertyDataSetCompareFunction(CompareCriteria _CompareCriteria = CompareByName, CompareOperator _CompareOperator = Less); /// /// \brief The reimplemented compare function. /// bool operator()(const PropertyDataSet &_Left, const PropertyDataSet &_Right) const; protected: CompareCriteria m_CompareCriteria; CompareOperator m_CompareOperator; }; /// /// An unary function for selecting Properties in a vector by a key word. /// struct PropertyListElementFilterFunction : public std::unary_function { PropertyListElementFilterFunction(const std::string &m_FilterKeyWord); /// /// \brief The reimplemented compare function. /// bool operator()(const PropertyDataSet &_Elem) const; protected: std::string m_FilterKeyWord; }; //# PROTECTED GETTER protected: /// /// \brief Searches for the specified property and returns the row of the element in this QTableModel. /// If any errors occur, the function returns -1. /// int FindProperty(const mitk::BaseProperty *_Property) const; //# PROTECTED SETTER protected: /// /// Adds a property dataset to the current selection. /// When a property is added a modified and delete listener /// is appended. /// void AddSelectedProperty(PropertyDataSet &_PropertyDataSet); /// /// Removes a property dataset from the current selection. /// When a property is removed the modified and delete listener /// are also removed. /// void RemoveSelectedProperty(unsigned int _Index); /// /// Reset is called when a new filter keyword is set or a new /// PropertyList is set. First of all, all priorly selected /// properties are removed. Then all properties to be /// selected (specified by the keyword) are added to the selection. /// void Reset(); //# PROTECTED MEMBERS protected: /// /// Holds the pointer to the properties list. Dont use smart pointers here. Instead: Listen /// to the delete event. mitk::WeakPointer m_PropertyList; /// /// Store the properties in a vector so that they may be sorted std::vector m_SelectedProperties; /// /// \brief Holds all tags of Modified Event Listeners. We need it to remove them again. /// std::vector m_PropertyModifiedObserverTags; /// /// \brief Holds all tags of Modified Event Listeners. We need it to remove them again. /// std::vector m_PropertyDeleteObserverTags; + unsigned long m_PropertyListDeleteObserverTag; + /// /// \brief Indicates if this class should neglect all incoming events because /// the class itself triggered the event (e.g. when a property was edited). /// bool m_BlockEvents; /// /// \brief The property is true when the property list is sorted in descending order. /// bool m_SortDescending; /// /// \brief If set to any value, only properties containing the specified keyword in their name will be shown. /// std::string m_FilterKeyWord; }; #endif /* QMITKPROPERTIESTABLEMODEL_H_ */ diff --git a/Modules/QtWidgets/include/QmitkPropertyItemModel.h b/Modules/QtWidgets/include/QmitkPropertyItemModel.h index 803048e442..333e7f691f 100644 --- a/Modules/QtWidgets/include/QmitkPropertyItemModel.h +++ b/Modules/QtWidgets/include/QmitkPropertyItemModel.h @@ -1,88 +1,89 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkPropertyItemModel_h #define QmitkPropertyItemModel_h #include #include #include #include class QmitkPropertyItem; namespace berry { struct IBerryPreferences; } namespace mitk { class IPropertyAliases; class IPropertyFilters; enum { PropertyRole = Qt::UserRole + 1 }; } class MITKQTWIDGETS_EXPORT QmitkPropertyItemModel : public QAbstractItemModel { Q_OBJECT public: explicit QmitkPropertyItemModel(QObject *parent = nullptr); ~QmitkPropertyItemModel() override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; mitk::PropertyList *GetPropertyList() const; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; void OnPreferencesChanged(); QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; void SetPropertyList(mitk::PropertyList *propertyList, const QString &className = ""); void Update(); void SetShowAliases(const bool showAliases) { this->m_ShowAliases = showAliases; } bool GetShowAliases() const { return this->m_ShowAliases; } void SetFilterProperties(const bool filterProperties) { this->m_FilterProperties = filterProperties; } bool GetFilterProperties() const { return this->m_FilterProperties; } private: void CreateRootItem(); QModelIndex FindProperty(const mitk::BaseProperty *property); - void OnPropertyListModified(const itk::Object *propertyList); - void OnPropertyListDeleted(const itk::Object *propertyList); - void OnPropertyDeleted(const itk::Object *property, const itk::EventObject &event); + void OnPropertyListModified(); + void OnPropertyListDeleted(); void OnPropertyModified(const itk::Object *property, const itk::EventObject &event); - void SetNewPropertyList(mitk::PropertyList *propertyList); + void SetNewPropertyList(mitk::PropertyList *newPropertyList); bool m_ShowAliases; bool m_FilterProperties; mitk::IPropertyAliases *m_PropertyAliases; mitk::IPropertyFilters *m_PropertyFilters; mitk::WeakPointer m_PropertyList; QString m_ClassName; std::unique_ptr m_RootItem; std::map m_PropertyDeletedTags; std::map m_PropertyModifiedTags; + unsigned long m_PropertyListDeletedTag; + unsigned long m_PropertyListModifiedTag; }; #endif diff --git a/Modules/QtWidgets/src/QmitkDataStorageComboBox.cpp b/Modules/QtWidgets/src/QmitkDataStorageComboBox.cpp index 7c3b722013..9a498c94df 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageComboBox.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageComboBox.cpp @@ -1,428 +1,435 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkDataStorageComboBox.h" #include //#CTORS/DTOR QmitkDataStorageComboBox::QmitkDataStorageComboBox(QWidget *parent, bool _AutoSelectNewNodes) : QComboBox(parent), m_DataStorage(nullptr), m_Predicate(nullptr), m_BlockEvents(false), m_AutoSelectNewNodes(_AutoSelectNewNodes) { this->Init(); } QmitkDataStorageComboBox::QmitkDataStorageComboBox(mitk::DataStorage *_DataStorage, const mitk::NodePredicateBase *_Predicate, QWidget *parent, bool _AutoSelectNewNodes) : QComboBox(parent), m_DataStorage(nullptr), m_Predicate(_Predicate), m_BlockEvents(false), m_AutoSelectNewNodes(_AutoSelectNewNodes) { // make connections, fill combobox this->Init(); this->SetDataStorage(_DataStorage); } QmitkDataStorageComboBox::~QmitkDataStorageComboBox() { // if there was an old storage, remove listeners - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { - this->m_DataStorage->AddNodeEvent.RemoveListener( + auto dataStorage = m_DataStorage.Lock(); + + dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageComboBox::AddNode)); - this->m_DataStorage->RemoveNodeEvent.RemoveListener( + dataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageComboBox::RemoveNode)); } // we have lots of observers to nodes and their name properties, this get's ugly if nodes live longer than the box while (m_Nodes.size() > 0) RemoveNode(0); } //#PUBLIC GETTER mitk::DataStorage::Pointer QmitkDataStorageComboBox::GetDataStorage() const { - return m_DataStorage.GetPointer(); + return m_DataStorage.Lock(); } const mitk::NodePredicateBase::ConstPointer QmitkDataStorageComboBox::GetPredicate() const { return m_Predicate.GetPointer(); } mitk::DataNode::Pointer QmitkDataStorageComboBox::GetNode(int index) const { return (this->HasIndex(index)) ? m_Nodes.at(index) : nullptr; } mitk::DataNode::Pointer QmitkDataStorageComboBox::GetSelectedNode() const { if (this->count() == 0) return nullptr; int currentIndex = this->currentIndex(); return currentIndex >= 0 ? this->GetNode(currentIndex) : nullptr; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkDataStorageComboBox::GetNodes() const { mitk::DataStorage::SetOfObjects::Pointer _SetOfObjects = mitk::DataStorage::SetOfObjects::New(); for (auto it = m_Nodes.begin(); it != m_Nodes.end(); ++it) { _SetOfObjects->push_back(*it); } return _SetOfObjects.GetPointer(); } bool QmitkDataStorageComboBox::GetAutoSelectNewItems() { return m_AutoSelectNewNodes; } //#PUBLIC SETTER void QmitkDataStorageComboBox::SetDataStorage(mitk::DataStorage *_DataStorage) { + auto dataStorage = m_DataStorage.Lock(); + // reset only if datastorage really changed - if (m_DataStorage.GetPointer() != _DataStorage) + if (dataStorage.GetPointer() != _DataStorage) { // if there was an old storage, remove listeners - if (m_DataStorage.IsNotNull()) + if (dataStorage.IsNotNull()) { - this->m_DataStorage->AddNodeEvent.RemoveListener( + dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageComboBox::AddNode)); - this->m_DataStorage->RemoveNodeEvent.RemoveListener( + dataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageComboBox::RemoveNode)); } // set new storage m_DataStorage = _DataStorage; // if there is a new storage, add listeners - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { - this->m_DataStorage->AddNodeEvent.AddListener( + dataStorage = m_DataStorage.Lock(); + + dataStorage->AddNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkDataStorageComboBox::AddNode)); - this->m_DataStorage->RemoveNodeEvent.AddListener( + dataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageComboBox::RemoveNode)); } // reset predicate to reset the combobox this->Reset(); } } void QmitkDataStorageComboBox::SetPredicate(const mitk::NodePredicateBase *_Predicate) { if (m_Predicate != _Predicate) { m_Predicate = _Predicate; this->Reset(); } } void QmitkDataStorageComboBox::AddNode(const mitk::DataNode *_DataNode) { // this is an event function, make sure that we didnt call ourself if (!m_BlockEvents) { m_BlockEvents = true; // pass a -1 to the InsertNode function in order to append the datatreenode to the end this->InsertNode(-1, _DataNode); m_BlockEvents = false; } } void QmitkDataStorageComboBox::RemoveNode(int index) { if (this->HasIndex(index)) { //# remove itk::Event observer mitk::DataNode *_DataNode = m_Nodes.at(index); // get name property first mitk::BaseProperty *nameProperty = _DataNode->GetProperty("name"); // if prop exists remove modified listener if (nameProperty) { nameProperty->RemoveObserver(m_NodesModifiedObserverTags[index]); // remove name property map m_PropertyToNode.erase(_DataNode); } // then remove delete listener on the node itself _DataNode->RemoveObserver(m_NodesDeleteObserverTags[index]); // remove observer tags from lists m_NodesModifiedObserverTags.erase(m_NodesModifiedObserverTags.begin() + index); m_NodesDeleteObserverTags.erase(m_NodesDeleteObserverTags.begin() + index); // remove node from node vector m_Nodes.erase(m_Nodes.begin() + index); // remove node name from combobox this->removeItem(index); } } void QmitkDataStorageComboBox::RemoveNode(const mitk::DataNode *_DataNode) { // this is an event function, make sure that we didnt call ourself if (!m_BlockEvents) { m_BlockEvents = true; this->RemoveNode(this->Find(_DataNode)); m_BlockEvents = false; } } void QmitkDataStorageComboBox::SetNode(int index, const mitk::DataNode *_DataNode) { if (this->HasIndex(index)) { this->InsertNode(index, _DataNode); } } void QmitkDataStorageComboBox::SetNode(const mitk::DataNode *_DataNode, const mitk::DataNode *_OtherDataNode) { this->SetNode(this->Find(_DataNode), _OtherDataNode); } void QmitkDataStorageComboBox::SetAutoSelectNewItems(bool _AutoSelectNewItems) { m_AutoSelectNewNodes = _AutoSelectNewItems; } void QmitkDataStorageComboBox::OnDataNodeDeleteOrModified(const itk::Object *caller, const itk::EventObject &event) { if (!m_BlockEvents) { m_BlockEvents = true; // check if we have a modified event (if not it is a delete event) const itk::ModifiedEvent *modifiedEvent = dynamic_cast(&event); // when node was modified reset text if (modifiedEvent) { const mitk::BaseProperty *_NameProperty = dynamic_cast(caller); // node name changed, set it // but first of all find associated node for (auto it = m_PropertyToNode.begin(); it != m_PropertyToNode.end(); ++it) { // property is found take node if (it->second == _NameProperty) { // looks strange but when calling setnode with the same node, that means the node gets updated this->SetNode(it->first, it->first); break; } } } else { const mitk::DataNode *_ConstDataNode = dynamic_cast(caller); if (_ConstDataNode) // node will be deleted, remove it this->RemoveNode(_ConstDataNode); } m_BlockEvents = false; } } void QmitkDataStorageComboBox::SetSelectedNode(mitk::DataNode::Pointer item) { int index = this->Find(item); if (index == -1) { MITK_INFO << "QmitkDataStorageComboBox: item not available"; } else { this->setCurrentIndex(index); } } //#PROTECTED GETTER bool QmitkDataStorageComboBox::HasIndex(unsigned int index) const { return (m_Nodes.size() > 0 && index < m_Nodes.size()); } int QmitkDataStorageComboBox::Find(const mitk::DataNode *_DataNode) const { int index = -1; auto nodeIt = std::find(m_Nodes.begin(), m_Nodes.end(), _DataNode); if (nodeIt != m_Nodes.end()) index = std::distance(m_Nodes.begin(), nodeIt); return index; } //#PROTECTED SETTER void QmitkDataStorageComboBox::OnCurrentIndexChanged(int index) { if (index >= 0 && index < this->count()) emit OnSelectionChanged(this->GetSelectedNode()); if (index == -1) emit OnSelectionChanged(nullptr); } void QmitkDataStorageComboBox::InsertNode(int index, const mitk::DataNode *_DataNode) { // check new or updated node first if (m_Predicate.IsNotNull() && !m_Predicate->CheckNode(_DataNode)) return; bool addNewNode = false; bool insertNewNode = false; bool changedNode = false; // if this->HasIndex(index), then a node shall be updated if (this->HasIndex(index)) { // if we really have another node at this position then ... if (_DataNode != m_Nodes.at(index)) { // ... remove node, then proceed as usual this->RemoveNode(index); insertNewNode = true; } else changedNode = true; } // otherwise a new node shall be added, let index point to the element after the last element else { index = m_Nodes.size(); addNewNode = true; } // const cast because we need non const nodes mitk::DataNode *_NonConstDataNode = const_cast(_DataNode); mitk::BaseProperty *nameProperty = _NonConstDataNode->GetProperty("name"); if (!changedNode) { // break on duplicated nodes (that doesnt make sense to have duplicates in the combobox) if (this->Find(_DataNode) != -1) return; // add modified observer itk::MemberCommand::Pointer modifiedCommand = itk::MemberCommand::New(); modifiedCommand->SetCallbackFunction(this, &QmitkDataStorageComboBox::OnDataNodeDeleteOrModified); // !!!! add modified observer for the name /// property of the node because this is the only thing we are interested in !!!!! if (nameProperty) { m_NodesModifiedObserverTags.push_back(nameProperty->AddObserver(itk::ModifiedEvent(), modifiedCommand)); m_PropertyToNode[_NonConstDataNode] = nameProperty; } // if there is no name node save an invalid value for the observer tag (-1) else m_NodesModifiedObserverTags.push_back(-1); // add delete observer itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkDataStorageComboBox::OnDataNodeDeleteOrModified); m_NodesDeleteObserverTags.push_back(_NonConstDataNode->AddObserver(itk::DeleteEvent(), modifiedCommand)); } // add node to the vector if (addNewNode) m_Nodes.push_back(_NonConstDataNode); else if (insertNewNode) m_Nodes.insert(m_Nodes.begin() + index, _NonConstDataNode); // ... and to the combobox std::string _NonConstDataNodeName = "unnamed node"; // _NonConstDataNodeName is "unnamed node" so far, change it if there is a name property in the node if (nameProperty) _NonConstDataNodeName = nameProperty->GetValueAsString(); if (addNewNode) { this->addItem(QString::fromStdString(_NonConstDataNodeName)); // select new node if m_AutoSelectNewNodes is true or if we have just added the first node if (m_AutoSelectNewNodes || m_Nodes.size() == 1) this->setCurrentIndex(index); } else { // update text in combobox this->setItemText(index, QString::fromStdString(_NonConstDataNodeName)); } } void QmitkDataStorageComboBox::Init() { connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(OnCurrentIndexChanged(int))); } void QmitkDataStorageComboBox::Reset() { // remove all nodes first while (!m_Nodes.empty()) { // remove last node // explicietely calling RemoveNode of QmitkDataStorageComboBox since derived classes may prevent the removal of all // nodes in their respective RemoveNode implementation. This is happening for example in // QmitkDataStorageComboBoxWithSelectNone. QmitkDataStorageComboBox::RemoveNode(m_Nodes.size() - 1); } // clear combobox this->clear(); - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { + auto dataStorage = m_DataStorage.Lock(); mitk::DataStorage::SetOfObjects::ConstPointer setOfObjects; // select all if predicate == nullptr if (m_Predicate.IsNotNull()) - setOfObjects = m_DataStorage->GetSubset(m_Predicate); + setOfObjects = dataStorage->GetSubset(m_Predicate); else - setOfObjects = m_DataStorage->GetAll(); + setOfObjects = dataStorage->GetAll(); // add all found nodes for (mitk::DataStorage::SetOfObjects::ConstIterator nodeIt = setOfObjects->Begin(); nodeIt != setOfObjects->End(); ++nodeIt) // for each _DataNode { // add node to the node vector and to the combobox this->AddNode(nodeIt.Value().GetPointer()); } } } diff --git a/Modules/QtWidgets/src/QmitkDataStorageTableModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageTableModel.cpp index b2ae7fd2b4..f555b5fbee 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageTableModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageTableModel.cpp @@ -1,514 +1,520 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkDataStorageTableModel.h" //# Own includes #include "QmitkCustomVariants.h" #include "QmitkEnums.h" #include "mitkNodePredicateBase.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include //# Toolkit includes #include #include #include //#CTORS/DTOR QmitkDataStorageTableModel::QmitkDataStorageTableModel(mitk::DataStorage::Pointer _DataStorage, mitk::NodePredicateBase *_Predicate, QObject *parent) : QAbstractTableModel(parent), m_DataStorage(nullptr), m_Predicate(nullptr), m_BlockEvents(false), m_SortDescending(false) { this->SetPredicate(_Predicate); this->SetDataStorage(_DataStorage); } QmitkDataStorageTableModel::~QmitkDataStorageTableModel() { // set data storage 0 to remove event listeners this->SetDataStorage(nullptr); } //# Public GETTER const mitk::DataStorage::Pointer QmitkDataStorageTableModel::GetDataStorage() const { - return m_DataStorage.GetPointer(); + return m_DataStorage.Lock(); } mitk::NodePredicateBase::Pointer QmitkDataStorageTableModel::GetPredicate() const { return m_Predicate; } mitk::DataNode::Pointer QmitkDataStorageTableModel::GetNode(const QModelIndex &index) const { mitk::DataNode::Pointer node; if (index.isValid()) { node = m_NodeSet.at(index.row()); } return node; } QVariant QmitkDataStorageTableModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant headerData; // show only horizontal header if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) { // first column: "Name" if (section == 0) headerData = "Name"; else if (section == 1) headerData = "Data Type"; else if (section == 2) headerData = "Visibility"; } else if (orientation == Qt::Vertical) { // show numbers for rows headerData = section + 1; } } return headerData; } Qt::ItemFlags QmitkDataStorageTableModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractItemModel::flags(index); // name & visibility is editable if (index.column() == 0) { flags |= Qt::ItemIsEditable; } else if (index.column() == 2) { flags |= Qt::ItemIsUserCheckable; } return flags; } int QmitkDataStorageTableModel::rowCount(const QModelIndex &) const { return m_NodeSet.size(); } int QmitkDataStorageTableModel::columnCount(const QModelIndex &) const { // show name, type and visible columnn int columns = 3; return columns; } QVariant QmitkDataStorageTableModel::data(const QModelIndex &index, int role) const { QVariant data; if (index.isValid() && !m_NodeSet.empty()) { mitk::DataNode::Pointer node = m_NodeSet.at(index.row()); std::string nodeName = node->GetName(); if (nodeName.empty()) nodeName = "unnamed"; // get name if (index.column() == 0) { // get name of node (may also be edited) if (role == Qt::DisplayRole || role == Qt::EditRole) { data = QString::fromStdString(nodeName); } else if (role == QmitkDataNodeRole) { data = QVariant::fromValue(node); } } else if (index.column() == 1) { QmitkNodeDescriptor *nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(node); // get type property of mitk::BaseData if (role == Qt::DisplayRole) { data = nodeDescriptor->GetNameOfClass(); } // show some nice icons for datatype else if (role == Qt::DecorationRole) { data = nodeDescriptor->GetIcon(node); } } else if (index.column() == 2) { // get visible property of mitk::BaseData bool visibility = false; if (node->GetVisibility(visibility, nullptr) && role == Qt::CheckStateRole) { data = (visibility ? Qt::Checked : Qt::Unchecked); } // node->GetVisibility(visibility, 0) && role == Qt::CheckStateRole } // index.column() == 2 } // index.isValid() && !m_NodeSet.empty() return data; } //# Public SETTERS void QmitkDataStorageTableModel::SetPredicate(mitk::NodePredicateBase *_Predicate) { // ensure that a new predicate is set in order to avoid unnecessary changed events if (m_Predicate != _Predicate) { m_Predicate = _Predicate; this->Reset(); } } void QmitkDataStorageTableModel::SetDataStorage(mitk::DataStorage::Pointer _DataStorage) { // only proceed if we have a new datastorage - if (m_DataStorage.GetPointer() != _DataStorage.GetPointer()) + if (m_DataStorage != _DataStorage) { // if a data storage was set before remove old event listeners - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { - this->m_DataStorage->AddNodeEvent.RemoveListener( + auto dataStorage = m_DataStorage.Lock(); + + dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTableModel::AddNode)); - this->m_DataStorage->RemoveNodeEvent.RemoveListener( + dataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTableModel::RemoveNode)); } // set new data storage - m_DataStorage = _DataStorage.GetPointer(); + m_DataStorage = _DataStorage; // if new storage is not 0 subscribe for events - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { + auto dataStorage = m_DataStorage.Lock(); + // subscribe for node added/removed events - this->m_DataStorage->AddNodeEvent.AddListener( + dataStorage->AddNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTableModel::AddNode)); - this->m_DataStorage->RemoveNodeEvent.AddListener( + dataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTableModel::RemoveNode)); } // Reset model (even if datastorage is 0->will be checked in Reset()) this->Reset(); } } void QmitkDataStorageTableModel::AddNode(const mitk::DataNode *node) { // garantuee no recursions when a new node event is thrown if (!m_BlockEvents) { // if we have a predicate, check node against predicate first if (m_Predicate.IsNotNull() && !m_Predicate->CheckNode(node)) return; // dont add nodes without data (formerly known as helper objects) if (node->GetData() == nullptr) return; // create listener commands to listen to changes in the name or the visibility of the node itk::MemberCommand::Pointer propertyModifiedCommand = itk::MemberCommand::New(); propertyModifiedCommand->SetCallbackFunction(this, &QmitkDataStorageTableModel::PropertyModified); mitk::BaseProperty *tempProperty = nullptr; // add listener for properties tempProperty = node->GetProperty("visible"); if (tempProperty) m_VisiblePropertyModifiedObserverTags[tempProperty] = tempProperty->AddObserver(itk::ModifiedEvent(), propertyModifiedCommand); tempProperty = node->GetProperty("name"); if (tempProperty) m_NamePropertyModifiedObserverTags[tempProperty] = tempProperty->AddObserver(itk::ModifiedEvent(), propertyModifiedCommand); // emit beginInsertRows event beginInsertRows(QModelIndex(), m_NodeSet.size(), m_NodeSet.size()); // add node m_NodeSet.push_back(const_cast(node)); // emit endInsertRows event endInsertRows(); } } void QmitkDataStorageTableModel::RemoveNode(const mitk::DataNode *node) { // garantuee no recursions when a new node event is thrown if (!m_BlockEvents) { // find corresponding node auto nodeIt = std::find(m_NodeSet.begin(), m_NodeSet.end(), node); if (nodeIt != m_NodeSet.end()) { // now: remove listeners for name property ... mitk::BaseProperty *tempProperty = nullptr; tempProperty = (*nodeIt)->GetProperty("visible"); if (tempProperty) tempProperty->RemoveObserver(m_VisiblePropertyModifiedObserverTags[tempProperty]); m_VisiblePropertyModifiedObserverTags.erase(tempProperty); // ... and visibility property tempProperty = (*nodeIt)->GetProperty("name"); if (tempProperty) tempProperty->RemoveObserver(m_NamePropertyModifiedObserverTags[tempProperty]); m_NamePropertyModifiedObserverTags.erase(tempProperty); // get an index from iterator int row = std::distance(m_NodeSet.begin(), nodeIt); // emit beginRemoveRows event (QModelIndex is empty because we dont have a tree model) this->beginRemoveRows(QModelIndex(), row, row); // remove node m_NodeSet.erase(nodeIt); // emit endRemoveRows event endRemoveRows(); } } } void QmitkDataStorageTableModel::PropertyModified(const itk::Object *caller, const itk::EventObject &) { if (!m_BlockEvents) { // get modified property const mitk::BaseProperty *modifiedProperty = dynamic_cast(caller); if (modifiedProperty) { // find node that holds the modified property int row = -1; int column = -1; std::vector::iterator it; mitk::BaseProperty *visibilityProperty = nullptr; mitk::BaseProperty *nameProperty = nullptr; // search for property that changed and emit datachanged on the corresponding ModelIndex for (it = m_NodeSet.begin(); it != m_NodeSet.end(); it++) { // check for the visible property or the name property visibilityProperty = (*it)->GetProperty("visible"); if (modifiedProperty == visibilityProperty) { column = 2; break; } nameProperty = (*it)->GetProperty("name"); if (modifiedProperty == nameProperty) { column = 0; break; } } // if we have the property we have a valid iterator if (it != m_NodeSet.end()) row = std::distance(m_NodeSet.begin(), it); // now emit the dataChanged signal QModelIndex indexOfChangedProperty = index(row, column); emit dataChanged(indexOfChangedProperty, indexOfChangedProperty); } } } bool QmitkDataStorageTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { bool noErr = false; if (index.isValid() && (role == Qt::EditRole || role == Qt::CheckStateRole)) { // any change events produced here should not be caught in this class // --> set m_BlockEvents to true m_BlockEvents = true; mitk::DataNode::Pointer node = m_NodeSet.at(index.row()); if (index.column() == 0) { node->SetStringProperty("name", value.toString().toStdString().c_str()); } else if (index.column() == 2) { node->SetBoolProperty("visible", (value.toInt() == Qt::Checked ? true : false)); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } // inform listeners about changes emit dataChanged(index, index); m_BlockEvents = false; noErr = true; } return noErr; } //#Protected SETTER void QmitkDataStorageTableModel::Reset() { mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet; // remove all nodes now (dont use iterators because removing elements // would invalidate the iterator) // start at the last element: first in, last out unsigned int i = m_NodeSet.size(); while (!m_NodeSet.empty()) { --i; this->RemoveNode(m_NodeSet.at(i)); } // normally now everything should be empty->just to be sure // erase all arrays again m_NamePropertyModifiedObserverTags.clear(); m_VisiblePropertyModifiedObserverTags.clear(); m_NodeSet.clear(); // the whole reset depends on the fact if a data storage is set or not - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { + auto dataStorage = m_DataStorage.Lock(); + if (m_Predicate.IsNotNull()) // get subset - _NodeSet = m_DataStorage->GetSubset(m_Predicate); + _NodeSet = dataStorage->GetSubset(m_Predicate); // if predicate is nullptr, select all nodes else { - _NodeSet = m_DataStorage->GetAll(); + _NodeSet = dataStorage->GetAll(); // remove ghost root node } // finally add all nodes to the model for (auto it = _NodeSet->begin(); it != _NodeSet->end(); it++) { // save node this->AddNode(*it); } } } void QmitkDataStorageTableModel::sort(int column, Qt::SortOrder order /*= Qt::AscendingOrder */) { bool sortDescending = (order == Qt::DescendingOrder) ? true : false; // do not sort twice !!! (dont know why, but qt calls this func twice. STUPID!) /* if(sortDescending != m_SortDescending) {*/ // m_SortDescending = sortDescending; DataNodeCompareFunction::CompareCriteria _CompareCriteria = DataNodeCompareFunction::CompareByName; DataNodeCompareFunction::CompareOperator _CompareOperator = sortDescending ? DataNodeCompareFunction::Greater : DataNodeCompareFunction::Less; if (column == 1) _CompareCriteria = DataNodeCompareFunction::CompareByClassName; else if (column == 2) _CompareCriteria = DataNodeCompareFunction::CompareByVisibility; DataNodeCompareFunction compareFunc(_CompareCriteria, _CompareOperator); std::sort(m_NodeSet.begin(), m_NodeSet.end(), compareFunc); QAbstractTableModel::beginResetModel(); QAbstractTableModel::endResetModel(); //} } std::vector QmitkDataStorageTableModel::GetNodeSet() const { return m_NodeSet; } QmitkDataStorageTableModel::DataNodeCompareFunction::DataNodeCompareFunction(CompareCriteria _CompareCriteria, CompareOperator _CompareOperator) : m_CompareCriteria(_CompareCriteria), m_CompareOperator(_CompareOperator) { } bool QmitkDataStorageTableModel::DataNodeCompareFunction::operator()(const mitk::DataNode::Pointer &_Left, const mitk::DataNode::Pointer &_Right) const { switch (m_CompareCriteria) { case CompareByClassName: if (m_CompareOperator == Less) return (_Left->GetData()->GetNameOfClass() < _Right->GetData()->GetNameOfClass()); else return (_Left->GetData()->GetNameOfClass() > _Right->GetData()->GetNameOfClass()); break; case CompareByVisibility: { bool _LeftVisibility = false; bool _RightVisibility = false; _Left->GetVisibility(_LeftVisibility, nullptr); _Right->GetVisibility(_RightVisibility, nullptr); if (m_CompareOperator == Less) return (_LeftVisibility < _RightVisibility); else return (_LeftVisibility > _RightVisibility); } break; // CompareByName: default: if (m_CompareOperator == Less) return (_Left->GetName() < _Right->GetName()); else return (_Left->GetName() > _Right->GetName()); break; } } diff --git a/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp index 463c57c6bf..a5b633993d 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp @@ -1,1035 +1,1041 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include "QmitkDataStorageTreeModel.h" #include "QmitkNodeDescriptorManager.h" #include #include #include #include #include #include #include #include #include QmitkDataStorageTreeModel::QmitkDataStorageTreeModel(mitk::DataStorage *_DataStorage, bool _PlaceNewNodesOnTop, QObject *parent) : QAbstractItemModel(parent), m_DataStorage(0), m_PlaceNewNodesOnTop(_PlaceNewNodesOnTop), m_Root(0), m_BlockDataStorageEvents(false), m_AllowHierarchyChange(false) { this->SetDataStorage(_DataStorage); } QmitkDataStorageTreeModel::~QmitkDataStorageTreeModel() { // set data storage to 0 = remove all listeners this->SetDataStorage(0); m_Root->Delete(); m_Root = 0; } mitk::DataNode::Pointer QmitkDataStorageTreeModel::GetNode(const QModelIndex &index) const { return this->TreeItemFromIndex(index)->GetDataNode(); } const mitk::DataStorage::Pointer QmitkDataStorageTreeModel::GetDataStorage() const { - return m_DataStorage.GetPointer(); + return m_DataStorage.Lock(); } QModelIndex QmitkDataStorageTreeModel::index(int row, int column, const QModelIndex &parent) const { TreeItem *parentItem; if (!parent.isValid()) parentItem = m_Root; else parentItem = static_cast(parent.internalPointer()); TreeItem *childItem = parentItem->GetChild(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } int QmitkDataStorageTreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentTreeItem = this->TreeItemFromIndex(parent); return parentTreeItem->GetChildCount(); } Qt::ItemFlags QmitkDataStorageTreeModel::flags(const QModelIndex &index) const { mitk::DataNode *dataNode = this->TreeItemFromIndex(index)->GetDataNode(); if (index.isValid()) { if (DicomPropertiesExists(*dataNode)) { return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } else { return Qt::ItemIsDropEnabled; } } int QmitkDataStorageTreeModel::columnCount(const QModelIndex & /* parent = QModelIndex() */) const { return 1; } QModelIndex QmitkDataStorageTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); TreeItem *childItem = this->TreeItemFromIndex(index); TreeItem *parentItem = childItem->GetParent(); if (parentItem == m_Root) return QModelIndex(); return this->createIndex(parentItem->GetIndex(), 0, parentItem); } QmitkDataStorageTreeModel::TreeItem *QmitkDataStorageTreeModel::TreeItemFromIndex(const QModelIndex &index) const { if (index.isValid()) return static_cast(index.internalPointer()); else return m_Root; } Qt::DropActions QmitkDataStorageTreeModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropActions QmitkDataStorageTreeModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction; } bool QmitkDataStorageTreeModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) { // Early exit, returning true, but not actually doing anything (ignoring data). if (action == Qt::IgnoreAction) { return true; } // Note, we are returning true if we handled it, and false otherwise bool returnValue = false; if (data->hasFormat("application/x-qabstractitemmodeldatalist")) { returnValue = true; // First we extract a Qlist of TreeItem* pointers. QList listOfItemsToDrop = ToTreeItemPtrList(data); if (listOfItemsToDrop.empty()) { return false; } // Retrieve the TreeItem* where we are dropping stuff, and its parent. TreeItem *dropItem = this->TreeItemFromIndex(parent); TreeItem *parentItem = dropItem->GetParent(); // If item was dropped onto empty space, we select the root node if (dropItem == m_Root) { parentItem = m_Root; } // Dragging and Dropping is only allowed within the same parent, so use the first item in list to validate. // (otherwise, you could have a derived image such as a segmentation, and assign it to another image). // NOTE: We are assuming the input list is valid... i.e. when it was dragged, all the items had the same parent. // Determine whether or not the drag and drop operation is a valid one. // Examples of invalid operations include: // - dragging nodes with different parents // - dragging nodes from one parent to another parent, if m_AllowHierarchyChange is false // - dragging a node on one of its child nodes (only relevant if m_AllowHierarchyChange is true) bool isValidDragAndDropOperation(true); // different parents { TreeItem *firstParent = listOfItemsToDrop[0]->GetParent(); QList::iterator diIter; for (diIter = listOfItemsToDrop.begin() + 1; diIter != listOfItemsToDrop.end(); diIter++) { if (firstParent != (*diIter)->GetParent()) { isValidDragAndDropOperation = false; break; } } } // dragging from one parent to another if ((!m_AllowHierarchyChange) && isValidDragAndDropOperation) { if (row == -1) // drag onto a node { isValidDragAndDropOperation = listOfItemsToDrop[0]->GetParent() == parentItem; } else // drag between nodes { isValidDragAndDropOperation = listOfItemsToDrop[0]->GetParent() == dropItem; } } // dragging on a child node of one the dragged nodes { QList::iterator diIter; for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { TreeItem *tempItem = dropItem; while (tempItem != m_Root) { tempItem = tempItem->GetParent(); if (tempItem == *diIter) { isValidDragAndDropOperation = false; } } } } if (!isValidDragAndDropOperation) return isValidDragAndDropOperation; if (listOfItemsToDrop[0] != dropItem && isValidDragAndDropOperation) { // Retrieve the index of where we are dropping stuff. QModelIndex parentModelIndex = this->IndexFromTreeItem(parentItem); int dragIndex = 0; // Iterate through the list of TreeItem (which may be at non-consecutive indexes). QList::iterator diIter; for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { TreeItem *itemToDrop = *diIter; // if the item is dragged down we have to compensate its final position for the // fact it is deleted lateron, this only applies if it is dragged within the same level if ((itemToDrop->GetIndex() < row) && (itemToDrop->GetParent() == dropItem)) { dragIndex = 1; } // Here we assume that as you remove items, one at a time, that GetIndex() will be valid. this->beginRemoveRows( this->IndexFromTreeItem(itemToDrop->GetParent()), itemToDrop->GetIndex(), itemToDrop->GetIndex()); itemToDrop->GetParent()->RemoveChild(itemToDrop); this->endRemoveRows(); } // row = -1 dropped on an item, row != -1 dropped in between two items // Select the target index position, or put it at the end of the list. int dropIndex = 0; if (row != -1) { if (dragIndex == 0) dropIndex = std::min(row, parentItem->GetChildCount() - 1); else dropIndex = std::min(row - 1, parentItem->GetChildCount() - 1); } else { dropIndex = dropItem->GetIndex(); } QModelIndex dropItemModelIndex = this->IndexFromTreeItem(dropItem); if ((row == -1 && dropItemModelIndex.row() == -1) || dropItemModelIndex.row() > parentItem->GetChildCount()) dropIndex = parentItem->GetChildCount() - 1; // Now insert items again at the drop item position if (m_AllowHierarchyChange) { this->beginInsertRows(dropItemModelIndex, dropIndex, dropIndex + listOfItemsToDrop.size() - 1); } else { this->beginInsertRows(parentModelIndex, dropIndex, dropIndex + listOfItemsToDrop.size() - 1); } for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { // dropped on node, behaviour depends on preference setting if (m_AllowHierarchyChange) { + auto dataStorage = m_DataStorage.Lock(); + m_BlockDataStorageEvents = true; mitk::DataNode *droppedNode = (*diIter)->GetDataNode(); mitk::DataNode *dropOntoNode = dropItem->GetDataNode(); - m_DataStorage->Remove(droppedNode); - m_DataStorage->Add(droppedNode, dropOntoNode); + dataStorage->Remove(droppedNode); + dataStorage->Add(droppedNode, dropOntoNode); m_BlockDataStorageEvents = false; dropItem->InsertChild((*diIter), dropIndex); } else { if (row == -1) // drag onto a node { parentItem->InsertChild((*diIter), dropIndex); } else // drag between nodes { dropItem->InsertChild((*diIter), dropIndex); } } dropIndex++; } this->endInsertRows(); // Change Layers to match. this->AdjustLayerProperty(); } } else if (data->hasFormat("application/x-mitk-datanodes")) { returnValue = true; int numberOfNodesDropped = 0; QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); mitk::DataNode *node = nullptr; foreach (node, dataNodeList) { - if (node && m_DataStorage.IsNotNull() && !m_DataStorage->Exists(node)) + if (node && !m_DataStorage.IsExpired() && !m_DataStorage.Lock()->Exists(node)) { - m_DataStorage->Add(node); + m_DataStorage.Lock()->Add(node); mitk::BaseData::Pointer basedata = node->GetData(); if (basedata.IsNotNull()) { mitk::RenderingManager::GetInstance()->InitializeViews( basedata->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); numberOfNodesDropped++; } } } // Only do a rendering update, if we actually dropped anything. if (numberOfNodesDropped > 0) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } return returnValue; } QStringList QmitkDataStorageTreeModel::mimeTypes() const { QStringList types = QAbstractItemModel::mimeTypes(); types << "application/x-qabstractitemmodeldatalist"; types << "application/x-mitk-datanodes"; return types; } QMimeData *QmitkDataStorageTreeModel::mimeData(const QModelIndexList &indexes) const { return mimeDataFromModelIndexList(indexes); } QMimeData *QmitkDataStorageTreeModel::mimeDataFromModelIndexList(const QModelIndexList &indexes) { QMimeData *ret = new QMimeData; QString treeItemAddresses(""); QString dataNodeAddresses(""); QByteArray baTreeItemPtrs; QByteArray baDataNodePtrs; QDataStream dsTreeItemPtrs(&baTreeItemPtrs, QIODevice::WriteOnly); QDataStream dsDataNodePtrs(&baDataNodePtrs, QIODevice::WriteOnly); for (int i = 0; i < indexes.size(); i++) { TreeItem *treeItem = static_cast(indexes.at(i).internalPointer()); dsTreeItemPtrs << reinterpret_cast(treeItem); dsDataNodePtrs << reinterpret_cast(treeItem->GetDataNode().GetPointer()); // --------------- deprecated ----------------- unsigned long long treeItemAddress = reinterpret_cast(treeItem); unsigned long long dataNodeAddress = reinterpret_cast(treeItem->GetDataNode().GetPointer()); QTextStream(&treeItemAddresses) << treeItemAddress; QTextStream(&dataNodeAddresses) << dataNodeAddress; if (i != indexes.size() - 1) { QTextStream(&treeItemAddresses) << ","; QTextStream(&dataNodeAddresses) << ","; } // -------------- end deprecated ------------- } // ------------------ deprecated ----------------- ret->setData("application/x-qabstractitemmodeldatalist", QByteArray(treeItemAddresses.toLatin1())); ret->setData("application/x-mitk-datanodes", QByteArray(dataNodeAddresses.toLatin1())); // --------------- end deprecated ----------------- ret->setData(QmitkMimeTypes::DataStorageTreeItemPtrs, baTreeItemPtrs); ret->setData(QmitkMimeTypes::DataNodePtrs, baDataNodePtrs); return ret; } QVariant QmitkDataStorageTreeModel::data(const QModelIndex &index, int role) const { mitk::DataNode *dataNode = this->TreeItemFromIndex(index)->GetDataNode(); // get name of treeItem (may also be edited) QString nodeName; if (DicomPropertiesExists(*dataNode)) { mitk::BaseProperty *seriesDescription = (dataNode->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103e).c_str())); mitk::BaseProperty *studyDescription = (dataNode->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str())); mitk::BaseProperty *patientsName = (dataNode->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str())); mitk::BaseProperty *seriesDescription_deprecated = (dataNode->GetProperty("dicom.series.SeriesDescription")); mitk::BaseProperty *studyDescription_deprecated = (dataNode->GetProperty("dicom.study.StudyDescription")); mitk::BaseProperty *patientsName_deprecated = (dataNode->GetProperty("dicom.patient.PatientsName")); if (patientsName) { nodeName += QString::fromStdString(patientsName->GetValueAsString()) + "\n"; nodeName += QString::fromStdString(studyDescription->GetValueAsString()) + "\n"; nodeName += QString::fromStdString(seriesDescription->GetValueAsString()); } else { /** Code coveres the deprecated property naming for backwards compatibility */ nodeName += QString::fromStdString(patientsName_deprecated->GetValueAsString()) + "\n"; nodeName += QString::fromStdString(studyDescription_deprecated->GetValueAsString()) + "\n"; nodeName += QString::fromStdString(seriesDescription_deprecated->GetValueAsString()); } } else { nodeName = QString::fromStdString(dataNode->GetName()); } if (nodeName.isEmpty()) { nodeName = "unnamed"; } if (role == Qt::DisplayRole) return nodeName; else if (role == Qt::ToolTipRole) return nodeName; else if (role == Qt::DecorationRole) { QmitkNodeDescriptor *nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); return nodeDescriptor->GetIcon(dataNode); } else if (role == Qt::CheckStateRole) { return dataNode->IsVisible(0); } else if (role == QmitkDataNodeRole) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } else if (role == QmitkDataNodeRawPointerRole) { return QVariant::fromValue(dataNode); } return QVariant(); } bool QmitkDataStorageTreeModel::DicomPropertiesExists(const mitk::DataNode &node) const { bool propertiesExists = false; mitk::BaseProperty *seriesDescription_deprecated = (node.GetProperty("dicom.series.SeriesDescription")); mitk::BaseProperty *studyDescription_deprecated = (node.GetProperty("dicom.study.StudyDescription")); mitk::BaseProperty *patientsName_deprecated = (node.GetProperty("dicom.patient.PatientsName")); mitk::BaseProperty *seriesDescription = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103e).c_str())); mitk::BaseProperty *studyDescription = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str())); mitk::BaseProperty *patientsName = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str())); if (patientsName != nullptr && studyDescription != nullptr && seriesDescription != nullptr) { if ((!patientsName->GetValueAsString().empty()) && (!studyDescription->GetValueAsString().empty()) && (!seriesDescription->GetValueAsString().empty())) { propertiesExists = true; } } /** Code coveres the deprecated property naming for backwards compatibility */ if (patientsName_deprecated != nullptr && studyDescription_deprecated != nullptr && seriesDescription_deprecated != nullptr) { if ((!patientsName_deprecated->GetValueAsString().empty()) && (!studyDescription_deprecated->GetValueAsString().empty()) && (!seriesDescription_deprecated->GetValueAsString().empty())) { propertiesExists = true; } } return propertiesExists; } QVariant QmitkDataStorageTreeModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole && m_Root) return QString::fromStdString(m_Root->GetDataNode()->GetName()); return QVariant(); } void QmitkDataStorageTreeModel::SetDataStorage(mitk::DataStorage *_DataStorage) { if (m_DataStorage != _DataStorage) // dont take the same again { - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { + auto dataStorage = m_DataStorage.Lock(); + // remove Listener for the data storage itself - m_DataStorage.ObjectDelete.RemoveListener(mitk::MessageDelegate1( - this, &QmitkDataStorageTreeModel::SetDataStorageDeleted)); + dataStorage->RemoveObserver(m_DataStorageDeletedTag); // remove listeners for the nodes - m_DataStorage->AddNodeEvent.RemoveListener( + dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageTreeModel::AddNode)); - m_DataStorage->ChangedNodeEvent.RemoveListener( + dataStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetNodeModified)); - m_DataStorage->RemoveNodeEvent.RemoveListener( + dataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::RemoveNode)); } // take over the new data storage m_DataStorage = _DataStorage; // delete the old root (if necessary, create new) if (m_Root) m_Root->Delete(); mitk::DataNode::Pointer rootDataNode = mitk::DataNode::New(); rootDataNode->SetName("Data Manager"); m_Root = new TreeItem(rootDataNode, 0); this->beginResetModel(); this->endResetModel(); - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { + auto dataStorage = m_DataStorage.Lock(); + // add Listener for the data storage itself - m_DataStorage.ObjectDelete.AddListener(mitk::MessageDelegate1( - this, &QmitkDataStorageTreeModel::SetDataStorageDeleted)); + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &QmitkDataStorageTreeModel::SetDataStorageDeleted); + m_DataStorageDeletedTag = dataStorage->AddObserver(itk::DeleteEvent(), command); // add listeners for the nodes - m_DataStorage->AddNodeEvent.AddListener(mitk::MessageDelegate1( + dataStorage->AddNodeEvent.AddListener(mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::AddNode)); - m_DataStorage->ChangedNodeEvent.AddListener( + dataStorage->ChangedNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetNodeModified)); - m_DataStorage->RemoveNodeEvent.AddListener( + dataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::RemoveNode)); - mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = m_DataStorage->GetSubset(m_Predicate); + mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = dataStorage->GetSubset(m_Predicate); // finally add all nodes to the model this->Update(); } } } -void QmitkDataStorageTreeModel::SetDataStorageDeleted(const itk::Object * /*_DataStorage*/) +void QmitkDataStorageTreeModel::SetDataStorageDeleted() { this->SetDataStorage(0); } void QmitkDataStorageTreeModel::AddNodeInternal(const mitk::DataNode *node) { - if (node == 0 || m_DataStorage.IsNull() || !m_DataStorage->Exists(node) || m_Root->Find(node) != 0) + if (node == 0 || m_DataStorage.IsExpired() || !m_DataStorage.Lock()->Exists(node) || m_Root->Find(node) != 0) return; // find out if we have a root node TreeItem *parentTreeItem = m_Root; QModelIndex index; mitk::DataNode *parentDataNode = this->GetParentNode(node); if (parentDataNode) // no top level data node { parentTreeItem = m_Root->Find(parentDataNode); // find the corresponding tree item if (!parentTreeItem) { this->AddNode(parentDataNode); parentTreeItem = m_Root->Find(parentDataNode); if (!parentTreeItem) return; } // get the index of this parent with the help of the grand parent index = this->createIndex(parentTreeItem->GetIndex(), 0, parentTreeItem); } // add node if (m_PlaceNewNodesOnTop) { // emit beginInsertRows event beginInsertRows(index, 0, 0); parentTreeItem->InsertChild(new TreeItem(const_cast(node)), 0); } else { int firstRowWithASiblingBelow = 0; int nodeLayer = -1; node->GetIntProperty("layer", nodeLayer); for (TreeItem* siblingTreeItem: parentTreeItem->GetChildren()) { int siblingLayer = -1; if (mitk::DataNode* siblingNode = siblingTreeItem->GetDataNode()) { siblingNode->GetIntProperty("layer", siblingLayer); } if (nodeLayer > siblingLayer) { break; } ++firstRowWithASiblingBelow; } beginInsertRows(index, firstRowWithASiblingBelow, firstRowWithASiblingBelow); parentTreeItem->InsertChild(new TreeItem(const_cast(node)), firstRowWithASiblingBelow); } // emit endInsertRows event endInsertRows(); if(m_PlaceNewNodesOnTop) { this->AdjustLayerProperty(); } } void QmitkDataStorageTreeModel::AddNode(const mitk::DataNode *node) { - if (node == 0 || m_BlockDataStorageEvents || m_DataStorage.IsNull() || !m_DataStorage->Exists(node) || + if (node == 0 || m_BlockDataStorageEvents || m_DataStorage.IsExpired() || !m_DataStorage.Lock()->Exists(node) || m_Root->Find(node) != 0) return; this->AddNodeInternal(node); } void QmitkDataStorageTreeModel::SetPlaceNewNodesOnTop(bool _PlaceNewNodesOnTop) { m_PlaceNewNodesOnTop = _PlaceNewNodesOnTop; } void QmitkDataStorageTreeModel::RemoveNodeInternal(const mitk::DataNode *node) { if (!m_Root) return; TreeItem *treeItem = m_Root->Find(node); if (!treeItem) return; // return because there is no treeitem containing this node TreeItem *parentTreeItem = treeItem->GetParent(); QModelIndex parentIndex = this->IndexFromTreeItem(parentTreeItem); // emit beginRemoveRows event (QModelIndex is empty because we dont have a tree model) this->beginRemoveRows(parentIndex, treeItem->GetIndex(), treeItem->GetIndex()); // remove node std::vector children = treeItem->GetChildren(); delete treeItem; // emit endRemoveRows event endRemoveRows(); // move all children of deleted node into its parent for (std::vector::iterator it = children.begin(); it != children.end(); it++) { // emit beginInsertRows event beginInsertRows(parentIndex, parentTreeItem->GetChildCount(), parentTreeItem->GetChildCount()); // add nodes again parentTreeItem->AddChild(*it); // emit endInsertRows event endInsertRows(); } this->AdjustLayerProperty(); } void QmitkDataStorageTreeModel::RemoveNode(const mitk::DataNode *node) { if (node == 0 || m_BlockDataStorageEvents) return; this->RemoveNodeInternal(node); } void QmitkDataStorageTreeModel::SetNodeModified(const mitk::DataNode *node) { TreeItem *treeItem = m_Root->Find(node); if (treeItem) { TreeItem *parentTreeItem = treeItem->GetParent(); // as the root node should not be removed one should always have a parent item if (!parentTreeItem) return; QModelIndex index = this->createIndex(treeItem->GetIndex(), 0, treeItem); // now emit the dataChanged signal emit dataChanged(index, index); } } mitk::DataNode *QmitkDataStorageTreeModel::GetParentNode(const mitk::DataNode *node) const { mitk::DataNode *dataNode = 0; - mitk::DataStorage::SetOfObjects::ConstPointer _Sources = m_DataStorage->GetSources(node); + mitk::DataStorage::SetOfObjects::ConstPointer _Sources = m_DataStorage.Lock()->GetSources(node); if (_Sources->Size() > 0) dataNode = _Sources->front(); return dataNode; } bool QmitkDataStorageTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { mitk::DataNode *dataNode = this->TreeItemFromIndex(index)->GetDataNode(); if (!dataNode) return false; if (role == Qt::EditRole && !value.toString().isEmpty()) { dataNode->SetStringProperty("name", value.toString().toStdString().c_str()); mitk::PlanarFigure *planarFigure = dynamic_cast(dataNode->GetData()); if (planarFigure != nullptr) mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else if (role == Qt::CheckStateRole) { // Please note: value.toInt() returns 2, independentely from the actual checkstate of the index element. // Therefore the checkstate is being estimated again here. QVariant qcheckstate = index.data(Qt::CheckStateRole); int checkstate = qcheckstate.toInt(); bool isVisible = bool(checkstate); dataNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } // inform listeners about changes emit dataChanged(index, index); return true; } bool QmitkDataStorageTreeModel::setHeaderData(int /*section*/, Qt::Orientation /*orientation*/, const QVariant & /* value */, int /*role = Qt::EditRole*/) { return false; } void QmitkDataStorageTreeModel::AdjustLayerProperty() { /// transform the tree into an array and set the layer property descending std::vector vec; this->TreeToVector(m_Root, vec); int i = vec.size() - 1; for (std::vector::const_iterator it = vec.begin(); it != vec.end(); ++it) { mitk::DataNode::Pointer dataNode = (*it)->GetDataNode(); bool fixedLayer = false; if (!(dataNode->GetBoolProperty("fixedLayer", fixedLayer) && fixedLayer)) dataNode->SetIntProperty("layer", i); --i; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkDataStorageTreeModel::TreeToVector(TreeItem *parent, std::vector &vec) const { TreeItem *current; for (int i = 0; i < parent->GetChildCount(); ++i) { current = parent->GetChild(i); this->TreeToVector(current, vec); vec.push_back(current); } } QModelIndex QmitkDataStorageTreeModel::IndexFromTreeItem(TreeItem *item) const { if (item == m_Root) return QModelIndex(); else return this->createIndex(item->GetIndex(), 0, item); } QList QmitkDataStorageTreeModel::GetNodeSet() const { QList res; if (m_Root) this->TreeToNodeSet(m_Root, res); return res; } void QmitkDataStorageTreeModel::TreeToNodeSet(TreeItem *parent, QList &vec) const { TreeItem *current; for (int i = 0; i < parent->GetChildCount(); ++i) { current = parent->GetChild(i); vec.push_back(current->GetDataNode()); this->TreeToNodeSet(current, vec); } } QModelIndex QmitkDataStorageTreeModel::GetIndex(const mitk::DataNode *node) const { if (m_Root) { TreeItem *item = m_Root->Find(node); if (item) return this->IndexFromTreeItem(item); } return QModelIndex(); } QList QmitkDataStorageTreeModel::ToTreeItemPtrList(const QMimeData *mimeData) { if (mimeData == nullptr || !mimeData->hasFormat(QmitkMimeTypes::DataStorageTreeItemPtrs)) { return QList(); } return ToTreeItemPtrList(mimeData->data(QmitkMimeTypes::DataStorageTreeItemPtrs)); } QList QmitkDataStorageTreeModel::ToTreeItemPtrList(const QByteArray &ba) { QList result; QDataStream ds(ba); while (!ds.atEnd()) { quintptr treeItemPtr; ds >> treeItemPtr; result.push_back(reinterpret_cast(treeItemPtr)); } return result; } QmitkDataStorageTreeModel::TreeItem::TreeItem(mitk::DataNode *_DataNode, TreeItem *_Parent) : m_Parent(_Parent), m_DataNode(_DataNode) { if (m_Parent) m_Parent->AddChild(this); } QmitkDataStorageTreeModel::TreeItem::~TreeItem() { if (m_Parent) m_Parent->RemoveChild(this); } void QmitkDataStorageTreeModel::TreeItem::Delete() { while (m_Children.size() > 0) delete m_Children.back(); delete this; } QmitkDataStorageTreeModel::TreeItem *QmitkDataStorageTreeModel::TreeItem::Find(const mitk::DataNode *_DataNode) const { QmitkDataStorageTreeModel::TreeItem *item = 0; if (_DataNode) { if (m_DataNode == _DataNode) item = const_cast(this); else { for (std::vector::const_iterator it = m_Children.begin(); it != m_Children.end(); ++it) { if (item) break; item = (*it)->Find(_DataNode); } } } return item; } int QmitkDataStorageTreeModel::TreeItem::IndexOfChild(const TreeItem *item) const { std::vector::const_iterator it = std::find(m_Children.begin(), m_Children.end(), item); return it != m_Children.end() ? std::distance(m_Children.begin(), it) : -1; } QmitkDataStorageTreeModel::TreeItem *QmitkDataStorageTreeModel::TreeItem::GetChild(int index) const { return (m_Children.size() > 0 && index >= 0 && index < (int)m_Children.size()) ? m_Children.at(index) : 0; } void QmitkDataStorageTreeModel::TreeItem::AddChild(TreeItem *item) { this->InsertChild(item); } void QmitkDataStorageTreeModel::TreeItem::RemoveChild(TreeItem *item) { std::vector::iterator it = std::find(m_Children.begin(), m_Children.end(), item); if (it != m_Children.end()) { m_Children.erase(it); item->SetParent(0); } } int QmitkDataStorageTreeModel::TreeItem::GetChildCount() const { return m_Children.size(); } int QmitkDataStorageTreeModel::TreeItem::GetIndex() const { if (m_Parent) return m_Parent->IndexOfChild(this); return 0; } QmitkDataStorageTreeModel::TreeItem *QmitkDataStorageTreeModel::TreeItem::GetParent() const { return m_Parent; } mitk::DataNode::Pointer QmitkDataStorageTreeModel::TreeItem::GetDataNode() const { return m_DataNode; } void QmitkDataStorageTreeModel::TreeItem::InsertChild(TreeItem *item, int index) { std::vector::iterator it = std::find(m_Children.begin(), m_Children.end(), item); if (it == m_Children.end()) { if (m_Children.size() > 0 && index >= 0 && index < (int)m_Children.size()) { it = m_Children.begin(); std::advance(it, index); m_Children.insert(it, item); } else m_Children.push_back(item); // add parent if necessary if (item->GetParent() != this) item->SetParent(this); } } std::vector QmitkDataStorageTreeModel::TreeItem::GetChildren() const { return m_Children; } void QmitkDataStorageTreeModel::TreeItem::SetParent(TreeItem *_Parent) { m_Parent = _Parent; if (m_Parent) m_Parent->AddChild(this); } void QmitkDataStorageTreeModel::Update() { - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { - mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = m_DataStorage->GetAll(); + mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = m_DataStorage.Lock()->GetAll(); /// Regardless the value of this preference, the new nodes must not be inserted /// at the top now, but at the position according to their layer. bool newNodesWereToBePlacedOnTop = m_PlaceNewNodesOnTop; m_PlaceNewNodesOnTop = false; for (const auto& node: *_NodeSet) { this->AddNodeInternal(node); } m_PlaceNewNodesOnTop = newNodesWereToBePlacedOnTop; /// Adjust the layers to ensure that derived nodes are above their sources. this->AdjustLayerProperty(); } } void QmitkDataStorageTreeModel::SetAllowHierarchyChange(bool allowHierarchyChange) { m_AllowHierarchyChange = allowHierarchyChange; } diff --git a/Modules/QtWidgets/src/QmitkPropertiesTableModel.cpp b/Modules/QtWidgets/src/QmitkPropertiesTableModel.cpp index c79d66414c..99ac142291 100644 --- a/Modules/QtWidgets/src/QmitkPropertiesTableModel.cpp +++ b/Modules/QtWidgets/src/QmitkPropertiesTableModel.cpp @@ -1,530 +1,532 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkPropertiesTableModel.h" //# Own includes #include "QmitkCustomVariants.h" #include "mitkColorProperty.h" #include "mitkEnumerationProperty.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkStringProperty.h" //# Toolkit includes #include #include #include #include //# PUBLIC CTORS,DTOR QmitkPropertiesTableModel::QmitkPropertiesTableModel(QObject *parent, mitk::PropertyList::Pointer _PropertyList) : QAbstractTableModel(parent), m_PropertyList(nullptr), m_BlockEvents(false), m_SortDescending(false), m_FilterKeyWord("") { this->SetPropertyList(_PropertyList); } QmitkPropertiesTableModel::~QmitkPropertiesTableModel() { // remove all event listeners by setting the property list to 0 this->SetPropertyList(nullptr); } //# PUBLIC GETTER mitk::PropertyList::Pointer QmitkPropertiesTableModel::GetPropertyList() const { - return m_PropertyList.GetPointer(); + return m_PropertyList.Lock(); } Qt::ItemFlags QmitkPropertiesTableModel::flags(const QModelIndex &index) const { // no editing so far, return default (enabled, selectable) Qt::ItemFlags flags = QAbstractItemModel::flags(index); if (index.column() == PROPERTY_VALUE_COLUMN) { // there are also read only property items -> do not allow editing them if (index.data(Qt::EditRole).isValid()) flags |= Qt::ItemIsEditable; if (index.data(Qt::CheckStateRole).isValid()) flags |= Qt::ItemIsUserCheckable; } return flags; } QVariant QmitkPropertiesTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (section) { case PROPERTY_NAME_COLUMN: return tr("Name"); case PROPERTY_VALUE_COLUMN: return tr("Value"); default: return QVariant(); } } return QVariant(); } QVariant QmitkPropertiesTableModel::data(const QModelIndex &index, int role) const { // empty data by default QVariant data; if (!index.isValid() || m_SelectedProperties.empty() || index.row() > (int)(m_SelectedProperties.size() - 1)) return data; // the properties name if (index.column() == PROPERTY_NAME_COLUMN) { if (role == Qt::DisplayRole) data = QString::fromStdString(m_SelectedProperties[index.row()].first); } // the real properties value else if (index.column() == PROPERTY_VALUE_COLUMN) { mitk::BaseProperty *baseProp = m_SelectedProperties[index.row()].second; if (const mitk::ColorProperty *colorProp = dynamic_cast(baseProp)) { mitk::Color col = colorProp->GetColor(); QColor qcol((int)(col.GetRed() * 255), (int)(col.GetGreen() * 255), (int)(col.GetBlue() * 255)); if (role == Qt::DisplayRole) data.setValue(qcol); else if (role == Qt::EditRole) data.setValue(qcol); } else if (mitk::BoolProperty *boolProp = dynamic_cast(baseProp)) { if (role == Qt::CheckStateRole) data = boolProp->GetValue() ? Qt::Checked : Qt::Unchecked; } else if (mitk::StringProperty *stringProp = dynamic_cast(baseProp)) { if (role == Qt::DisplayRole) data.setValue(QString::fromStdString(stringProp->GetValue())); else if (role == Qt::EditRole) data.setValue(QString::fromStdString(stringProp->GetValue())); } else if (mitk::IntProperty *intProp = dynamic_cast(baseProp)) { if (role == Qt::DisplayRole) data.setValue(intProp->GetValue()); else if (role == Qt::EditRole) data.setValue(intProp->GetValue()); } else if (mitk::FloatProperty *floatProp = dynamic_cast(baseProp)) { if (role == Qt::DisplayRole) data.setValue(floatProp->GetValue()); else if (role == Qt::EditRole) data.setValue(floatProp->GetValue()); } else if (mitk::EnumerationProperty *enumerationProp = dynamic_cast(baseProp)) { if (role == Qt::DisplayRole) data.setValue(QString::fromStdString(baseProp->GetValueAsString())); else if (role == Qt::EditRole) { QStringList values; for (auto it = enumerationProp->Begin(); it != enumerationProp->End(); it++) { values << QString::fromStdString(it->second); } data.setValue(values); } } else { if (role == Qt::DisplayRole) data.setValue(QString::fromStdString(m_SelectedProperties[index.row()].second->GetValueAsString())); } } return data; } int QmitkPropertiesTableModel::rowCount(const QModelIndex & /*parent*/) const { // return the number of properties in the properties list. return m_SelectedProperties.size(); } int QmitkPropertiesTableModel::columnCount(const QModelIndex & /*parent*/) const { return 2; } //# PUBLIC SETTER void QmitkPropertiesTableModel::SetPropertyList(mitk::PropertyList *_PropertyList) { // if propertylist really changed - if (m_PropertyList.GetPointer() != _PropertyList) + if (m_PropertyList != _PropertyList) { // Remove delete listener if there was a propertylist before - if (m_PropertyList.IsNotNull()) - { - m_PropertyList.ObjectDelete.RemoveListener(mitk::MessageDelegate1( - this, &QmitkPropertiesTableModel::PropertyListDelete)); - } + if (!m_PropertyList.IsExpired()) + m_PropertyList.Lock()->RemoveObserver(m_PropertyListDeleteObserverTag); // set new list m_PropertyList = _PropertyList; - if (m_PropertyList.IsNotNull()) + if (!m_PropertyList.IsExpired()) { - m_PropertyList.ObjectDelete.AddListener(mitk::MessageDelegate1( - this, &QmitkPropertiesTableModel::PropertyListDelete)); + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &QmitkPropertiesTableModel::PropertyListDelete); + m_PropertyListDeleteObserverTag = m_PropertyList.Lock()->AddObserver(itk::DeleteEvent(), command); } this->Reset(); } } -void QmitkPropertiesTableModel::PropertyListDelete(const itk::Object * /*_PropertyList*/) +void QmitkPropertiesTableModel::PropertyListDelete() { if (!m_BlockEvents) { m_BlockEvents = true; this->Reset(); m_BlockEvents = false; } } void QmitkPropertiesTableModel::PropertyModified(const itk::Object *caller, const itk::EventObject & /*event*/) { if (!m_BlockEvents) { m_BlockEvents = true; int row = this->FindProperty(dynamic_cast(caller)); QModelIndex indexOfChangedProperty = index(row, 1); emit dataChanged(indexOfChangedProperty, indexOfChangedProperty); m_BlockEvents = false; } } void QmitkPropertiesTableModel::PropertyDelete(const itk::Object *caller, const itk::EventObject & /*event*/) { if (!m_BlockEvents) { m_BlockEvents = true; int row = this->FindProperty(dynamic_cast(caller)); if (row >= 0) this->Reset(); m_BlockEvents = false; } } bool QmitkPropertiesTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { // TODO: check 'role' condition if (index.isValid() && !m_SelectedProperties.empty() && index.row() < (int)(m_SelectedProperties.size()) && (role == Qt::EditRole || role == Qt::CheckStateRole)) { // block all events now! m_BlockEvents = true; + auto propertyList = m_PropertyList.Lock(); + // the properties name if (index.column() == PROPERTY_VALUE_COLUMN) { mitk::BaseProperty *baseProp = m_SelectedProperties[index.row()].second; if (mitk::ColorProperty *colorProp = dynamic_cast(baseProp)) { QColor qcolor = value.value(); if (!qcolor.isValid()) return false; mitk::Color col = colorProp->GetColor(); col.SetRed(qcolor.red() / 255.0); col.SetGreen(qcolor.green() / 255.0); col.SetBlue(qcolor.blue() / 255.0); colorProp->SetColor(col); - m_PropertyList->InvokeEvent(itk::ModifiedEvent()); - m_PropertyList->Modified(); + propertyList->InvokeEvent(itk::ModifiedEvent()); + propertyList->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else if (mitk::BoolProperty *boolProp = dynamic_cast(baseProp)) { boolProp->SetValue(value.toInt() == Qt::Checked ? true : false); - m_PropertyList->InvokeEvent(itk::ModifiedEvent()); - m_PropertyList->Modified(); + propertyList->InvokeEvent(itk::ModifiedEvent()); + propertyList->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else if (mitk::StringProperty *stringProp = dynamic_cast(baseProp)) { stringProp->SetValue((value.value()).toStdString()); - m_PropertyList->InvokeEvent(itk::ModifiedEvent()); - m_PropertyList->Modified(); + propertyList->InvokeEvent(itk::ModifiedEvent()); + propertyList->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else if (mitk::IntProperty *intProp = dynamic_cast(baseProp)) { int intValue = value.value(); if (intValue != intProp->GetValue()) { intProp->SetValue(intValue); - m_PropertyList->InvokeEvent(itk::ModifiedEvent()); - m_PropertyList->Modified(); + propertyList->InvokeEvent(itk::ModifiedEvent()); + propertyList->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } else if (mitk::FloatProperty *floatProp = dynamic_cast(baseProp)) { float floatValue = value.value(); if (floatValue != floatProp->GetValue()) { floatProp->SetValue(floatValue); - m_PropertyList->InvokeEvent(itk::ModifiedEvent()); - m_PropertyList->Modified(); + propertyList->InvokeEvent(itk::ModifiedEvent()); + propertyList->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } else if (mitk::EnumerationProperty *enumerationProp = dynamic_cast(baseProp)) { std::string activatedItem = value.value().toStdString(); if (activatedItem != enumerationProp->GetValueAsString()) { if (enumerationProp->IsValidEnumerationValue(activatedItem)) { enumerationProp->SetValue(activatedItem); - m_PropertyList->InvokeEvent(itk::ModifiedEvent()); - m_PropertyList->Modified(); + propertyList->InvokeEvent(itk::ModifiedEvent()); + propertyList->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } } // property was changed by us, now we can accept property changes triggered by someone else m_BlockEvents = false; emit dataChanged(index, index); return true; } return false; } void QmitkPropertiesTableModel::sort(int column, Qt::SortOrder order /*= Qt::AscendingOrder */) { bool sortDescending = (order == Qt::DescendingOrder) ? true : false; // do not sort twice !!! (dont know why, but qt calls this func twice. STUPID!) if (sortDescending != m_SortDescending) { m_SortDescending = sortDescending; PropertyDataSetCompareFunction::CompareCriteria _CompareCriteria = PropertyDataSetCompareFunction::CompareByName; PropertyDataSetCompareFunction::CompareOperator _CompareOperator = m_SortDescending ? PropertyDataSetCompareFunction::Greater : PropertyDataSetCompareFunction::Less; if (column == PROPERTY_VALUE_COLUMN) _CompareCriteria = PropertyDataSetCompareFunction::CompareByValue; PropertyDataSetCompareFunction compareFunc(_CompareCriteria, _CompareOperator); std::sort(m_SelectedProperties.begin(), m_SelectedProperties.end(), compareFunc); QAbstractTableModel::beginResetModel(); QAbstractTableModel::endResetModel(); } } //# PROTECTED GETTER int QmitkPropertiesTableModel::FindProperty(const mitk::BaseProperty *_Property) const { int row = -1; if (_Property) { // search for property that changed and emit datachanged on the corresponding ModelIndex std::vector::const_iterator propertyIterator; for (propertyIterator = m_SelectedProperties.begin(); propertyIterator != m_SelectedProperties.end(); propertyIterator++) { if (propertyIterator->second == _Property) break; } if (propertyIterator != m_SelectedProperties.end()) row = std::distance(m_SelectedProperties.begin(), propertyIterator); } return row; } //# PROTECTED SETTER void QmitkPropertiesTableModel::AddSelectedProperty(PropertyDataSet &_PropertyDataSet) { // subscribe for modified event itk::MemberCommand::Pointer _PropertyDataSetModifiedCommand = itk::MemberCommand::New(); _PropertyDataSetModifiedCommand->SetCallbackFunction(this, &QmitkPropertiesTableModel::PropertyModified); m_PropertyModifiedObserverTags.push_back( _PropertyDataSet.second->AddObserver(itk::ModifiedEvent(), _PropertyDataSetModifiedCommand)); // subscribe for delete event itk::MemberCommand::Pointer _PropertyDataSetDeleteCommand = itk::MemberCommand::New(); _PropertyDataSetDeleteCommand->SetCallbackFunction(this, &QmitkPropertiesTableModel::PropertyDelete); m_PropertyDeleteObserverTags.push_back( _PropertyDataSet.second->AddObserver(itk::DeleteEvent(), _PropertyDataSetDeleteCommand)); // add to the selection m_SelectedProperties.push_back(_PropertyDataSet); } void QmitkPropertiesTableModel::RemoveSelectedProperty(unsigned int _Index) { PropertyDataSet &_PropertyDataSet = m_SelectedProperties.at(_Index); // remove modified event listener _PropertyDataSet.second->RemoveObserver(m_PropertyModifiedObserverTags[_Index]); m_PropertyModifiedObserverTags.erase(m_PropertyModifiedObserverTags.begin() + _Index); // remove delete event listener _PropertyDataSet.second->RemoveObserver(m_PropertyDeleteObserverTags[_Index]); m_PropertyDeleteObserverTags.erase(m_PropertyDeleteObserverTags.begin() + _Index); // remove from selection m_SelectedProperties.erase(m_SelectedProperties.begin() + _Index); } void QmitkPropertiesTableModel::Reset() { // remove all selected properties while (!m_SelectedProperties.empty()) { this->RemoveSelectedProperty(m_SelectedProperties.size() - 1); } std::vector allPredicates; - if (m_PropertyList.IsNotNull()) + if (!m_PropertyList.IsExpired()) { + auto propertyList = m_PropertyList.Lock(); + // first of all: collect all properties from the list - for (auto it = m_PropertyList->GetMap()->begin(); it != m_PropertyList->GetMap()->end(); it++) + for (auto it = propertyList->GetMap()->begin(); it != propertyList->GetMap()->end(); it++) { allPredicates.push_back(*it); //% TODO } } // make a subselection if a keyword is specified if (!m_FilterKeyWord.empty()) { std::vector subSelection; for (auto it = allPredicates.begin(); it != allPredicates.end(); it++) { // add this to the selection if it is matched by the keyword if ((*it).first.find(m_FilterKeyWord) != std::string::npos) subSelection.push_back((*it)); } allPredicates.clear(); allPredicates = subSelection; } PropertyDataSet tmpPropertyDataSet; // add all selected now to the Model for (auto it = allPredicates.begin(); it != allPredicates.end(); it++) { tmpPropertyDataSet = *it; this->AddSelectedProperty(tmpPropertyDataSet); } // sort the list as indicated by m_SortDescending this->sort(m_SortDescending); // model was resetted QAbstractTableModel::beginResetModel(); QAbstractTableModel::endResetModel(); } void QmitkPropertiesTableModel::SetFilterPropertiesKeyWord(std::string _FilterKeyWord) { m_FilterKeyWord = _FilterKeyWord; this->Reset(); } QmitkPropertiesTableModel::PropertyDataSetCompareFunction::PropertyDataSetCompareFunction( CompareCriteria _CompareCriteria, CompareOperator _CompareOperator) : m_CompareCriteria(_CompareCriteria), m_CompareOperator(_CompareOperator) { } bool QmitkPropertiesTableModel::PropertyDataSetCompareFunction::operator()(const PropertyDataSet &_Left, const PropertyDataSet &_Right) const { switch (m_CompareCriteria) { case CompareByValue: if (m_CompareOperator == Less) return (_Left.second->GetValueAsString() < _Right.second->GetValueAsString()); else return (_Left.second->GetValueAsString() > _Right.second->GetValueAsString()); break; // CompareByName: default: if (m_CompareOperator == Less) return (_Left.first < _Right.first); else return (_Left.first > _Right.first); break; } } QmitkPropertiesTableModel::PropertyListElementFilterFunction::PropertyListElementFilterFunction( const std::string &_FilterKeyWord) : m_FilterKeyWord(_FilterKeyWord) { } bool QmitkPropertiesTableModel::PropertyListElementFilterFunction::operator()(const PropertyDataSet &_Elem) const { if (m_FilterKeyWord.empty()) return true; return (_Elem.first.find(m_FilterKeyWord) == 0); } diff --git a/Modules/QtWidgets/src/QmitkPropertyItemDelegate.cpp b/Modules/QtWidgets/src/QmitkPropertyItemDelegate.cpp index 1d114ace30..8e1f599046 100644 --- a/Modules/QtWidgets/src/QmitkPropertyItemDelegate.cpp +++ b/Modules/QtWidgets/src/QmitkPropertyItemDelegate.cpp @@ -1,395 +1,395 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkPropertyItemDelegate.h" #include "QmitkPropertyItemModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include mitk::IPropertyExtensions *GetPropertyService() { us::ModuleContext *context = us::GetModuleContext(); us::ServiceReference serviceRef = context->GetServiceReference(); return serviceRef ? context->GetService(serviceRef) : nullptr; } QmitkColorWidget::QmitkColorWidget(QWidget *parent) : QWidget(parent), m_LineEdit(new QLineEdit), m_Button(new QToolButton) { m_LineEdit->setText(m_Color.name()); m_Button->setText("..."); QHBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_LineEdit); layout->addWidget(m_Button); this->setFocusProxy(m_LineEdit); this->setLayout(layout); connect(m_LineEdit, SIGNAL(editingFinished()), this, SLOT(OnLineEditEditingFinished())); connect(m_Button, SIGNAL(clicked()), this, SLOT(OnButtonClicked())); } QmitkColorWidget::~QmitkColorWidget() { } QColor QmitkColorWidget::GetColor() const { return m_Color; } void QmitkColorWidget::SetColor(QColor color) { m_Color = color; m_LineEdit->setText(color.name()); } void QmitkColorWidget::OnLineEditEditingFinished() { if (!QColor::isValidColor(m_LineEdit->text())) m_LineEdit->setText("#000000"); m_Color.setNamedColor(m_LineEdit->text()); } void QmitkColorWidget::OnButtonClicked() { QColor color = QColorDialog::getColor(m_Color, QApplication::activeWindow()); if (color.isValid()) { this->SetColor(color); emit ColorPicked(); } } QmitkComboBoxListView::QmitkComboBoxListView(QComboBox *comboBox) : m_ComboBox(comboBox) { } QmitkComboBoxListView::~QmitkComboBoxListView() { } void QmitkComboBoxListView::paintEvent(QPaintEvent *event) { if (m_ComboBox != nullptr) { QStyleOptionComboBox option; option.initFrom(m_ComboBox); option.editable = m_ComboBox->isEditable(); if (m_ComboBox->style()->styleHint(QStyle::SH_ComboBox_Popup, &option, m_ComboBox)) { QStyleOptionMenuItem menuOption; menuOption.initFrom(this); menuOption.palette = this->palette(); menuOption.state = QStyle::State_None; menuOption.checkType = QStyleOptionMenuItem::NotCheckable; menuOption.menuRect = event->rect(); menuOption.maxIconWidth = 0; menuOption.tabWidth = 0; QPainter painter(this->viewport()); m_ComboBox->style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOption, &painter, this); } } QListView::paintEvent(event); } void QmitkComboBoxListView::resizeEvent(QResizeEvent *event) { int width = this->viewport()->width(); int height = this->contentsSize().height(); this->resizeContents(width, height); QListView::resizeEvent(event); } QStyleOptionViewItem QmitkComboBoxListView::viewOptions() const { QStyleOptionViewItem option = QListView::viewOptions(); option.showDecorationSelected = true; if (m_ComboBox != nullptr) option.font = m_ComboBox->font(); return option; } class PropertyEqualTo { public: PropertyEqualTo(const mitk::BaseProperty *property) : m_Property(property) {} bool operator()(const mitk::PropertyList::PropertyMapElementType &pair) const { return pair.second.GetPointer() == m_Property; } private: const mitk::BaseProperty *m_Property; }; QmitkPropertyItemDelegate::QmitkPropertyItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QmitkPropertyItemDelegate::~QmitkPropertyItemDelegate() { } QWidget *QmitkPropertyItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant data = index.data(Qt::EditRole); if (data.isValid()) { if (data.type() == QVariant::Int) { QSpinBox *spinBox = new QSpinBox(parent); mitk::IPropertyExtensions *extensions = GetPropertyService(); std::string name = this->GetPropertyName(index); if (extensions != nullptr && !name.empty() && extensions->HasExtension(name)) { mitk::IntPropertyExtension::Pointer extension = dynamic_cast(extensions->GetExtension(name).GetPointer()); if (extension.IsNotNull()) { spinBox->setMinimum(extension->GetMinimum()); spinBox->setMaximum(extension->GetMaximum()); spinBox->setSingleStep(extension->GetSingleStep()); } } connect(spinBox, SIGNAL(editingFinished()), this, SLOT(OnSpinBoxEditingFinished())); return spinBox; } if (data.type() == QVariant::Double || static_cast(data.type()) == QMetaType::Float) { QDoubleSpinBox *spinBox = new QDoubleSpinBox(parent); mitk::IPropertyExtensions *extensions = GetPropertyService(); std::string name = this->GetPropertyName(index); if (extensions != nullptr && !name.empty() && extensions->HasExtension(name)) { mitk::FloatPropertyExtension::Pointer extension = dynamic_cast(extensions->GetExtension(name).GetPointer()); if (extension.IsNotNull()) { spinBox->setMinimum(extension->GetMinimum()); spinBox->setMaximum(extension->GetMaximum()); spinBox->setSingleStep(extension->GetSingleStep()); spinBox->setDecimals(extension->GetDecimals()); } } else { spinBox->setSingleStep(0.1); spinBox->setDecimals(4); } if (name == "opacity") // TODO { spinBox->setMinimum(0.0); spinBox->setMaximum(1.0); } connect(spinBox, SIGNAL(editingFinished()), this, SLOT(OnSpinBoxEditingFinished())); return spinBox; } if (data.type() == QVariant::StringList) { QComboBox *comboBox = new QComboBox(parent); comboBox->setView(new QmitkComboBoxListView(comboBox)); comboBox->addItems(data.toStringList()); connect(comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnComboBoxCurrentIndexChanged(int))); return comboBox; } if (data.type() == QVariant::Color) { QmitkColorWidget *colorWidget = new QmitkColorWidget(parent); connect(colorWidget, SIGNAL(ColorPicked()), this, SLOT(OnColorPicked())); return colorWidget; } } return QStyledItemDelegate::createEditor(parent, option, index); } std::string QmitkPropertyItemDelegate::GetPropertyName(const QModelIndex &index) const { - if (m_PropertyList.IsNotNull()) + if (!m_PropertyList.IsExpired()) { mitk::BaseProperty *property = reinterpret_cast(index.data(mitk::PropertyRole).value()); - const mitk::PropertyList::PropertyMap *propertyMap = m_PropertyList->GetMap(); + const mitk::PropertyList::PropertyMap *propertyMap = m_PropertyList.Lock()->GetMap(); mitk::PropertyList::PropertyMap::const_iterator it = std::find_if(propertyMap->begin(), propertyMap->end(), PropertyEqualTo(property)); if (it != propertyMap->end()) return it->first; } return ""; } void QmitkPropertyItemDelegate::OnComboBoxCurrentIndexChanged(int) { QComboBox *comboBox = qobject_cast(sender()); emit commitData(comboBox); emit closeEditor(comboBox); } void QmitkPropertyItemDelegate::OnSpinBoxEditingFinished() { QAbstractSpinBox *spinBox = qobject_cast(sender()); emit commitData(spinBox); emit closeEditor(spinBox); } void QmitkPropertyItemDelegate::OnColorPicked() { QmitkColorWidget *colorWidget = qobject_cast(sender()); emit commitData(colorWidget); emit closeEditor(colorWidget); } void QmitkPropertyItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant data = index.data(); if (index.column() == 1 && data.type() == QVariant::Color) { painter->fillRect(option.rect, data.value()); return; } QStyledItemDelegate::paint(painter, option, index); } void QmitkPropertyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QVariant data = index.data(Qt::EditRole); if (!data.isValid()) return; if (data.type() == QVariant::StringList) { QComboBox *comboBox = qobject_cast(editor); comboBox->setCurrentIndex(comboBox->findText(index.data().toString())); } if (data.type() == QVariant::Color) { QmitkColorWidget *colorWidget = qobject_cast(editor); colorWidget->SetColor(data.value()); } else { QStyledItemDelegate::setEditorData(editor, index); } } void QmitkPropertyItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QVariant data = index.data(Qt::EditRole); if (!data.isValid()) return; if (data.type() == QVariant::Int) { QSpinBox *spinBox = qobject_cast(editor); model->setData(index, spinBox->value()); } else if (data.type() == QVariant::Double) { QDoubleSpinBox *spinBox = qobject_cast(editor); model->setData(index, spinBox->value()); } else if (static_cast(data.type()) == QMetaType::Float) { QDoubleSpinBox *spinBox = qobject_cast(editor); model->setData(index, static_cast(spinBox->value())); } else if (data.type() == QVariant::StringList) { QComboBox *comboBox = qobject_cast(editor); model->setData(index, comboBox->currentText()); } else if (data.type() == QVariant::Color) { QmitkColorWidget *colorWidget = qobject_cast(editor); model->setData(index, colorWidget->GetColor()); } else { QStyledItemDelegate::setModelData(editor, model, index); } } void QmitkPropertyItemDelegate::SetPropertyList(mitk::PropertyList *propertyList) { - if (m_PropertyList.GetPointer() != propertyList) + if (m_PropertyList != propertyList) m_PropertyList = propertyList; } diff --git a/Modules/QtWidgets/src/QmitkPropertyItemModel.cpp b/Modules/QtWidgets/src/QmitkPropertyItemModel.cpp index 8825572b07..c6d0d4656c 100644 --- a/Modules/QtWidgets/src/QmitkPropertyItemModel.cpp +++ b/Modules/QtWidgets/src/QmitkPropertyItemModel.cpp @@ -1,539 +1,518 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkPropertyItemModel.h" #include "QmitkPropertyItem.h" #include #include #include #include #include #include #include #include #include #include #include template T *GetPropertyService() { us::ModuleContext *context = us::GetModuleContext(); us::ServiceReference serviceRef = context->GetServiceReference(); return serviceRef ? context->GetService(serviceRef) : nullptr; } static QColor MitkToQt(const mitk::Color &color) { return QColor(color.GetRed() * 255, color.GetGreen() * 255, color.GetBlue() * 255); } static mitk::BaseProperty *GetBaseProperty(const QVariant &data) { return data.isValid() ? reinterpret_cast(data.value()) : nullptr; } static mitk::Color QtToMitk(const QColor &color) { mitk::Color mitkColor; mitkColor.SetRed(color.red() / 255.0f); mitkColor.SetGreen(color.green() / 255.0f); mitkColor.SetBlue(color.blue() / 255.0f); return mitkColor; } class PropertyEqualTo { public: PropertyEqualTo(const mitk::BaseProperty *property) : m_Property(property) {} bool operator()(const mitk::PropertyList::PropertyMapElementType &pair) const { return pair.second.GetPointer() == m_Property; } private: const mitk::BaseProperty *m_Property; }; QmitkPropertyItemModel::QmitkPropertyItemModel(QObject *parent) : QAbstractItemModel(parent), m_ShowAliases(false), m_FilterProperties(false), m_PropertyAliases(nullptr), m_PropertyFilters(nullptr) { this->CreateRootItem(); } QmitkPropertyItemModel::~QmitkPropertyItemModel() { this->SetNewPropertyList(nullptr); } int QmitkPropertyItemModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return static_cast(parent.internalPointer())->GetColumnCount(); else return m_RootItem->GetColumnCount(); } void QmitkPropertyItemModel::CreateRootItem() { QList rootData; rootData << "Property" << "Value"; m_RootItem.reset(new QmitkPropertyItem(rootData)); this->beginResetModel(); this->endResetModel(); } QVariant QmitkPropertyItemModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); mitk::BaseProperty *property = index.column() == 1 ? GetBaseProperty(static_cast(index.internalPointer())->GetData(1)) : nullptr; if (role == Qt::DisplayRole) { if (index.column() == 0) { return static_cast(index.internalPointer())->GetData(0); } else if (index.column() == 1 && property != nullptr) { if (auto colorProperty = dynamic_cast(property)) return MitkToQt(colorProperty->GetValue()); else if (dynamic_cast(property) == nullptr) return QString::fromStdString(property->GetValueAsString()); } } else if (index.column() == 1 && property != nullptr) { if (role == Qt::CheckStateRole) { if (auto boolProperty = dynamic_cast(property)) return boolProperty->GetValue() ? Qt::Checked : Qt::Unchecked; } else if (role == Qt::EditRole) { if (dynamic_cast(property) != nullptr) { return QString::fromStdString(property->GetValueAsString()); } else if (auto intProperty = dynamic_cast(property)) { return intProperty->GetValue(); } else if (auto floatProperty = dynamic_cast(property)) { return floatProperty->GetValue(); } else if (auto doubleProperty = dynamic_cast(property)) { return doubleProperty->GetValue(); } else if (auto enumProperty = dynamic_cast(property)) { QStringList values; for (mitk::EnumerationProperty::EnumConstIterator it = enumProperty->Begin(); it != enumProperty->End(); it++) values << QString::fromStdString(it->second); return values; } else if (auto colorProperty = dynamic_cast(property)) { return MitkToQt(colorProperty->GetValue()); } } else if (role == mitk::PropertyRole) { return QVariant::fromValue(property); } } return QVariant(); } QModelIndex QmitkPropertyItemModel::FindProperty(const mitk::BaseProperty *property) { if (property == nullptr) return QModelIndex(); - auto propertyMap = m_PropertyList->GetMap(); + auto propertyMap = m_PropertyList.Lock()->GetMap(); auto it = std::find_if(propertyMap->begin(), propertyMap->end(), PropertyEqualTo(property)); if (it == propertyMap->end()) return QModelIndex(); QString name = QString::fromStdString(it->first); if (!name.contains('.')) { QModelIndexList item = this->match(index(0, 0), Qt::DisplayRole, name, 1, Qt::MatchExactly); if (!item.empty()) return item[0]; } else { QStringList names = name.split('.'); QModelIndexList items = this->match(index(0, 0), Qt::DisplayRole, names.last(), -1, Qt::MatchRecursive | Qt::MatchExactly); for (auto item : items) { QModelIndex candidate = item; for (int i = names.length() - 1; i != 0; --i) { QModelIndex parent = item.parent(); if (parent.parent() == QModelIndex()) { if (parent.data() != names.first()) break; return candidate; } if (parent.data() != names[i - 1]) break; item = parent; } } } return QModelIndex(); } Qt::ItemFlags QmitkPropertyItemModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractItemModel::flags(index); if (index.column() == 1) { if (index.data(Qt::EditRole).isValid()) flags |= Qt::ItemIsEditable; if (index.data(Qt::CheckStateRole).isValid()) flags |= Qt::ItemIsUserCheckable; } return flags; } mitk::PropertyList *QmitkPropertyItemModel::GetPropertyList() const { - return m_PropertyList.GetPointer(); + return m_PropertyList.Lock().GetPointer(); } QVariant QmitkPropertyItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return m_RootItem->GetData(section); return QVariant(); } QModelIndex QmitkPropertyItemModel::index(int row, int column, const QModelIndex &parent) const { if (!this->hasIndex(row, column, parent)) return QModelIndex(); QmitkPropertyItem *parentItem = parent.isValid() ? static_cast(parent.internalPointer()) : m_RootItem.get(); QmitkPropertyItem *childItem = parentItem->GetChild(row); return childItem != nullptr ? this->createIndex(row, column, childItem) : QModelIndex(); } void QmitkPropertyItemModel::OnPreferencesChanged() { bool updateAliases = m_ShowAliases != (m_PropertyAliases != nullptr); bool updateFilters = m_FilterProperties != (m_PropertyFilters != nullptr); bool resetPropertyList = false; if (updateAliases) { m_PropertyAliases = m_ShowAliases ? GetPropertyService() : nullptr; - resetPropertyList = m_PropertyList.IsNotNull(); + resetPropertyList = !m_PropertyList.IsExpired(); } if (updateFilters) { m_PropertyFilters = m_FilterProperties ? GetPropertyService() : nullptr; if (!resetPropertyList) - resetPropertyList = m_PropertyList.IsNotNull(); + resetPropertyList = !m_PropertyList.IsExpired(); } if (resetPropertyList) - this->SetNewPropertyList(m_PropertyList.GetPointer()); + this->SetNewPropertyList(m_PropertyList.Lock()); } -void QmitkPropertyItemModel::OnPropertyDeleted(const itk::Object * /*property*/, const itk::EventObject &) +void QmitkPropertyItemModel::OnPropertyListModified() { - /*QModelIndex index = this->FindProperty(static_cast(property)); - - if (index != QModelIndex()) - this->reset();*/ -} - -void QmitkPropertyItemModel::OnPropertyListModified(const itk::Object *propertyList) -{ - this->SetNewPropertyList(dynamic_cast(const_cast(propertyList))); + this->SetNewPropertyList(m_PropertyList.Lock()); } -void QmitkPropertyItemModel::OnPropertyListDeleted(const itk::Object *) +void QmitkPropertyItemModel::OnPropertyListDeleted() { this->CreateRootItem(); } void QmitkPropertyItemModel::OnPropertyModified(const itk::Object *property, const itk::EventObject &) { QModelIndex index = this->FindProperty(static_cast(property)); if (index != QModelIndex()) emit dataChanged(index, index); } QModelIndex QmitkPropertyItemModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkPropertyItem *parentItem = static_cast(child.internalPointer())->GetParent(); if (parentItem == m_RootItem.get()) return QModelIndex(); return this->createIndex(parentItem->GetRow(), 0, parentItem); } int QmitkPropertyItemModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; QmitkPropertyItem *parentItem = parent.isValid() ? static_cast(parent.internalPointer()) : m_RootItem.get(); return parentItem->GetChildCount(); } bool QmitkPropertyItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || index.column() != 1 || (role != Qt::EditRole && role != Qt::CheckStateRole)) return false; mitk::BaseProperty *property = GetBaseProperty(static_cast(index.internalPointer())->GetData(1)); if (property == nullptr) return false; if (mitk::BoolProperty *boolProperty = dynamic_cast(property)) { boolProperty->SetValue(value.toInt() == Qt::Checked ? true : false); } else if (mitk::StringProperty *stringProperty = dynamic_cast(property)) { stringProperty->SetValue(value.toString().toStdString()); } else if (mitk::IntProperty *intProperty = dynamic_cast(property)) { intProperty->SetValue(value.toInt()); } else if (mitk::FloatProperty *floatProperty = dynamic_cast(property)) { floatProperty->SetValue(value.toFloat()); } else if (mitk::DoubleProperty *doubleProperty = dynamic_cast(property)) { doubleProperty->SetValue(value.toDouble()); } else if (mitk::EnumerationProperty *enumProperty = dynamic_cast(property)) { std::string selection = value.toString().toStdString(); if (selection != enumProperty->GetValueAsString() && enumProperty->IsValidEnumerationValue(selection)) enumProperty->SetValue(selection); } else if (mitk::ColorProperty *colorProperty = dynamic_cast(property)) { colorProperty->SetValue(QtToMitk(value.value())); } - m_PropertyList->InvokeEvent(itk::ModifiedEvent()); - m_PropertyList->Modified(); + auto propertyList = m_PropertyList.Lock(); + + propertyList->InvokeEvent(itk::ModifiedEvent()); + propertyList->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return true; } -void QmitkPropertyItemModel::SetNewPropertyList(mitk::PropertyList *propertyList) +void QmitkPropertyItemModel::SetNewPropertyList(mitk::PropertyList *newPropertyList) { typedef mitk::PropertyList::PropertyMap PropertyMap; this->beginResetModel(); - if (m_PropertyList.IsNotNull()) + if (!m_PropertyList.IsExpired()) { - mitk::MessageDelegate1 onPropertyListDeleted( - this, &QmitkPropertyItemModel::OnPropertyListDeleted); - m_PropertyList.ObjectDelete.RemoveListener(onPropertyListDeleted); + auto propertyList = m_PropertyList.Lock(); - mitk::MessageDelegate1 onPropertyListModified( - this, &QmitkPropertyItemModel::OnPropertyListModified); - m_PropertyList.ObjectModified.RemoveListener(onPropertyListModified); + propertyList->RemoveObserver(m_PropertyListDeletedTag); + propertyList->RemoveObserver(m_PropertyListModifiedTag); - const PropertyMap *propertyMap = m_PropertyList->GetMap(); + const PropertyMap *propertyMap = m_PropertyList.Lock()->GetMap(); for (PropertyMap::const_iterator propertyIt = propertyMap->begin(); propertyIt != propertyMap->end(); ++propertyIt) { std::map::const_iterator tagIt = m_PropertyModifiedTags.find(propertyIt->first); if (tagIt != m_PropertyModifiedTags.end()) propertyIt->second->RemoveObserver(tagIt->second); tagIt = m_PropertyDeletedTags.find(propertyIt->first); if (tagIt != m_PropertyDeletedTags.end()) propertyIt->second->RemoveObserver(tagIt->second); } m_PropertyModifiedTags.clear(); m_PropertyDeletedTags.clear(); } - m_PropertyList = propertyList; + m_PropertyList = newPropertyList; - if (m_PropertyList.IsNotNull()) + if (!m_PropertyList.IsExpired()) { - mitk::MessageDelegate1 onPropertyListModified( - this, &QmitkPropertyItemModel::OnPropertyListModified); - m_PropertyList.ObjectModified.AddListener(onPropertyListModified); - - mitk::MessageDelegate1 onPropertyListDeleted( - this, &QmitkPropertyItemModel::OnPropertyListDeleted); - m_PropertyList.ObjectDelete.AddListener(onPropertyListDeleted); + auto onPropertyListModified = itk::SimpleMemberCommand::New(); + onPropertyListModified->SetCallbackFunction(this, &QmitkPropertyItemModel::OnPropertyListModified); + m_PropertyListModifiedTag = m_PropertyList.Lock()->AddObserver(itk::ModifiedEvent(), onPropertyListModified); - mitk::MessageDelegate2 onPropertyModified( - this, &QmitkPropertyItemModel::OnPropertyModified); + auto onPropertyListDeleted = itk::SimpleMemberCommand::New(); + onPropertyListDeleted->SetCallbackFunction(this, &QmitkPropertyItemModel::OnPropertyListDeleted); + m_PropertyListDeletedTag = m_PropertyList.Lock()->AddObserver(itk::DeleteEvent(), onPropertyListDeleted); - itk::MemberCommand::Pointer modifiedCommand = - itk::MemberCommand::New(); + auto modifiedCommand = itk::MemberCommand::New(); modifiedCommand->SetCallbackFunction(this, &QmitkPropertyItemModel::OnPropertyModified); - const PropertyMap *propertyMap = m_PropertyList->GetMap(); + const PropertyMap *propertyMap = m_PropertyList.Lock()->GetMap(); for (PropertyMap::const_iterator it = propertyMap->begin(); it != propertyMap->end(); ++it) m_PropertyModifiedTags.insert( std::make_pair(it->first, it->second->AddObserver(itk::ModifiedEvent(), modifiedCommand))); - - itk::MemberCommand::Pointer deletedCommand = - itk::MemberCommand::New(); - deletedCommand->SetCallbackFunction(this, &QmitkPropertyItemModel::OnPropertyDeleted); - - for (PropertyMap::const_iterator it = propertyMap->begin(); it != propertyMap->end(); ++it) - m_PropertyDeletedTags.insert( - std::make_pair(it->first, it->second->AddObserver(itk::DeleteEvent(), deletedCommand))); } this->CreateRootItem(); - if (m_PropertyList != nullptr && !m_PropertyList->IsEmpty()) + if (m_PropertyList != nullptr && !m_PropertyList.Lock()->IsEmpty()) { mitk::PropertyList::PropertyMap filteredProperties; bool filterProperties = false; if (m_PropertyFilters != nullptr && (m_PropertyFilters->HasFilter() || m_PropertyFilters->HasFilter(m_ClassName.toStdString()))) { - filteredProperties = m_PropertyFilters->ApplyFilter(*m_PropertyList->GetMap(), m_ClassName.toStdString()); + filteredProperties = m_PropertyFilters->ApplyFilter(*m_PropertyList.Lock()->GetMap(), m_ClassName.toStdString()); filterProperties = true; } const mitk::PropertyList::PropertyMap *propertyMap = - !filterProperties ? m_PropertyList->GetMap() : &filteredProperties; + !filterProperties ? m_PropertyList.Lock()->GetMap() : &filteredProperties; mitk::PropertyList::PropertyMap::const_iterator end = propertyMap->end(); for (mitk::PropertyList::PropertyMap::const_iterator iter = propertyMap->begin(); iter != end; ++iter) { std::vector aliases; if (m_PropertyAliases != nullptr) { aliases = m_PropertyAliases->GetAliases(iter->first, m_ClassName.toStdString()); if (aliases.empty() && !m_ClassName.isEmpty()) aliases = m_PropertyAliases->GetAliases(iter->first); } if (aliases.empty()) { QList data; data << QString::fromStdString(iter->first) << QVariant::fromValue((reinterpret_cast(iter->second.GetPointer()))); m_RootItem->AppendChild(new QmitkPropertyItem(data)); } else { std::vector::const_iterator end = aliases.end(); for (std::vector::const_iterator aliasIter = aliases.begin(); aliasIter != end; ++aliasIter) { QList data; data << QString::fromStdString(*aliasIter) << QVariant::fromValue((reinterpret_cast(iter->second.GetPointer()))); m_RootItem->AppendChild(new QmitkPropertyItem(data)); } } } } this->endResetModel(); } void QmitkPropertyItemModel::SetPropertyList(mitk::PropertyList *propertyList, const QString &className) { - if (m_PropertyList.GetPointer() != propertyList) + if (m_PropertyList != propertyList) { m_ClassName = className; this->SetNewPropertyList(propertyList); } } void QmitkPropertyItemModel::Update() { - this->SetNewPropertyList(m_PropertyList); + this->SetNewPropertyList(m_PropertyList.Lock()); } diff --git a/Modules/QtWidgetsExt/include/QmitkPointListViewWidget.h b/Modules/QtWidgetsExt/include/QmitkPointListViewWidget.h index ce923201d5..d4d46978c3 100644 --- a/Modules/QtWidgetsExt/include/QmitkPointListViewWidget.h +++ b/Modules/QtWidgetsExt/include/QmitkPointListViewWidget.h @@ -1,100 +1,104 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkPointListViewWidget_h #define QmitkPointListViewWidget_h #include "MitkQtWidgetsExtExports.h" #include #include #include class QmitkStdMultiWidget; /*! * \brief GUI widget for handling mitk::PointSet * * Displays all the points in a mitk::PointSet graphically. * Reacts automatically to changes in the PointSet's selection status. * Updates PointSet's selection status when this list's selection changes. * * If a QmitkStdMultiWidget is assigned via SetMultiWidget(), the * crosshair of the QmitkStdMultiWidget is moved to the currently selected * point. * */ class MITKQTWIDGETSEXT_EXPORT QmitkPointListViewWidget : public QListWidget { Q_OBJECT signals: void PointSelectionChanged(); ///< this signal is emmitted, if the selection of a point in the pointset is changed public: QmitkPointListViewWidget(QWidget *parent = 0); ~QmitkPointListViewWidget() override; /// assign a point set for observation void SetPointSet(mitk::PointSet *pointSet); /// which point set to work on const mitk::PointSet *GetPointSet() const; void SetMultiWidget( QmitkStdMultiWidget *multiWidget); ///< assign a QmitkStdMultiWidget for updating render window crosshair QmitkStdMultiWidget *GetMultiWidget() const; ///< return the QmitkStdMultiWidget that is used for updating render window crosshair /// which time step to display/model void SetTimeStep(int t); /// which time step to display/model int GetTimeStep() const; /// observer for point set "modified" events - void OnPointSetChanged(const itk::Object * /*obj*/); + void OnPointSetChanged(); /// observer for point set "delete" events - void OnPointSetDeleted(const itk::Object * /*obj*/); + void OnPointSetDeleted(); protected slots: /// /// Filtering double click event for editing point coordinates via a dialog /// void OnItemDoubleClicked(QListWidgetItem *item); /// called when the selection of the view widget changes void OnCurrentRowChanged(int /*currentRow*/); protected: void keyPressEvent(QKeyEvent *e) override; ///< react to F2, F3 and DEL keys void MoveSelectedPointUp(); void MoveSelectedPointDown(); void RemoveSelectedPoint(); void Update(bool currentRowChanged = false); protected: mitk::WeakPointer m_PointSet; + + unsigned long m_PointSetDeletedTag; + unsigned long m_PointSetModifiedTag; + int m_TimeStep; bool m_SelfCall; /// used to position the planes on a selected point QmitkStdMultiWidget *m_MultiWidget; }; #endif diff --git a/Modules/QtWidgetsExt/src/QmitkPointListViewWidget.cpp b/Modules/QtWidgetsExt/src/QmitkPointListViewWidget.cpp index 1f98432b6e..dec31d5cba 100644 --- a/Modules/QtWidgetsExt/src/QmitkPointListViewWidget.cpp +++ b/Modules/QtWidgetsExt/src/QmitkPointListViewWidget.cpp @@ -1,247 +1,260 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkPointListViewWidget.h" #include "QmitkEditPointDialog.h" #include "QmitkPointListModel.h" #include "QmitkStdMultiWidget.h" #include "mitkInteractionConst.h" #include "mitkPointOperation.h" #include "mitkRenderingManager.h" #include QmitkPointListViewWidget::QmitkPointListViewWidget(QWidget *parent) : QListWidget(parent), m_TimeStep(0), m_SelfCall(false), m_MultiWidget(nullptr) { QListWidget::setAlternatingRowColors(true); // logic QListWidget::setSelectionBehavior(QAbstractItemView::SelectRows); QListWidget::setSelectionMode(QAbstractItemView::SingleSelection); connect(this, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(OnItemDoubleClicked(QListWidgetItem *))); connect(this, SIGNAL(currentRowChanged(int)), this, SLOT(OnCurrentRowChanged(int))); } QmitkPointListViewWidget::~QmitkPointListViewWidget() { this->SetPointSet(0); // remove listener } void QmitkPointListViewWidget::SetPointSet(mitk::PointSet *pointSet) { - if (m_PointSet.IsNotNull()) + if (!m_PointSet.IsExpired()) { - m_PointSet.ObjectModified.RemoveListener(mitk::MessageDelegate1( - this, &QmitkPointListViewWidget::OnPointSetChanged)); - m_PointSet.ObjectDelete.RemoveListener(mitk::MessageDelegate1( - this, &QmitkPointListViewWidget::OnPointSetDeleted)); + auto pointSet = m_PointSet.Lock(); + + pointSet->RemoveObserver(m_PointSetModifiedTag); + pointSet->RemoveObserver(m_PointSetDeletedTag); } m_PointSet = pointSet; - if (m_PointSet.IsNotNull()) + if (!m_PointSet.IsExpired()) { - m_PointSet.ObjectModified.AddListener(mitk::MessageDelegate1( - this, &QmitkPointListViewWidget::OnPointSetChanged)); - m_PointSet.ObjectDelete.AddListener(mitk::MessageDelegate1( - this, &QmitkPointListViewWidget::OnPointSetDeleted)); + auto pointSet = m_PointSet.Lock(); + + auto onPointSetDeleted = itk::SimpleMemberCommand::New(); + onPointSetDeleted->SetCallbackFunction(this, &QmitkPointListViewWidget::OnPointSetDeleted); + m_PointSetDeletedTag = pointSet->AddObserver(itk::DeleteEvent(), onPointSetDeleted); + + auto onPointSetModified = itk::SimpleMemberCommand::New(); + onPointSetModified->SetCallbackFunction(this, &QmitkPointListViewWidget::OnPointSetChanged); + m_PointSetModifiedTag = pointSet->AddObserver(itk::DeleteEvent(), onPointSetModified); } this->Update(); } const mitk::PointSet *QmitkPointListViewWidget::GetPointSet() const { - return m_PointSet; + return m_PointSet.Lock(); } void QmitkPointListViewWidget::SetTimeStep(int t) { m_TimeStep = t; this->Update(); } int QmitkPointListViewWidget::GetTimeStep() const { return m_TimeStep; } void QmitkPointListViewWidget::SetMultiWidget(QmitkStdMultiWidget *multiWidget) { m_MultiWidget = multiWidget; } QmitkStdMultiWidget *QmitkPointListViewWidget::GetMultiWidget() const { return m_MultiWidget; } -void QmitkPointListViewWidget::OnPointSetChanged(const itk::Object *) +void QmitkPointListViewWidget::OnPointSetChanged() { if (!m_SelfCall) this->Update(); } -void QmitkPointListViewWidget::OnPointSetDeleted(const itk::Object *) +void QmitkPointListViewWidget::OnPointSetDeleted() { this->SetPointSet(0); this->Update(); } void QmitkPointListViewWidget::OnItemDoubleClicked(QListWidgetItem *item) { QmitkEditPointDialog _EditPointDialog(this); - _EditPointDialog.SetPoint(m_PointSet, this->row(item), m_TimeStep); + _EditPointDialog.SetPoint(m_PointSet.Lock(), this->row(item), m_TimeStep); _EditPointDialog.exec(); } void QmitkPointListViewWidget::OnCurrentRowChanged(int) { this->Update(true); } void QmitkPointListViewWidget::keyPressEvent(QKeyEvent *e) { - if (m_PointSet.IsNull()) + if (m_PointSet.IsExpired()) return; int key = e->key(); switch (key) { case Qt::Key_F2: this->MoveSelectedPointUp(); break; case Qt::Key_F3: this->MoveSelectedPointDown(); break; case Qt::Key_Delete: this->RemoveSelectedPoint(); break; default: break; } } void QmitkPointListViewWidget::MoveSelectedPointUp() { - if (m_PointSet.IsNull()) + if (m_PointSet.IsExpired()) return; + auto pointSet = m_PointSet.Lock(); + mitk::PointSet::PointIdentifier selectedID; - selectedID = m_PointSet->SearchSelectedPoint(m_TimeStep); + selectedID = pointSet->SearchSelectedPoint(m_TimeStep); mitk::PointOperation *doOp = - new mitk::PointOperation(mitk::OpMOVEPOINTUP, m_PointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); - m_PointSet->ExecuteOperation(doOp); + new mitk::PointOperation(mitk::OpMOVEPOINTUP, pointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); + pointSet->ExecuteOperation(doOp); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // Workaround for update problem in Pointset/Mapper } void QmitkPointListViewWidget::MoveSelectedPointDown() { - if (m_PointSet.IsNull()) + if (m_PointSet.IsExpired()) return; + auto pointSet = m_PointSet.Lock(); + mitk::PointSet::PointIdentifier selectedID; - selectedID = m_PointSet->SearchSelectedPoint(m_TimeStep); + selectedID = pointSet->SearchSelectedPoint(m_TimeStep); mitk::PointOperation *doOp = - new mitk::PointOperation(mitk::OpMOVEPOINTDOWN, m_PointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); - m_PointSet->ExecuteOperation(doOp); + new mitk::PointOperation(mitk::OpMOVEPOINTDOWN, pointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); + pointSet->ExecuteOperation(doOp); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // Workaround for update problem in Pointset/Mapper } void QmitkPointListViewWidget::RemoveSelectedPoint() { - if (m_PointSet.IsNull()) + if (m_PointSet.IsExpired()) return; + auto pointSet = m_PointSet.Lock(); + mitk::PointSet::PointIdentifier selectedID; - selectedID = m_PointSet->SearchSelectedPoint(m_TimeStep); + selectedID = pointSet->SearchSelectedPoint(m_TimeStep); mitk::PointOperation *doOp = - new mitk::PointOperation(mitk::OpREMOVE, m_PointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); - m_PointSet->ExecuteOperation(doOp); + new mitk::PointOperation(mitk::OpREMOVE, pointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); + pointSet->ExecuteOperation(doOp); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // Workaround for update problem in Pointset/Mapper } void QmitkPointListViewWidget::Update(bool currentRowChanged) { if (m_SelfCall) return; - if (m_PointSet.IsNull()) + if (m_PointSet.IsExpired()) { this->clear(); return; } + auto pointSet = m_PointSet.Lock(); + m_SelfCall = true; QString text; int i = 0; - mitk::PointSet::DataType::Pointer pointset = m_PointSet->GetPointSet(m_TimeStep); + mitk::PointSet::DataType::Pointer pointset = pointSet->GetPointSet(m_TimeStep); for (mitk::PointSet::PointsContainer::Iterator it = pointset->GetPoints()->Begin(); it != pointset->GetPoints()->End(); ++it) { text = QString("%0: (%1, %2, %3)") .arg(i, 3) .arg(it.Value().GetElement(0), 0, 'f', 3) .arg(it.Value().GetElement(1), 0, 'f', 3) .arg(it.Value().GetElement(2), 0, 'f', 3); if (i == this->count()) this->addItem(text); // insert text else this->item(i)->setText(text); // update text if (currentRowChanged) { if (i == this->currentRow()) - m_PointSet->SetSelectInfo(this->currentRow(), true, m_TimeStep); + pointSet->SetSelectInfo(this->currentRow(), true, m_TimeStep); else - m_PointSet->SetSelectInfo(it->Index(), false, m_TimeStep); // select nothing now + pointSet->SetSelectInfo(it->Index(), false, m_TimeStep); // select nothing now } ++i; } // remove unnecessary listwidgetitems - while (m_PointSet->GetPointSet(m_TimeStep)->GetPoints()->Size() < (unsigned int)this->count()) + while (pointSet->GetPointSet(m_TimeStep)->GetPoints()->Size() < (unsigned int)this->count()) { QListWidgetItem *item = this->takeItem(this->count() - 1); delete item; } // update selection in pointset or in the list widget if (!currentRowChanged) { - if (m_PointSet->GetNumberOfSelected(m_TimeStep) > 1) + if (pointSet->GetNumberOfSelected(m_TimeStep) > 1) { /// @TODO use logging as soon as available std::cerr << "Point set has multiple selected points. This view is not designed for more than one selected point." << std::endl; } - int selectedIndex = m_PointSet->SearchSelectedPoint(m_TimeStep); + int selectedIndex = pointSet->SearchSelectedPoint(m_TimeStep); if (selectedIndex != -1) // no selected point is found { this->setCurrentRow(selectedIndex); } } m_SelfCall = false; } diff --git a/Modules/Segmentation/Controllers/mitkToolManager.cpp b/Modules/Segmentation/Controllers/mitkToolManager.cpp index b632ed9e20..392ecb2d32 100644 --- a/Modules/Segmentation/Controllers/mitkToolManager.cpp +++ b/Modules/Segmentation/Controllers/mitkToolManager.cpp @@ -1,514 +1,514 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkToolManager.h" #include "mitkCoreObjectFactory.h" #include #include #include #include "mitkInteractionEventObserver.h" #include "mitkSegTool2D.h" #include "usGetModuleContext.h" #include "usModuleContext.h" mitk::ToolManager::ToolManager(DataStorage *storage) : m_ActiveTool(nullptr), m_ActiveToolID(-1), m_RegisteredClients(0), m_DataStorage(storage) { CoreObjectFactory::GetInstance(); // to make sure a CoreObjectFactory was instantiated (and in turn, possible tools // are registered) - bug 1029 this->InitializeTools(); } mitk::ToolManager::~ToolManager() { for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) (*dataIter)->RemoveObserver(m_WorkingDataObserverTags[(*dataIter)]); if (this->GetDataStorage() != nullptr) this->GetDataStorage()->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } for (auto observerTagMapIter = m_ReferenceDataObserverTags.begin(); observerTagMapIter != m_ReferenceDataObserverTags.end(); ++observerTagMapIter) { observerTagMapIter->first->RemoveObserver(observerTagMapIter->second); } } void mitk::ToolManager::InitializeTools() { // clear all previous tool pointers (tools may be still activated from another recently used plugin) if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } m_Tools.clear(); // get a list of all known mitk::Tools std::list thingsThatClaimToBeATool = itk::ObjectFactoryBase::CreateAllInstance("mitkTool"); // remember these tools for (auto iter = thingsThatClaimToBeATool.begin(); iter != thingsThatClaimToBeATool.end(); ++iter) { if (auto *tool = dynamic_cast(iter->GetPointer())) { tool->InitializeStateMachine(); tool->SetToolManager(this); // important to call right after instantiation tool->ErrorMessage += MessageDelegate1(this, &ToolManager::OnToolErrorMessage); tool->GeneralMessage += MessageDelegate1(this, &ToolManager::OnGeneralToolMessage); m_Tools.push_back(tool); } } } void mitk::ToolManager::OnToolErrorMessage(std::string s) { this->ToolErrorMessage(s); } void mitk::ToolManager::OnGeneralToolMessage(std::string s) { this->GeneralToolMessage(s); } const mitk::ToolManager::ToolVectorTypeConst mitk::ToolManager::GetTools() { ToolVectorTypeConst resultList; for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter) { resultList.push_back(iter->GetPointer()); } return resultList; } mitk::Tool *mitk::ToolManager::GetToolById(int id) { try { return m_Tools.at(id); } catch (const std::exception &) { return nullptr; } } bool mitk::ToolManager::ActivateTool(int id) { if (id != -1 && !this->GetToolById(id)->CanHandle(this->GetReferenceData(0)->GetData())) return false; if (this->GetDataStorage()) { this->GetDataStorage()->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); } if (GetToolById(id) == m_ActiveTool) return true; // no change needed static int nextTool = -1; nextTool = id; static bool inActivateTool = false; if (inActivateTool) { return true; } inActivateTool = true; while (nextTool != m_ActiveToolID) { if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); } m_ActiveTool = GetToolById(nextTool); m_ActiveToolID = m_ActiveTool ? nextTool : -1; // current ID if tool is valid, otherwise -1 ActiveToolChanged.Send(); if (m_ActiveTool) { if (m_RegisteredClients > 0) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } } inActivateTool = false; return (m_ActiveTool != nullptr); } void mitk::ToolManager::SetReferenceData(DataVectorType data) { if (data != m_ReferenceData) { // remove observers from old nodes for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { auto searchIter = m_ReferenceDataObserverTags.find(*dataIter); if (searchIter != m_ReferenceDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_ReferenceData = data; // TODO tell active tool? // attach new observers m_ReferenceDataObserverTags.clear(); for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeletedConst); m_ReferenceDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } ReferenceDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheReferenceDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheReferenceDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheReferenceDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_ReferenceDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetReferenceData(v); } void mitk::ToolManager::SetReferenceData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } SetReferenceData(v); } void mitk::ToolManager::SetWorkingData(DataVectorType data) { if (data != m_WorkingData) { // remove observers from old nodes for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { auto searchIter = m_WorkingDataObserverTags.find(*dataIter); if (searchIter != m_WorkingDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_WorkingData = data; // TODO tell active tool? // Quick workaround for bug #16598 if (m_WorkingData.empty()) this->ActivateTool(-1); // workaround end // attach new observers m_WorkingDataObserverTags.clear(); for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeletedConst); m_WorkingDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } WorkingDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheWorkingDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheWorkingDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheWorkingDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_WorkingDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetWorkingData(v); } void mitk::ToolManager::SetWorkingData(DataNode *data) { DataVectorType v; if (data) // don't allow for nullptr nodes { v.push_back(data); } SetWorkingData(v); } void mitk::ToolManager::SetRoiData(DataVectorType data) { if (data != m_RoiData) { // remove observers from old nodes for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { auto searchIter = m_RoiDataObserverTags.find(*dataIter); if (searchIter != m_RoiDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_RoiData = data; // TODO tell active tool? // attach new observers m_RoiDataObserverTags.clear(); for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeletedConst); m_RoiDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } RoiDataChanged.Send(); } } void mitk::ToolManager::SetRoiData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } this->SetRoiData(v); } void mitk::ToolManager::OnOneOfTheRoiDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheRoiDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheRoiDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_RoiDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetRoiData(v); } mitk::ToolManager::DataVectorType mitk::ToolManager::GetReferenceData() { return m_ReferenceData; } mitk::DataNode *mitk::ToolManager::GetReferenceData(int idx) { try { return m_ReferenceData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::ToolManager::DataVectorType mitk::ToolManager::GetWorkingData() { return m_WorkingData; } mitk::ToolManager::DataVectorType mitk::ToolManager::GetRoiData() { return m_RoiData; } mitk::DataNode *mitk::ToolManager::GetRoiData(int idx) { try { return m_RoiData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::DataStorage *mitk::ToolManager::GetDataStorage() { - if (m_DataStorage.IsNotNull()) + if (!m_DataStorage.IsExpired()) { - return m_DataStorage; + return m_DataStorage.Lock(); } else { return nullptr; } } void mitk::ToolManager::SetDataStorage(DataStorage &storage) { m_DataStorage = &storage; } mitk::DataNode *mitk::ToolManager::GetWorkingData(unsigned int idx) { if (m_WorkingData.empty()) return nullptr; if (m_WorkingData.size() > idx) return m_WorkingData[idx]; return nullptr; } int mitk::ToolManager::GetActiveToolID() { return m_ActiveToolID; } mitk::Tool *mitk::ToolManager::GetActiveTool() { return m_ActiveTool; } void mitk::ToolManager::RegisterClient() { if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } ++m_RegisteredClients; } void mitk::ToolManager::UnregisterClient() { if (m_RegisteredClients < 1) return; --m_RegisteredClients; if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); } } } int mitk::ToolManager::GetToolID(const Tool *tool) { int id(0); for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter, ++id) { if (tool == iter->GetPointer()) { return id; } } return -1; } void mitk::ToolManager::OnNodeRemoved(const mitk::DataNode *node) { // check all storage vectors OnOneOfTheReferenceDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheRoiDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheWorkingDataDeleted(const_cast(node), itk::DeleteEvent()); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h index da981f7762..1235cb4cfb 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h @@ -1,299 +1,298 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkSlicesInterpolator_h_Included #define QmitkSlicesInterpolator_h_Included #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkSegmentationInterpolationController.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceInterpolationController.h" #include "mitkToolManager.h" -#include "mitkWeakPointer.h" #include #include "mitkFeatureBasedEdgeDetectionFilter.h" #include "mitkPointCloudScoringFilter.h" #include #include #include #include #include #include #include #include "mitkVtkRepresentationProperty.h" #include "vtkProperty.h" // For running 3D interpolation in background #include #include #include #include namespace mitk { class PlaneGeometry; class SliceNavigationController; } class QPushButton; /** \brief GUI for slices interpolation. \ingroup ToolManagerEtAl \ingroup Widgets \sa QmitkInteractiveSegmentation \sa mitk::SegmentationInterpolation There is a separate page describing the general design of QmitkInteractiveSegmentation: \ref QmitkInteractiveSegmentationTechnicalPage While mitk::SegmentationInterpolation does the bookkeeping of interpolation (keeping track of which slices contain how much segmentation) and the algorithmic work, QmitkSlicesInterpolator is responsible to watch the GUI, to notice, which slice is currently visible. It triggers generation of interpolation suggestions and also triggers acception of suggestions. \todo show/hide feedback on demand Last contributor: $Author: maleike $ */ class MITKSEGMENTATIONUI_EXPORT QmitkSlicesInterpolator : public QWidget { Q_OBJECT public: QmitkSlicesInterpolator(QWidget *parent = 0, const char *name = 0); /** To be called once before real use. */ void Initialize(mitk::ToolManager *toolManager, const QList &controllers); void Uninitialize(); ~QmitkSlicesInterpolator() override; void SetDataStorage(mitk::DataStorage::Pointer storage); mitk::DataStorage *GetDataStorage(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerWorkingDataModified(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerReferenceDataModified(); void OnTimeChanged(itk::Object *sender, const itk::EventObject &); void OnSliceChanged(itk::Object *sender, const itk::EventObject &); void OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationInfoChanged(const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnSurfaceInterpolationInfoChanged(const itk::EventObject &); /** * @brief Set the visibility of the 3d interpolation */ void Show3DInterpolationResult(bool); signals: void SignalRememberContourPositions(bool); void SignalShowMarkerNodes(bool); public slots: virtual void setEnabled(bool); /** Call this from the outside to enable/disable interpolation */ void EnableInterpolation(bool); void Enable3DInterpolation(bool); /** Call this from the outside to accept all interpolations */ void FinishInterpolation(mitk::SliceNavigationController *slicer = nullptr); protected slots: /** Reaction to button clicks. */ void OnAcceptInterpolationClicked(); /* Opens popup to ask about which orientation should be interpolated */ void OnAcceptAllInterpolationsClicked(); /* Reaction to button clicks */ void OnAccept3DInterpolationClicked(); void OnReinit3DInterpolation(); void OnSuggestPlaneClicked(); /* * Will trigger interpolation for all slices in given orientation (called from popup menu of * OnAcceptAllInterpolationsClicked) */ void OnAcceptAllPopupActivated(QAction *action); /** Called on activation/deactivation */ void OnInterpolationActivated(bool); void On3DInterpolationActivated(bool); void OnInterpolationMethodChanged(int index); // Enhancement for 3D interpolation void On2DInterpolationEnabled(bool); void On3DInterpolationEnabled(bool); void OnInterpolationDisabled(bool); void OnShowMarkers(bool); void Run3DInterpolation(); void RunPlaneSuggestion(); void OnSurfaceInterpolationFinished(); void StartUpdateInterpolationTimer(); void StopUpdateInterpolationTimer(); void ChangeSurfaceColor(); protected: const std::map createActionToSliceDimension(); std::map ACTION_TO_SLICEDIMENSION; void AcceptAllInterpolations(mitk::SliceNavigationController *slicer); /** Retrieves the currently selected PlaneGeometry from a SlicedGeometry3D that is generated by a SliceNavigationController and calls Interpolate to further process this PlaneGeometry into an interpolation. \param e is a actually a mitk::SliceNavigationController::GeometrySliceEvent, sent by a SliceNavigationController \param slice the SliceNavigationController */ bool TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer); /** Given a PlaneGeometry, this method figures out which slice of the first working image (of the associated ToolManager) should be interpolated. The actual work is then done by our SegmentationInterpolation object. */ void Interpolate(mitk::PlaneGeometry *plane, unsigned int timeStep, mitk::SliceNavigationController *slicer); // void InterpolateSurface(); /** Called internally to update the interpolation suggestion. Finds out about the focused render window and requests an interpolation. */ void UpdateVisibleSuggestion(); void SetCurrentContourListID(); private: void HideAllInterpolationControls(); void Show2DInterpolationControls(bool show); void Show3DInterpolationControls(bool show); void CheckSupportedImageDimension(); void WaitForFutures(); void NodeRemoved(const mitk::DataNode* node); mitk::SegmentationInterpolationController::Pointer m_Interpolator; mitk::SurfaceInterpolationController::Pointer m_SurfaceInterpolator; mitk::FeatureBasedEdgeDetectionFilter::Pointer m_EdgeDetector; mitk::PointCloudScoringFilter::Pointer m_PointScorer; mitk::ToolManager::Pointer m_ToolManager; bool m_Initialized; QHash m_ControllerToTimeObserverTag; QHash m_ControllerToSliceObserverTag; QHash m_ControllerToDeleteObserverTag; unsigned int InterpolationInfoChangedObserverTag; unsigned int SurfaceInterpolationInfoChangedObserverTag; QGroupBox *m_GroupBoxEnableExclusiveInterpolationMode; QComboBox *m_CmbInterpolation; QPushButton *m_BtnApply2D; QPushButton *m_BtnApplyForAllSlices2D; QPushButton *m_BtnApply3D; QPushButton *m_BtnSuggestPlane; QCheckBox *m_ChkShowPositionNodes; QPushButton *m_BtnReinit3DInterpolation; mitk::DataNode::Pointer m_FeedbackNode; mitk::DataNode::Pointer m_InterpolatedSurfaceNode; mitk::DataNode::Pointer m_3DContourNode; mitk::Image *m_Segmentation; mitk::SliceNavigationController *m_LastSNC; unsigned int m_LastSliceIndex; QHash m_TimeStep; bool m_2DInterpolationEnabled; bool m_3DInterpolationEnabled; // unsigned int m_CurrentListID; mitk::DataStorage::Pointer m_DataStorage; QFuture m_Future; QFutureWatcher m_Watcher; QTimer *m_Timer; QFuture m_PlaneFuture; QFutureWatcher m_PlaneWatcher; bool m_FirstRun; }; #endif diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h index 0fa817eaa1..4e5a99bfec 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h @@ -1,255 +1,254 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkSurfaceInterpolationController_h_Included #define mitkSurfaceInterpolationController_h_Included #include "mitkColorProperty.h" #include "mitkCommon.h" #include "mitkInteractionConst.h" #include "mitkProperties.h" #include "mitkRestorePlanePositionOperation.h" #include "mitkSurface.h" #include #include "mitkComputeContourSetNormalsFilter.h" #include "mitkCreateDistanceImageFromSurfaceFilter.h" #include "mitkReduceContourSetFilter.h" #include "mitkDataNode.h" #include "mitkDataStorage.h" -#include "mitkWeakPointer.h" #include "vtkAppendPolyData.h" #include "vtkCellArray.h" #include "vtkPoints.h" #include "vtkPolyData.h" #include "vtkPolygon.h" #include "vtkSmartPointer.h" #include "mitkImageTimeSelector.h" #include "mitkVtkRepresentationProperty.h" #include "vtkImageData.h" #include "vtkMarchingCubes.h" #include "vtkProperty.h" #include "mitkProgressBar.h" namespace mitk { class MITKSURFACEINTERPOLATION_EXPORT SurfaceInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceInterpolationController, itk::Object) itkFactorylessNewMacro(Self) itkCloneMacro(Self) itkGetMacro(DistanceImageSpacing, double) struct ContourPositionInformation { Surface::Pointer contour; Vector3D contourNormal; Point3D contourPoint; }; typedef std::vector ContourPositionInformationList; typedef std::vector ContourPositionInformationVec2D; // typedef std::map ContourListMap; typedef std::map ContourListMap; static SurfaceInterpolationController *GetInstance(); void SetCurrentTimeStep(unsigned int ts) { if (m_CurrentTimeStep != ts) { m_CurrentTimeStep = ts; if (m_SelectedSegmentation) { this->ReinitializeInterpolation(); } } }; unsigned int GetCurrentTimeStep() { return m_CurrentTimeStep; }; /** * @brief Adds a new extracted contour to the list * @param newContour the contour to be added. If a contour at that position * already exists the related contour will be updated */ void AddNewContour(Surface::Pointer newContour); /** * @brief Removes the contour for a given plane for the current selected segmenation * @param contourInfo the contour which should be removed * @return true if a contour was found and removed, false if no contour was found */ bool RemoveContour(ContourPositionInformation contourInfo); /** * @brief Adds new extracted contours to the list. If one or more contours at a given position * already exist they will be updated respectively * @param newContours the list of the contours */ void AddNewContours(std::vector newContours); /** * @brief Returns the contour for a given plane for the current selected segmenation * @param ontourInfo the contour which should be returned * @return the contour as an mitk::Surface. If no contour is available at the give position nullptr is returned */ const mitk::Surface *GetContour(ContourPositionInformation contourInfo); /** * @brief Returns the number of available contours for the current selected segmentation * @return the number of contours */ unsigned int GetNumberOfContours(); /** * Interpolates the 3D surface from the given extracted contours */ void Interpolate(); mitk::Surface::Pointer GetInterpolationResult(); /** * Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface */ void SetMinSpacing(double minSpacing); /** * Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface */ void SetMaxSpacing(double maxSpacing); /** * Sets the volume i.e. the number of pixels that the distance image should have * By evaluation we found out that 50.000 pixel delivers a good result */ void SetDistanceImageVolume(unsigned int distImageVolume); /** * @brief Get the current selected segmentation for which the interpolation is performed * @return the current segmentation image */ mitk::Image::Pointer GetCurrentSegmentation(); Surface *GetContoursAsSurface(); void SetDataStorage(DataStorage::Pointer ds); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param segmentation The current selected segmentation * \deprecatedSince{2014_03} */ DEPRECATED(void SetCurrentSegmentationInterpolationList(mitk::Image::Pointer segmentation)); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param segmentation The current selected segmentation */ void SetCurrentInterpolationSession(mitk::Image::Pointer currentSegmentationImage); /** * Removes the segmentation and all its contours from the list * @param segmentation The segmentation to be removed * \deprecatedSince{2014_03} */ DEPRECATED(void RemoveSegmentationFromContourList(mitk::Image *segmentation)); /** * @brief Remove interpolation session * @param segmentationImage the session to be removed */ void RemoveInterpolationSession(mitk::Image::Pointer segmentationImage); /** * Replaces the current interpolation session with a new one. All contours form the old * session will be applied to the new session. This only works if the two images have the * geometry * @param oldSession the session which should be replaced * @param newSession the new session which replaces the old one * @return true it the the replacement was successful, false if not (e.g. the image's geometry differs) */ bool ReplaceInterpolationSession(mitk::Image::Pointer oldSession, mitk::Image::Pointer newSession); /** * @brief Removes all sessions */ void RemoveAllInterpolationSessions(); /** * @brief Reinitializes the interpolation using the provided contour data * @param contours a mitk::Surface which contains the contours as polys in the vtkPolyData */ void ReinitializeInterpolation(mitk::Surface::Pointer contours); mitk::Image *GetImage(); /** * Estimates the memory which is needed to build up the equationsystem for the interpolation. * \returns The percentage of the real memory which will be used by the interpolation */ double EstimatePortionOfNeededMemory(); unsigned int GetNumberOfInterpolationSessions(); protected: SurfaceInterpolationController(); ~SurfaceInterpolationController(); template void GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result); private: void ReinitializeInterpolation(); void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); void AddToInterpolationPipeline(ContourPositionInformation contourInfo); ReduceContourSetFilter::Pointer m_ReduceFilter; ComputeContourSetNormalsFilter::Pointer m_NormalsFilter; CreateDistanceImageFromSurfaceFilter::Pointer m_InterpolateSurfaceFilter; Surface::Pointer m_Contours; double m_DistanceImageSpacing; vtkSmartPointer m_PolyData; mitk::DataStorage::Pointer m_DataStorage; ContourListMap m_ListOfInterpolationSessions; mitk::Surface::Pointer m_InterpolationResult; unsigned int m_CurrentNumberOfReducedContours; mitk::Image *m_SelectedSegmentation; std::map m_SegmentationObserverTags; unsigned int m_CurrentTimeStep; }; } #endif diff --git a/Plugins/org.mitk.gui.qt.diffusionimaging.partialvolume/src/internal/QmitkPartialVolumeAnalysisView.h b/Plugins/org.mitk.gui.qt.diffusionimaging.partialvolume/src/internal/QmitkPartialVolumeAnalysisView.h index 83c9dd870f..3a01e75b75 100644 --- a/Plugins/org.mitk.gui.qt.diffusionimaging.partialvolume/src/internal/QmitkPartialVolumeAnalysisView.h +++ b/Plugins/org.mitk.gui.qt.diffusionimaging.partialvolume/src/internal/QmitkPartialVolumeAnalysisView.h @@ -1,270 +1,269 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #if !defined(QmitkPartialVolumeAnalysisView_H__INCLUDED) #define QmitkPartialVolumeAnalysisView_H__INCLUDED #include "ui_QmitkPartialVolumeAnalysisViewControls.h" #include #include #include // berry #include #include // itk #include #include #include // qmitk #include "QmitkStepperAdapter.h" #include "QmitkRenderWindow.h" // mitk #include "mitkPartialVolumeAnalysisHistogramCalculator.h" #include "mitkPlanarLine.h" -#include #include "mitkDataStorageSelection.h" #include // vtk #include #include #include //#include "itkProcessObject.h" /*! \brief QmitkPartialVolumeAnalysis */ class QmitkPartialVolumeAnalysisView : public QmitkAbstractView, public mitk::IZombieViewPart//, public itk::ProcessObject { Q_OBJECT public: /*! \ Convenient typedefs */ typedef mitk::DataStorage::SetOfObjects ConstVector; typedef ConstVector::ConstPointer ConstVectorPointer; typedef ConstVector::ConstIterator ConstVectorIterator; typedef mitk::PartialVolumeAnalysisHistogramCalculator HistogramCalculatorType; typedef HistogramCalculatorType::HistogramType HistogramType; typedef mitk::PartialVolumeAnalysisClusteringCalculator ClusteringType; typedef itk::DiffusionTensorPrincipalDirectionImageFilter DirectionsFilterType; typedef itk::Image ItkUcharImgType; /*! \brief default constructor */ QmitkPartialVolumeAnalysisView(QObject *parent=0, const char *name=0); /*! \brief default destructor */ virtual ~QmitkPartialVolumeAnalysisView(); /*! \brief method for creating the widget containing the application controls, like sliders, buttons etc. */ virtual void CreateQtPartControl(QWidget *parent) override; /*! \brief method for creating the connections of main and control widget */ virtual void CreateConnections(); virtual bool event( QEvent *event ) override; virtual void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList& nodes) override; virtual void Activated() override; virtual void Deactivated() override; virtual void ActivatedZombieView(berry::IWorkbenchPartReference::Pointer reference) override; virtual void Hidden() override; virtual void Visible() override; virtual void SetFocus() override; bool AssertDrawingIsPossible(bool checked); virtual void NodeChanged(const mitk::DataNode* node) override; virtual void PropertyChanged(const mitk::DataNode* node, const mitk::BaseProperty* prop); virtual void NodeRemoved(const mitk::DataNode* node) override; virtual void NodeAddedInDataStorage(const mitk::DataNode* node); virtual void AddFigureToDataStorage(mitk::PlanarFigure* figure, const QString& name, const char *propertyKey = nullptr, mitk::BaseProperty *property = nullptr ); void PlanarFigureInitialized(); void PlanarFigureFocus(mitk::DataNode* node); void ShowClusteringResults(); static const std::string VIEW_ID; protected slots: void EstimateCircle(); void SetHistogramVisibility(); void SetAdvancedVisibility(); void NumberBinsChangedSlider(int v ); void UpsamplingChangedSlider( int v ); void GaussianSigmaChangedSlider( int v ); void SimilarAnglesChangedSlider(int v ); void OpacityChangedSlider(int v ); void NumberBinsReleasedSlider( ); void UpsamplingReleasedSlider( ); void GaussianSigmaReleasedSlider( ); void SimilarAnglesReleasedSlider( ); void ActionDrawEllipseTriggered(); void ActionDrawRectangleTriggered(); void ActionDrawPolygonTriggered(); void ToClipBoard(); void GreenRadio(bool checked); void PartialVolumeRadio(bool checked); void BlueRadio(bool checked); void AllRadio(bool checked); void OnRenderWindowDelete(QObject * obj); void TextIntON(); void ExportClusteringResults(); protected: /** \brief Issues a request to update statistics by sending an event to the * Qt event processing queue. * * Statistics update should only be executed after program execution returns * to the Qt main loop. This mechanism also prevents multiple execution of * updates where only one is required.*/ void RequestStatisticsUpdate(); /** \brief Recalculate statistics for currently selected image and mask and * update the GUI. */ void UpdateStatistics(); /** \brief Listener for progress events to update progress bar. */ void UpdateProgressBar(); /** \brief Removes any cached images which are no longer referenced elsewhere. */ void RemoveOrphanImages(); void Select( mitk::DataNode::Pointer node, bool clearMaskOnFirstArgnullptr=false, bool clearImageOnFirstArgnullptr=false ); void SetMeasurementInfoToRenderWindow(const QString& text); void FindRenderWindow(mitk::DataNode* node); void ExtractTensorImages( mitk::Image::Pointer tensorimage); typedef std::map< mitk::Image *, mitk::PartialVolumeAnalysisHistogramCalculator::Pointer > PartialVolumeAnalysisMapType; /*! * controls containing sliders for scrolling through the slices */ Ui::QmitkPartialVolumeAnalysisViewControls *m_Controls; QmitkStepperAdapter* m_TimeStepperAdapter; unsigned int m_CurrentTime; QString m_Clipboard; // result text rendering vtkRenderer * m_MeasurementInfoRenderer; vtkCornerAnnotation *m_MeasurementInfoAnnotation; // Image and mask data mitk::DataStorageSelection::Pointer m_SelectedImageNodes; mitk::Image::Pointer m_SelectedImage; mitk::DataNode::Pointer m_SelectedMaskNode; mitk::Image::Pointer m_SelectedImageMask; mitk::DataStorageSelection::Pointer m_SelectedPlanarFigureNodes; mitk::PlanarFigure::Pointer m_SelectedPlanarFigure; bool m_IsTensorImage; mitk::Image::Pointer m_FAImage; mitk::Image::Pointer m_CAImage; mitk::Image::Pointer m_RDImage; mitk::Image::Pointer m_ADImage; mitk::Image::Pointer m_MDImage; mitk::Image::Pointer m_DirectionComp1Image; mitk::Image::Pointer m_DirectionComp2Image; mitk::Image::Pointer m_AngularErrorImage; QmitkRenderWindow* m_SelectedRenderWindow; QmitkRenderWindow* m_LastRenderWindow; long m_ImageObserverTag; long m_ImageMaskObserverTag; long m_PlanarFigureObserverTag; // Hash map for associating one image statistics calculator with each iamge // (so that previously calculated histograms / statistics can be recovered // if a recalculation is not required) PartialVolumeAnalysisMapType m_PartialVolumeAnalysisMap; HistogramCalculatorType::Pointer m_CurrentStatisticsCalculator; bool m_CurrentStatisticsValid; bool m_StatisticsUpdatePending; bool m_GaussianSigmaChangedSliding; bool m_NumberBinsSliding; bool m_UpsamplingChangedSliding; mitk::DataNode::Pointer m_ClusteringResult; int m_EllipseCounter; int m_RectangleCounter; int m_PolygonCounter; long m_InitializedObserverTag; bool m_CurrentFigureNodeInitialized; int m_QuantifyClass; ClusteringType::HelperStructPerformRGBClusteringRetval* m_CurrentRGBClusteringResults; ClusteringType::HelperStructPerformClusteringRetval *m_CurrentPerformClusteringResults; QIcon* m_IconTexOFF; QIcon* m_IconTexON; bool m_TexIsOn; bool m_Visible; }; #endif diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropper.cpp b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropper.cpp index c2d556beba..c46bb828bd 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropper.cpp +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropper.cpp @@ -1,604 +1,606 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "QmitkImageCropper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Includes for image casting between ITK and MITK: added after using Plugin Generator #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkImageCropper::VIEW_ID = "org.mitk.views.qmitkimagecropper"; QmitkImageCropper::QmitkImageCropper(QObject *) : m_ParentWidget(0), m_ImageNode(nullptr), m_CroppingObject(nullptr), m_CroppingObjectNode(nullptr), m_BoundingShapeInteractor(nullptr), m_CropOutsideValue(0), m_Advanced(0), m_Active(0), m_ScrollEnabled(true) { CreateBoundingShapeInteractor(false); } QmitkImageCropper::~QmitkImageCropper() { //delete pointer objects m_CroppingObjectNode = nullptr; m_CroppingObject = nullptr; //disable interactor if (m_BoundingShapeInteractor != nullptr) { m_BoundingShapeInteractor->SetDataNode(nullptr); m_BoundingShapeInteractor->EnableInteraction(false); } } void QmitkImageCropper::SetFocus() { m_Controls.buttonCreateNewBoundingBox->setFocus(); } void QmitkImageCropper::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.boundingShapeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.boundingShapeSelector->SetPredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_CroppingObjectNode = m_Controls.boundingShapeSelector->GetSelectedNode(); connect(m_Controls.buttonCropping, SIGNAL(clicked()), this, SLOT(DoCropping())); connect(m_Controls.buttonMasking, SIGNAL(clicked()), this, SLOT(DoMasking())); connect(m_Controls.boundingShapeSelector, SIGNAL(OnSelectionChanged(const mitk::DataNode*)), this, SLOT(OnDataSelectionChanged(const mitk::DataNode*))); connect(m_Controls.buttonCreateNewBoundingBox, SIGNAL(clicked()), this, SLOT(DoCreateNewBoundingObject())); connect(m_Controls.buttonAdvancedSettings, SIGNAL(clicked()), this, SLOT(OnAdvancedSettingsButtonToggled())); connect(m_Controls.spinBoxOutsidePixelValue, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); setDefaultGUI(); m_Controls.labelWarningRotation->setVisible(false); m_BoundingObjectNames.clear(); m_Advanced = false; this->OnAdvancedSettingsButtonToggled(); m_ParentWidget = parent; } void QmitkImageCropper::OnDataSelectionChanged(const mitk::DataNode*) { m_Controls.boundingShapeSelector->setEnabled(true); m_CroppingObjectNode = m_Controls.boundingShapeSelector->GetSelectedNode(); if (m_CroppingObjectNode.IsNotNull() && dynamic_cast(this->m_CroppingObjectNode->GetData())) { m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.labelWarningBB->setVisible(false); m_CroppingObject = dynamic_cast(m_CroppingObjectNode->GetData()); m_Advanced = true; mitk::RenderingManager::GetInstance()->InitializeViews(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { setDefaultGUI(); m_CroppingObject = nullptr; m_BoundingShapeInteractor->EnableInteraction(false); m_BoundingShapeInteractor->SetDataNode(nullptr); m_Advanced = false; this->OnAdvancedSettingsButtonToggled(); } } void QmitkImageCropper::OnAdvancedSettingsButtonToggled() { m_Controls.groupImageSettings->setVisible(m_Advanced); m_Advanced = !m_Advanced; } void QmitkImageCropper::CreateBoundingShapeInteractor(bool rotationEnabled) { if (m_BoundingShapeInteractor.IsNull()) { m_BoundingShapeInteractor = mitk::BoundingShapeInteractor::New(); m_BoundingShapeInteractor->LoadStateMachine("BoundingShapeInteraction.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); m_BoundingShapeInteractor->SetEventConfig("BoundingShapeMouseConfig.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); } m_BoundingShapeInteractor->SetRotationEnabled(rotationEnabled); } mitk::Geometry3D::Pointer QmitkImageCropper::InitializeWithImageGeometry(mitk::BaseGeometry::Pointer geometry) { // convert a basegeometry into a Geometry3D (otherwise IO is not working properly) if (geometry == nullptr) mitkThrow() << "Geometry is not valid."; auto boundingGeometry = mitk::Geometry3D::New(); boundingGeometry->SetBounds(geometry->GetBounds()); boundingGeometry->SetImageGeometry(geometry->GetImageGeometry()); boundingGeometry->SetOrigin(geometry->GetOrigin()); boundingGeometry->SetSpacing(geometry->GetSpacing()); boundingGeometry->SetIndexToWorldTransform(geometry->GetIndexToWorldTransform()); boundingGeometry->Modified(); return boundingGeometry; } QString QmitkImageCropper::getAdaptedBoundingObjectName(const QString& name) const { bool nameNotTaken = false; unsigned int counter = 2; QString newName; while (!nameNotTaken) { newName = name + "_" + QString::number(counter); if (!m_BoundingObjectNames.contains(newName)) { nameNotTaken = true; } ++counter; } return newName; } void QmitkImageCropper::DoCreateNewBoundingObject() { - if (m_ImageNode.IsNotNull()) + if (!m_ImageNode.IsExpired()) { + auto imageNode = m_ImageNode.Lock(); + bool ok = false; QString defaultName = "BoundingShape"; if (m_BoundingObjectNames.contains(defaultName)) { defaultName = getAdaptedBoundingObjectName(defaultName); } QString name = QInputDialog::getText(QApplication::activeWindow() , "Add cropping shape...", "Enter name for the new cropping shape", QLineEdit::Normal, defaultName, &ok); if (!ok) return; if (name == "") { name = "Bounding Shape"; } if (m_BoundingObjectNames.contains(name)) { name = getAdaptedBoundingObjectName(name); QMessageBox::information(nullptr, "Information", "Bounding object name already exists. It was changed to: " + name); } m_BoundingObjectNames.append(name); m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.boundingShapeSelector->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); // get current timestep to support 3d+t images auto renderWindowPart = this->GetRenderWindowPart(OPEN); int timeStep = renderWindowPart->GetTimeNavigationController()->GetTime()->GetPos(); - mitk::BaseGeometry::Pointer imageGeometry = static_cast(m_ImageNode->GetData()->GetGeometry(timeStep)); + mitk::BaseGeometry::Pointer imageGeometry = static_cast(imageNode->GetData()->GetGeometry(timeStep)); m_CroppingObject = mitk::GeometryData::New(); m_CroppingObject->SetGeometry(static_cast(this->InitializeWithImageGeometry(imageGeometry))); m_CroppingObjectNode = mitk::DataNode::New(); m_CroppingObjectNode->SetData(m_CroppingObject); m_CroppingObjectNode->SetProperty("name", mitk::StringProperty::New(name.toStdString())); m_CroppingObjectNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); m_CroppingObjectNode->SetProperty("opacity", mitk::FloatProperty::New(0.6)); m_CroppingObjectNode->SetProperty("layer", mitk::IntProperty::New(99)); m_CroppingObjectNode->AddProperty("handle size factor", mitk::DoubleProperty::New(1.0 / 40.0)); m_CroppingObjectNode->SetBoolProperty("pickable", true); if (!this->GetDataStorage()->Exists(m_CroppingObjectNode)) { - GetDataStorage()->Add(m_CroppingObjectNode, m_ImageNode); + GetDataStorage()->Add(m_CroppingObjectNode, imageNode); m_Controls.boundingShapeSelector->SetSelectedNode(m_CroppingObjectNode); m_CroppingObjectNode->SetVisibility(true); m_BoundingShapeInteractor->EnableInteraction(true); m_BoundingShapeInteractor->SetDataNode(this->m_CroppingObjectNode); this->OnDataSelectionChanged(m_CroppingObjectNode); } } // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, m_CroppingObjectNode); //// initialize the views to the bounding geometry //mitk::TimeGeometry::Pointer bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); //mitk::RenderingManager::GetInstance()->InitializeViews(bounds); //mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkImageCropper::setDefaultGUI() { m_Controls.labelWarningImage->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningImage->setText(QString::fromStdString("Select an image.")); m_Controls.labelWarningImage->setVisible(true); m_Controls.labelWarningBB->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningBB->setText(QString::fromStdString("Create a bounding shape below.")); m_Controls.labelWarningBB->setVisible(true); m_Controls.buttonCreateNewBoundingBox->setEnabled(false); m_Controls.labelWarningRotation->setVisible(false); m_Controls.buttonCropping->setEnabled(false); m_Controls.buttonMasking->setEnabled(false); m_Controls.boundingShapeSelector->setEnabled(false); m_Controls.buttonAdvancedSettings->setEnabled(false); m_Controls.groupImageSettings->setEnabled(false); m_Controls.checkOverwriteImage->setChecked(false); m_Controls.checkBoxCropTimeStepOnly->setChecked(false); } void QmitkImageCropper::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { bool rotationEnabled = false; if (nodes.empty()) { setDefaultGUI(); return; } m_ParentWidget->setEnabled(true); foreach(mitk::DataNode::Pointer node, nodes) { if (node.IsNotNull() && dynamic_cast(node->GetData())) { m_ImageNode = nodes[0]; m_Controls.groupBoundingObject->setEnabled(true); m_Controls.labelWarningImage->setStyleSheet(" QLabel { color: rgb(0, 0, 0) }"); - m_Controls.labelWarningImage->setText(QString::fromStdString("File name: " + m_ImageNode->GetName())); + m_Controls.labelWarningImage->setText(QString::fromStdString("File name: " + nodes[0]->GetName())); m_Controls.buttonCreateNewBoundingBox->setEnabled(true); - mitk::Image::Pointer image = dynamic_cast(m_ImageNode->GetData()); + mitk::Image::Pointer image = dynamic_cast(nodes[0]->GetData()); if (image != nullptr) { if (image->GetDimension() < 3) { QMessageBox::warning(nullptr, tr("Invalid image selected"), tr("ImageCropper only works with 3 or more dimensions."), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); setDefaultGUI(); return; } vtkSmartPointer imageMat = image->GetGeometry()->GetVtkMatrix(); // check whether the image geometry is rotated, if so, no pixel aligned cropping or masking can be performed if ((imageMat->GetElement(1, 0) == 0.0) && (imageMat->GetElement(0, 1) == 0.0) && (imageMat->GetElement(1, 2) == 0.0) && (imageMat->GetElement(2, 1) == 0.0) && (imageMat->GetElement(2, 0) == 0.0) && (imageMat->GetElement(0, 2) == 0.0)) { rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); } else { rotationEnabled = true; m_Controls.labelWarningRotation->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningRotation->setVisible(true); } this->CreateBoundingShapeInteractor(rotationEnabled); m_CroppingObjectNode = m_Controls.boundingShapeSelector->GetSelectedNode(); if (m_CroppingObjectNode != nullptr) { this->OnDataSelectionChanged(m_CroppingObjectNode); m_BoundingShapeInteractor->EnableInteraction(true); m_BoundingShapeInteractor->SetDataNode(this->m_CroppingObjectNode); m_Controls.boundingShapeSelector->setEnabled(true); } if (image->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) { // Might be changed with the upcoming new image statistics plugin //(recomputation might be very expensive for large images ;) ) auto statistics = image->GetStatistics(); auto minPixelValue = statistics->GetScalarValueMin(); auto maxPixelValue = statistics->GetScalarValueMax(); if (minPixelValue < std::numeric_limits::min()) { minPixelValue = std::numeric_limits::min(); } if (maxPixelValue > std::numeric_limits::max()) { maxPixelValue = std::numeric_limits::max(); } m_Controls.spinBoxOutsidePixelValue->setEnabled(true); m_Controls.spinBoxOutsidePixelValue->setMaximum(static_cast(maxPixelValue)); m_Controls.spinBoxOutsidePixelValue->setMinimum(static_cast(minPixelValue)); m_Controls.spinBoxOutsidePixelValue->setValue(static_cast(minPixelValue)); } else { m_Controls.spinBoxOutsidePixelValue->setEnabled(false); } unsigned int dim = image->GetDimension(); if (dim < 2 || dim > 4) { m_Controls.labelWarningImage->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningImage->setText(QString::fromStdString("Select an image.")); m_ParentWidget->setEnabled(false); } if (m_CroppingObjectNode != nullptr) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.boundingShapeSelector->setEnabled(true); m_Controls.labelWarningBB->setVisible(false); } else { m_Controls.buttonCropping->setEnabled(false); m_Controls.buttonMasking->setEnabled(false); m_Controls.boundingShapeSelector->setEnabled(false); m_Controls.labelWarningBB->setVisible(true); } return; } // iterate all selected objects, adjust warning visibility setDefaultGUI(); m_ParentWidget->setEnabled(true); m_Controls.labelWarningRotation->setVisible(false); } } } void QmitkImageCropper::NodeRemoved(const mitk::DataNode * node) { QList::iterator it = m_BoundingObjectNames.begin(); while (it != m_BoundingObjectNames.end()) { if (node->GetName() == it->toStdString()) { it = m_BoundingObjectNames.erase(it); } else { ++it; } } } void QmitkImageCropper::OnComboBoxSelectionChanged(const mitk::DataNode* node) { mitk::DataNode* selectedNode = const_cast(node); if (selectedNode != nullptr) { - if (m_ImageNode.IsNotNull()) - selectedNode->SetDataInteractor(m_ImageNode->GetDataInteractor()); + if (!m_ImageNode.IsExpired()) + selectedNode->SetDataInteractor(m_ImageNode.Lock()->GetDataInteractor()); // m_ImageNode->GetDataInteractor()->SetDataNode(selectedNode); m_ImageNode = selectedNode; } } void QmitkImageCropper::OnSliderValueChanged(int slidervalue) { m_CropOutsideValue = slidervalue; } void QmitkImageCropper::DoMasking() { this->ProcessImage(true); } void QmitkImageCropper::DoCropping() { this->ProcessImage(false); } void QmitkImageCropper::ProcessImage(bool mask) { // cropping only possible if valid bounding shape as well as a valid image are loaded QList nodes = this->GetDataManagerSelection(); auto renderWindowPart = this->GetRenderWindowPart(OPEN); int timeStep = renderWindowPart->GetTimeNavigationController()->GetTime()->GetPos(); if (nodes.empty()) return; mitk::DataNode* node = nodes[0]; if (node == nullptr) { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); return; } if (m_CroppingObject == nullptr) { QMessageBox::information(nullptr, "Warning", "Please load and select a cropping object before starting image processing."); return; } mitk::BaseData* data = node->GetData(); //get data from node if (data != nullptr) { QString imageName; if (mask) { imageName = QString::fromStdString(node->GetName() + "_" + m_CroppingObjectNode->GetName() + "_masked"); } else { imageName = QString::fromStdString(node->GetName() + "_" + m_CroppingObjectNode->GetName() + "_cropped"); } if (m_Controls.checkBoxCropTimeStepOnly->isChecked()) { imageName = imageName + "_T" + QString::number(timeStep); } // image and bounding shape ok, set as input auto croppedImageNode = mitk::DataNode::New(); auto cutter = mitk::BoundingShapeCropper::New(); cutter->SetGeometry(m_CroppingObject); // adjustable in advanced settings cutter->SetUseWholeInputRegion(mask); //either mask (mask=true) or crop (mask=false) cutter->SetOutsideValue(m_CropOutsideValue); cutter->SetUseCropTimeStepOnly(m_Controls.checkBoxCropTimeStepOnly->isChecked()); cutter->SetCurrentTimeStep(timeStep); // TODO: Add support for MultiLayer (right now only Mulitlabel support) mitk::LabelSetImage* labelsetImageInput = dynamic_cast(data); if (labelsetImageInput != nullptr) { cutter->SetInput(labelsetImageInput); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } auto labelSetImage = mitk::LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(cutter->GetOutput()); for (unsigned int i = 0; i < labelsetImageInput->GetNumberOfLayers(); i++) { labelSetImage->AddLabelSetToLayer(i, labelsetImageInput->GetLabelSet(i)); } croppedImageNode->SetData(labelSetImage); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { if (!this->GetDataStorage()->Exists(croppedImageNode)) { - this->GetDataStorage()->Add(croppedImageNode, m_ImageNode); + this->GetDataStorage()->Add(croppedImageNode, m_ImageNode.Lock()); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { node->SetData(labelSetImage); node->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, node); // initialize the views to the bounding geometry mitk::TimeGeometry::Pointer bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } else { mitk::Image::Pointer imageInput = dynamic_cast(data); if (imageInput != nullptr) { cutter->SetInput(imageInput); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { croppedImageNode->SetData(cutter->GetOutput()); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); croppedImageNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); croppedImageNode->SetProperty("layer", mitk::IntProperty::New(99)); // arbitrary, copied from segmentation functionality if (!this->GetDataStorage()->Exists(croppedImageNode)) { - this->GetDataStorage()->Add(croppedImageNode, m_ImageNode); + this->GetDataStorage()->Add(croppedImageNode, m_ImageNode.Lock()); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { node->SetData(cutter->GetOutput()); node->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, node); // initialize the views to the bounding geometry mitk::TimeGeometry::Pointer bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } } else { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); } } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp index d2f24644fa..07c9912d54 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp @@ -1,923 +1,922 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkMeasurementView.h" #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mitkPluginActivator.h" #include "usModuleRegistry.h" #include "mitkInteractionEventObserver.h" #include "mitkDisplayInteractor.h" #include "usGetModuleContext.h" #include "usModuleContext.h" #include US_INITIALIZE_MODULE struct QmitkPlanarFigureData { QmitkPlanarFigureData() : m_EndPlacementObserverTag(0), m_SelectObserverTag(0), m_StartInteractionObserverTag(0), m_EndInteractionObserverTag(0) { } mitk::PlanarFigure::Pointer m_Figure; unsigned int m_EndPlacementObserverTag; unsigned int m_SelectObserverTag; unsigned int m_StartInteractionObserverTag; unsigned int m_EndInteractionObserverTag; }; struct QmitkMeasurementViewData { QmitkMeasurementViewData() : m_LineCounter(0), m_PathCounter(0), m_AngleCounter(0), m_FourPointAngleCounter(0), m_CircleCounter(0), m_EllipseCounter(0), m_DoubleEllipseCounter(0), m_RectangleCounter(0), m_PolygonCounter(0), m_BezierCurveCounter(0), m_SubdivisionPolygonCounter(0), m_UnintializedPlanarFigure(false), m_ScrollEnabled(true), m_Parent(nullptr), m_SelectedImageLabel(nullptr), m_DrawLine(nullptr), m_DrawPath(nullptr), m_DrawAngle(nullptr), m_DrawFourPointAngle(nullptr), m_DrawRectangle(nullptr), m_DrawPolygon(nullptr), m_DrawCircle(nullptr), m_DrawEllipse(nullptr), m_DrawDoubleEllipse(nullptr), m_DrawBezierCurve(nullptr), m_DrawSubdivisionPolygon(nullptr), m_DrawActionsToolBar(nullptr), m_DrawActionsGroup(nullptr), m_SelectedPlanarFiguresText(nullptr), m_CopyToClipboard(nullptr), m_Layout(nullptr) { } unsigned int m_LineCounter; unsigned int m_PathCounter; unsigned int m_AngleCounter; unsigned int m_FourPointAngleCounter; unsigned int m_CircleCounter; unsigned int m_EllipseCounter; unsigned int m_DoubleEllipseCounter; unsigned int m_RectangleCounter; unsigned int m_PolygonCounter; unsigned int m_BezierCurveCounter; unsigned int m_SubdivisionPolygonCounter; QList m_CurrentSelection; std::map m_DataNodeToPlanarFigureData; mitk::DataNode::Pointer m_SelectedImageNode; bool m_UnintializedPlanarFigure; bool m_ScrollEnabled; QWidget* m_Parent; QLabel* m_SelectedImageLabel; QAction* m_DrawLine; QAction* m_DrawPath; QAction* m_DrawAngle; QAction* m_DrawFourPointAngle; QAction* m_DrawRectangle; QAction* m_DrawPolygon; QAction* m_DrawCircle; QAction* m_DrawEllipse; QAction* m_DrawDoubleEllipse; QAction* m_DrawBezierCurve; QAction* m_DrawSubdivisionPolygon; QToolBar* m_DrawActionsToolBar; QActionGroup* m_DrawActionsGroup; QTextBrowser* m_SelectedPlanarFiguresText; QPushButton* m_CopyToClipboard; QGridLayout* m_Layout; }; const std::string QmitkMeasurementView::VIEW_ID = "org.mitk.views.measurement"; QmitkMeasurementView::QmitkMeasurementView() : d(new QmitkMeasurementViewData) { } QmitkMeasurementView::~QmitkMeasurementView() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeRemoved(it.Value()); delete d; } void QmitkMeasurementView::CreateQtPartControl(QWidget* parent) { d->m_Parent = parent; auto selectedImageLabel = new QLabel(tr("Reference Image: ")); d->m_SelectedImageLabel = new QLabel; d->m_SelectedImageLabel->setStyleSheet("font-weight: bold;"); d->m_DrawActionsToolBar = new QToolBar; d->m_DrawActionsGroup = new QActionGroup(this); d->m_DrawActionsGroup->setExclusive(true); auto* currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/line.png"), tr("Draw Line")); currentAction->setCheckable(true); d->m_DrawLine = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/path.png"), tr("Draw Path")); currentAction->setCheckable(true); d->m_DrawPath = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/angle.png"), tr("Draw Angle")); currentAction->setCheckable(true); d->m_DrawAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/four-point-angle.png"), tr("Draw Four Point Angle")); currentAction->setCheckable(true); d->m_DrawFourPointAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/circle.png"), tr("Draw Circle")); currentAction->setCheckable(true); d->m_DrawCircle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/ellipse.png"), tr("Draw Ellipse")); currentAction->setCheckable(true); d->m_DrawEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/doubleellipse.png"), tr("Draw Double Ellipse")); currentAction->setCheckable(true); d->m_DrawDoubleEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/rectangle.png"), tr("Draw Rectangle")); currentAction->setCheckable(true); d->m_DrawRectangle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/polygon.png"), tr("Draw Polygon")); currentAction->setCheckable(true); d->m_DrawPolygon = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/beziercurve.png"), tr("Draw Bezier Curve")); currentAction->setCheckable(true); d->m_DrawBezierCurve = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/subdivisionpolygon.png"), tr("Draw Subdivision Polygon")); currentAction->setCheckable(true); d->m_DrawSubdivisionPolygon = currentAction; // planar figure details text d->m_SelectedPlanarFiguresText = new QTextBrowser; // copy to clipboard button d->m_CopyToClipboard = new QPushButton(tr("Copy to Clipboard")); d->m_Layout = new QGridLayout; d->m_Layout->addWidget(selectedImageLabel, 0, 0, 1, 1); d->m_Layout->addWidget(d->m_SelectedImageLabel, 0, 1, 1, 1); d->m_Layout->addWidget(d->m_DrawActionsToolBar, 1, 0, 1, 2); d->m_Layout->addWidget(d->m_SelectedPlanarFiguresText, 2, 0, 1, 2); d->m_Layout->addWidget(d->m_CopyToClipboard, 3, 0, 1, 2); d->m_Parent->setLayout(d->m_Layout); this->CreateConnections(); this->AddAllInteractors(); } void QmitkMeasurementView::CreateConnections() { connect(d->m_DrawLine, SIGNAL(triggered(bool)), this, SLOT(OnDrawLineTriggered(bool))); connect(d->m_DrawPath, SIGNAL(triggered(bool)), this, SLOT(OnDrawPathTriggered(bool))); connect(d->m_DrawAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawAngleTriggered(bool))); connect(d->m_DrawFourPointAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawFourPointAngleTriggered(bool))); connect(d->m_DrawCircle, SIGNAL(triggered(bool)), this, SLOT(OnDrawCircleTriggered(bool))); connect(d->m_DrawEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawEllipseTriggered(bool))); connect(d->m_DrawDoubleEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawDoubleEllipseTriggered(bool))); connect(d->m_DrawRectangle, SIGNAL(triggered(bool)), this, SLOT(OnDrawRectangleTriggered(bool))); connect(d->m_DrawPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawPolygonTriggered(bool))); connect(d->m_DrawBezierCurve, SIGNAL(triggered(bool)), this, SLOT(OnDrawBezierCurveTriggered(bool))); connect(d->m_DrawSubdivisionPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawSubdivisionPolygonTriggered(bool))); connect(d->m_CopyToClipboard, SIGNAL(clicked(bool)), this, SLOT(OnCopyToClipboard(bool))); } void QmitkMeasurementView::NodeAdded(const mitk::DataNode* node) { // add observer for selection in renderwindow mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); auto isPositionMarker = false; node->GetBoolProperty("isContourMarker", isPositionMarker); if (planarFigure.IsNotNull() && !isPositionMarker) { auto nonConstNode = const_cast(node); mitk::PlanarFigureInteractor::Pointer interactor = dynamic_cast(node->GetDataInteractor().GetPointer()); if (interactor.IsNull()) { interactor = mitk::PlanarFigureInteractor::New(); auto planarFigureModule = us::ModuleRegistry::GetModule("MitkPlanarFigure"); interactor->LoadStateMachine("PlanarFigureInteraction.xml", planarFigureModule); interactor->SetEventConfig("PlanarFigureConfig.xml", planarFigureModule); } interactor->SetDataNode(nonConstNode); QmitkPlanarFigureData data; data.m_Figure = planarFigure; typedef itk::SimpleMemberCommand SimpleCommandType; typedef itk::MemberCommand MemberCommandType; // add observer for event when figure has been placed auto initializationCommand = SimpleCommandType::New(); initializationCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureInitialized); data.m_EndPlacementObserverTag = planarFigure->AddObserver(mitk::EndPlacementPlanarFigureEvent(), initializationCommand); // add observer for event when figure is picked (selected) auto selectCommand = MemberCommandType::New(); selectCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureSelected); data.m_SelectObserverTag = planarFigure->AddObserver(mitk::SelectPlanarFigureEvent(), selectCommand); // add observer for event when interaction with figure starts auto startInteractionCommand = SimpleCommandType::New(); startInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::DisableCrosshairNavigation); data.m_StartInteractionObserverTag = planarFigure->AddObserver(mitk::StartInteractionPlanarFigureEvent(), startInteractionCommand); // add observer for event when interaction with figure starts auto endInteractionCommand = SimpleCommandType::New(); endInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::EnableCrosshairNavigation); data.m_EndInteractionObserverTag = planarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), endInteractionCommand); // adding to the map of tracked planarfigures d->m_DataNodeToPlanarFigureData[nonConstNode] = data; } this->CheckForTopMostVisibleImage(); } void QmitkMeasurementView::NodeChanged(const mitk::DataNode* node) { // DETERMINE IF WE HAVE TO RENEW OUR DETAILS TEXT (ANY NODE CHANGED IN OUR SELECTION?) auto renewText = false; for (int i = 0; i < d->m_CurrentSelection.size(); ++i) { if (node == d->m_CurrentSelection[i]) { renewText = true; break; } } if (renewText) this->UpdateMeasurementText(); this->CheckForTopMostVisibleImage(); } void QmitkMeasurementView::CheckForTopMostVisibleImage(mitk::DataNode* nodeToNeglect) { d->m_SelectedImageNode = this->DetectTopMostVisibleImage(); if (d->m_SelectedImageNode.GetPointer() == nodeToNeglect) d->m_SelectedImageNode = nullptr; auto isImage = mitk::TNodePredicateDataType::New(); auto isHelpherObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(true)); auto isNotHelpherObject = mitk::NodePredicateNot::New(isHelpherObject); auto nodeElements = this->GetDataStorage()->GetSubset(isNotHelpherObject); if (d->m_SelectedImageNode.IsNotNull() && d->m_UnintializedPlanarFigure == false) { d->m_SelectedImageLabel->setText(QString::fromStdString(d->m_SelectedImageNode->GetName())); d->m_DrawActionsToolBar->setEnabled(true); } else if (d->m_UnintializedPlanarFigure == false && nodeElements->size() != 0) { d->m_SelectedImageLabel->setText(tr("Working without an image...")); d->m_DrawActionsToolBar->setEnabled(true); } else { if (d->m_UnintializedPlanarFigure == false) d->m_SelectedImageLabel->setText(tr("No visible data available.")); d->m_DrawActionsToolBar->setEnabled(false); } } void QmitkMeasurementView::NodeRemoved(const mitk::DataNode* node) { auto nonConstNode = const_cast(node); auto it = d->m_DataNodeToPlanarFigureData.find(nonConstNode); auto isFigureFinished = false; auto isPlaced = false; if (it != d->m_DataNodeToPlanarFigureData.end()) { QmitkPlanarFigureData& data = it->second; data.m_Figure->RemoveObserver(data.m_EndPlacementObserverTag); data.m_Figure->RemoveObserver(data.m_SelectObserverTag); data.m_Figure->RemoveObserver(data.m_StartInteractionObserverTag); data.m_Figure->RemoveObserver(data.m_EndInteractionObserverTag); isFigureFinished = data.m_Figure->GetPropertyList()->GetBoolProperty("initiallyplaced", isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons d->m_DataNodeToPlanarFigureData.erase( it ); } if (nonConstNode != nullptr) nonConstNode->SetDataInteractor(nullptr); auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto nodes = this->GetDataStorage()->GetDerivations(node, isPlanarFigure); for (unsigned int x = 0; x < nodes->size(); ++x) { mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(nodes->at(x)->GetData()); if (planarFigure.IsNotNull()) { isFigureFinished = planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced",isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode { this->GetDataStorage()->Remove(nodes->at(x)); if (!d->m_DataNodeToPlanarFigureData.empty()) { it = d->m_DataNodeToPlanarFigureData.find(nodes->at(x)); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_DataNodeToPlanarFigureData.erase(it); this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons this->EnableCrosshairNavigation(); } } } } } this->CheckForTopMostVisibleImage(nonConstNode); } void QmitkMeasurementView::PlanarFigureSelected(itk::Object* object, const itk::EventObject&) { d->m_CurrentSelection.clear(); auto it = d->m_DataNodeToPlanarFigureData.begin(); while (it != d->m_DataNodeToPlanarFigureData.end()) { auto node = it->first; QmitkPlanarFigureData& data = it->second; if (data.m_Figure == object ) { node->SetSelected(true); d->m_CurrentSelection.push_back( node ); } else { node->SetSelected(false); } ++it; } this->UpdateMeasurementText(); this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::PlanarFigureInitialized() { d->m_UnintializedPlanarFigure = false; d->m_DrawActionsToolBar->setEnabled(true); d->m_DrawLine->setChecked(false); d->m_DrawPath->setChecked(false); d->m_DrawAngle->setChecked(false); d->m_DrawFourPointAngle->setChecked(false); d->m_DrawCircle->setChecked(false); d->m_DrawEllipse->setChecked(false); d->m_DrawDoubleEllipse->setChecked(false); d->m_DrawRectangle->setChecked(false); d->m_DrawPolygon->setChecked(false); d->m_DrawBezierCurve->setChecked(false); d->m_DrawSubdivisionPolygon->setChecked(false); } void QmitkMeasurementView::SetFocus() { d->m_SelectedImageLabel->setFocus(); } void QmitkMeasurementView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) { this->CheckForTopMostVisibleImage(); d->m_CurrentSelection = nodes; this->UpdateMeasurementText(); // bug 16600: deselecting all planarfigures by clicking on datamanager when no node is selected if (d->m_CurrentSelection.size() == 0) { // bug 18440: resetting the selected image label here because unselecting the // current node did not reset the label d->m_SelectedImageLabel->setText(tr("No visible image available.")); auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto planarFigures = this->GetDataStorage()->GetSubset(isPlanarFigure); // setting all planar figures which are not helper objects not selected for (mitk::DataStorage::SetOfObjects::ConstIterator it = planarFigures->Begin(); it != planarFigures->End(); ++it) { auto node = it.Value(); auto isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (!isHelperObject) node->SetSelected(false); } } for (int i = d->m_CurrentSelection.size() - 1; i >= 0; --i) { auto node = d->m_CurrentSelection[i]; mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); // the last selected planar figure if (planarFigure.IsNotNull() && planarFigure->GetPlaneGeometry()) { auto planarFigureInitializedWindow = false; auto linkedRenderWindow = dynamic_cast(this->GetRenderWindowPart()); QmitkRenderWindow* selectedRenderWindow; if (!linkedRenderWindow) return; auto axialRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("axial"); auto sagittalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("sagittal"); auto coronalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("coronal"); auto threeDimRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("3d"); if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, threeDimRenderWindow->GetRenderer())) { selectedRenderWindow = threeDimRenderWindow; } else { selectedRenderWindow = nullptr; } auto planeGeometry = dynamic_cast(planarFigure->GetPlaneGeometry()); auto normal = planeGeometry->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer axialPlane = axialRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto axialNormal = axialPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer sagittalPlane = sagittalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto sagittalNormal = sagittalPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer coronalPlane = coronalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto coronalNormal = coronalPlane->GetNormalVnl(); normal[0] = fabs(normal[0]); normal[1] = fabs(normal[1]); normal[2] = fabs(normal[2]); axialNormal[0] = fabs(axialNormal[0]); axialNormal[1] = fabs(axialNormal[1]); axialNormal[2] = fabs(axialNormal[2]); sagittalNormal[0] = fabs(sagittalNormal[0]); sagittalNormal[1] = fabs(sagittalNormal[1]); sagittalNormal[2] = fabs(sagittalNormal[2]); coronalNormal[0] = fabs(coronalNormal[0]); coronalNormal[1] = fabs(coronalNormal[1]); coronalNormal[2] = fabs(coronalNormal[2]); auto ang1 = angle(normal, axialNormal); auto ang2 = angle(normal, sagittalNormal); auto ang3 = angle(normal, coronalNormal); if (ang1 < ang2 && ang1 < ang3) { selectedRenderWindow = axialRenderWindow; } else { if (ang2 < ang3) { selectedRenderWindow = sagittalRenderWindow; } else { selectedRenderWindow = coronalRenderWindow; } } // re-orient view if (selectedRenderWindow) selectedRenderWindow->GetSliceNavigationController()->ReorientSlices(planeGeometry->GetOrigin(), planeGeometry->GetNormal()); } break; } this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::OnDrawLineTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarLine::New(), QString("Line%1").arg(++d->m_LineCounter)); } void QmitkMeasurementView::OnDrawPathTriggered(bool) { auto propertyFilters = mitk::CoreServices::GetPropertyFilters(); if (propertyFilters != nullptr) { mitk::PropertyFilter filter; filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); propertyFilters->AddFilter(filter, "PlanarPolygon"); } mitk::PlanarPolygon::Pointer planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOff(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Path%1").arg(++d->m_PathCounter)); node->SetProperty("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnDrawAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarAngle::New(), QString("Angle%1").arg(++d->m_AngleCounter)); } void QmitkMeasurementView::OnDrawFourPointAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarFourPointAngle::New(), QString("Four Point Angle%1").arg(++d->m_FourPointAngleCounter)); } void QmitkMeasurementView::OnDrawCircleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarCircle::New(), QString("Circle%1").arg(++d->m_CircleCounter)); } void QmitkMeasurementView::OnDrawEllipseTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarEllipse::New(), QString("Ellipse%1").arg(++d->m_EllipseCounter)); } void QmitkMeasurementView::OnDrawDoubleEllipseTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarDoubleEllipse::New(), QString("DoubleEllipse%1").arg(++d->m_DoubleEllipseCounter)); } void QmitkMeasurementView::OnDrawBezierCurveTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarBezierCurve::New(), QString("BezierCurve%1").arg(++d->m_BezierCurveCounter)); } void QmitkMeasurementView::OnDrawSubdivisionPolygonTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarSubdivisionPolygon::New(), QString("SubdivisionPolygon%1").arg(++d->m_SubdivisionPolygonCounter)); } void QmitkMeasurementView::OnDrawRectangleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarRectangle::New(), QString("Rectangle%1").arg(++d->m_RectangleCounter)); } void QmitkMeasurementView::OnDrawPolygonTriggered(bool) { auto planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOn(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Polygon%1").arg(++d->m_PolygonCounter)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnCopyToClipboard(bool) { QApplication::clipboard()->setText(d->m_SelectedPlanarFiguresText->toPlainText(), QClipboard::Clipboard); } mitk::DataNode::Pointer QmitkMeasurementView::AddFigureToDataStorage(mitk::PlanarFigure* figure, const QString& name) { auto newNode = mitk::DataNode::New(); newNode->SetName(name.toStdString()); newNode->SetData(figure); newNode->SetSelected(true); if (d->m_SelectedImageNode.IsNotNull()) { this->GetDataStorage()->Add(newNode, d->m_SelectedImageNode); } else { this->GetDataStorage()->Add(newNode); } for (auto &node : d->m_CurrentSelection) node->SetSelected(false); d->m_CurrentSelection.clear(); d->m_CurrentSelection.push_back(newNode); this->UpdateMeasurementText(); this->DisableCrosshairNavigation(); d->m_DrawActionsToolBar->setEnabled(false); d->m_UnintializedPlanarFigure = true; return newNode; } void QmitkMeasurementView::UpdateMeasurementText() { d->m_SelectedPlanarFiguresText->clear(); QString infoText; QString plainInfoText; int j = 1; mitk::PlanarFigure::Pointer planarFigure; mitk::PlanarAngle::Pointer planarAngle; mitk::PlanarFourPointAngle::Pointer planarFourPointAngle; mitk::DataNode::Pointer node; for (int i = 0; i < d->m_CurrentSelection.size(); ++i, ++j) { plainInfoText.clear(); node = d->m_CurrentSelection[i]; planarFigure = dynamic_cast(node->GetData()); if (planarFigure.IsNull()) continue; if (j > 1) infoText.append("
"); infoText.append(QString("%1
").arg(QString::fromStdString(node->GetName()))); plainInfoText.append(QString("%1").arg(QString::fromStdString(node->GetName()))); planarAngle = dynamic_cast (planarFigure.GetPointer()); if (planarAngle.IsNull()) planarFourPointAngle = dynamic_cast (planarFigure.GetPointer()); double featureQuantity = 0.0; for (unsigned int k = 0; k < planarFigure->GetNumberOfFeatures(); ++k) { if (!planarFigure->IsFeatureActive(k)) continue; featureQuantity = planarFigure->GetQuantity(k); if ((planarAngle.IsNotNull() && k == planarAngle->FEATURE_ID_ANGLE) || (planarFourPointAngle.IsNotNull() && k == planarFourPointAngle->FEATURE_ID_ANGLE)) featureQuantity = featureQuantity * 180 / vnl_math::pi; infoText.append(QString("%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); plainInfoText.append(QString("\n%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); if (k + 1 != planarFigure->GetNumberOfFeatures()) infoText.append("
"); } if (j != d->m_CurrentSelection.size()) infoText.append("
"); } d->m_SelectedPlanarFiguresText->setHtml(infoText); } void QmitkMeasurementView::AddAllInteractors() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeAdded(it.Value()); } mitk::DataNode::Pointer QmitkMeasurementView::DetectTopMostVisibleImage() { // get all images from the data storage which are not a segmentation auto isImage = mitk::TNodePredicateDataType::New(); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isNotBinary = mitk::NodePredicateNot::New(isBinary); auto isNormalImage = mitk::NodePredicateAnd::New(isImage, isNotBinary); auto images = this->GetDataStorage()->GetSubset(isNormalImage); mitk::DataNode::Pointer currentNode; int maxLayer = std::numeric_limits::min(); int layer = 0; // iterate over selection for (auto it = images->Begin(); it != images->End(); ++it) { auto node = it->Value(); if (node.IsNull()) continue; if (node->IsVisible(nullptr) == false) continue; // we also do not want to assign planar figures to helper objects ( even if they are of type image ) if (node->GetProperty("helper object") != nullptr) continue; node->GetIntProperty("layer", layer); if (layer < maxLayer) { continue; } else { maxLayer = layer; currentNode = node; } } return currentNode; } void QmitkMeasurementView::EnableCrosshairNavigation() { // enable the crosshair navigation // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractorConfig : m_DisplayInteractorConfigs) { if (displayInteractorConfig.first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(displayInteractorConfig.first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(displayInteractorConfig.second); } } } m_DisplayInteractorConfigs.clear(); d->m_ScrollEnabled = true; } void QmitkMeasurementView::DisableCrosshairNavigation() { // dont deactivate twice, else we will clutter the config list ... if (d->m_ScrollEnabled == false) return; // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction will still be enabled m_DisplayInteractorConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(eventObserver)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(eventObserver, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig("DisplayConfigMITKLimited.xml"); } } d->m_ScrollEnabled = false; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkMeasurementView::GetAllPlanarFigures() const { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto isNotHelperObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(false)); auto isNotHelperButPlanarFigure = mitk::NodePredicateAnd::New( isPlanarFigure, isNotHelperObject ); return this->GetDataStorage()->GetSubset(isPlanarFigure); } diff --git a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp index 8dc1efe285..d2cfc1749a 100755 --- a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp +++ b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp @@ -1,344 +1,349 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkVolumeVisualizationView.h" #include #include #include #include #include #include //#include #include #include #include #include #include #include #include "mitkHistogramGenerator.h" #include "QmitkPiecewiseFunctionCanvas.h" #include "QmitkColorTransferFunctionCanvas.h" #include "mitkBaseRenderer.h" #include "mitkVtkVolumeRenderingProperty.h" #include #include const std::string QmitkVolumeVisualizationView::VIEW_ID = "org.mitk.views.volumevisualization"; enum {DEFAULT_RENDERMODE = 0, RAYCAST_RENDERMODE = 1, GPU_RENDERMODE = 2}; QmitkVolumeVisualizationView::QmitkVolumeVisualizationView() : QmitkAbstractView(), m_Controls(nullptr) { } QmitkVolumeVisualizationView::~QmitkVolumeVisualizationView() { } void QmitkVolumeVisualizationView::CreateQtPartControl(QWidget* parent) { if (!m_Controls) { m_Controls = new Ui::QmitkVolumeVisualizationViewControls; m_Controls->setupUi(parent); // Fill the tf presets in the generator widget std::vector names; mitk::TransferFunctionInitializer::GetPresetNames(names); for (std::vector::const_iterator it = names.begin(); it != names.end(); ++it) { m_Controls->m_TransferFunctionGeneratorWidget->AddPreset(QString::fromStdString(*it)); } // see enum in vtkSmartVolumeMapper m_Controls->m_RenderMode->addItem("Default"); m_Controls->m_RenderMode->addItem("RayCast"); m_Controls->m_RenderMode->addItem("GPU"); // see vtkVolumeMapper::BlendModes m_Controls->m_BlendMode->addItem("Comp"); m_Controls->m_BlendMode->addItem("Max"); m_Controls->m_BlendMode->addItem("Min"); m_Controls->m_BlendMode->addItem("Avg"); m_Controls->m_BlendMode->addItem("Add"); connect( m_Controls->m_EnableRenderingCB, SIGNAL( toggled(bool) ),this, SLOT( OnEnableRendering(bool) )); connect(m_Controls->m_RenderMode, SIGNAL(activated(int)), this, SLOT(OnRenderMode(int))); connect(m_Controls->m_BlendMode, SIGNAL(activated(int)), this, SLOT(OnBlendMode(int))); connect( m_Controls->m_TransferFunctionGeneratorWidget, SIGNAL( SignalUpdateCanvas( ) ), m_Controls->m_TransferFunctionWidget, SLOT( OnUpdateCanvas( ) ) ); connect( m_Controls->m_TransferFunctionGeneratorWidget, SIGNAL(SignalTransferFunctionModeChanged(int)), SLOT(OnMitkInternalPreset(int))); m_Controls->m_EnableRenderingCB->setEnabled(false); m_Controls->m_BlendMode->setEnabled(false); m_Controls->m_RenderMode->setEnabled(false); m_Controls->m_TransferFunctionWidget->setEnabled(false); m_Controls->m_TransferFunctionGeneratorWidget->setEnabled(false); m_Controls->m_SelectedImageLabel->hide(); m_Controls->m_ErrorImageLabel->hide(); } } void QmitkVolumeVisualizationView::OnMitkInternalPreset( int mode ) { - if (m_SelectedNode.IsNull()) return; + if (m_SelectedNode.IsExpired()) return; - mitk::DataNode::Pointer node(m_SelectedNode.GetPointer()); + auto node = m_SelectedNode.Lock(); mitk::TransferFunctionProperty::Pointer transferFuncProp; if (node->GetProperty(transferFuncProp, "TransferFunction")) { //first item is only information if( --mode == -1 ) return; // -- Creat new TransferFunction mitk::TransferFunctionInitializer::Pointer tfInit = mitk::TransferFunctionInitializer::New(transferFuncProp->GetValue()); tfInit->SetTransferFunctionMode(mode); RequestRenderWindowUpdate(); m_Controls->m_TransferFunctionWidget->OnUpdateCanvas(); } } void QmitkVolumeVisualizationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { bool weHadAnImageButItsNotThreeDeeOrFourDee = false; mitk::DataNode::Pointer node; for (mitk::DataNode::Pointer currentNode: nodes) { if( currentNode.IsNotNull() && dynamic_cast(currentNode->GetData()) ) { if( dynamic_cast(currentNode->GetData())->GetDimension()>=3 ) { if (node.IsNull()) { node = currentNode; } } else { weHadAnImageButItsNotThreeDeeOrFourDee = true; } } } if( node.IsNotNull() ) { m_Controls->m_NoSelectedImageLabel->hide(); m_Controls->m_ErrorImageLabel->hide(); m_Controls->m_SelectedImageLabel->show(); std::string infoText; if (node->GetName().empty()) infoText = std::string("Selected Image: [currently selected image has no name]"); else infoText = std::string("Selected Image: ") + node->GetName(); m_Controls->m_SelectedImageLabel->setText( QString( infoText.c_str() ) ); m_SelectedNode = node; } else { if(weHadAnImageButItsNotThreeDeeOrFourDee) { m_Controls->m_NoSelectedImageLabel->hide(); m_Controls->m_ErrorImageLabel->show(); std::string infoText; infoText = std::string("only 3D or 4D images are supported"); m_Controls->m_ErrorImageLabel->setText( QString( infoText.c_str() ) ); } else { m_Controls->m_SelectedImageLabel->hide(); m_Controls->m_ErrorImageLabel->hide(); m_Controls->m_NoSelectedImageLabel->show(); } - m_SelectedNode = 0; + m_SelectedNode = nullptr; } UpdateInterface(); } void QmitkVolumeVisualizationView::UpdateInterface() { - if(m_SelectedNode.IsNull()) + if(m_SelectedNode.IsExpired()) { // turnoff all m_Controls->m_EnableRenderingCB->setChecked(false); m_Controls->m_EnableRenderingCB->setEnabled(false); m_Controls->m_BlendMode->setCurrentIndex(0); m_Controls->m_BlendMode->setEnabled(false); m_Controls->m_RenderMode->setCurrentIndex(0); m_Controls->m_RenderMode->setEnabled(false); m_Controls->m_TransferFunctionWidget->SetDataNode(0); m_Controls->m_TransferFunctionWidget->setEnabled(false); m_Controls->m_TransferFunctionGeneratorWidget->SetDataNode(0); m_Controls->m_TransferFunctionGeneratorWidget->setEnabled(false); return; } bool enabled = false; + auto selectedNode = m_SelectedNode.Lock(); - m_SelectedNode->GetBoolProperty("volumerendering",enabled); + selectedNode->GetBoolProperty("volumerendering",enabled); m_Controls->m_EnableRenderingCB->setEnabled(true); m_Controls->m_EnableRenderingCB->setChecked(enabled); if(!enabled) { // turnoff all except volumerendering checkbox m_Controls->m_BlendMode->setCurrentIndex(0); m_Controls->m_BlendMode->setEnabled(false); m_Controls->m_RenderMode->setCurrentIndex(0); m_Controls->m_RenderMode->setEnabled(false); m_Controls->m_TransferFunctionWidget->SetDataNode(0); m_Controls->m_TransferFunctionWidget->setEnabled(false); m_Controls->m_TransferFunctionGeneratorWidget->SetDataNode(0); m_Controls->m_TransferFunctionGeneratorWidget->setEnabled(false); return; } // otherwise we can activate em all m_Controls->m_BlendMode->setEnabled(true); m_Controls->m_RenderMode->setEnabled(true); // Determine Combo Box mode { bool usegpu=false; bool useray=false; bool usemip=false; - m_SelectedNode->GetBoolProperty("volumerendering.usegpu",usegpu); - m_SelectedNode->GetBoolProperty("volumerendering.useray",useray); - m_SelectedNode->GetBoolProperty("volumerendering.usemip",usemip); + selectedNode->GetBoolProperty("volumerendering.usegpu",usegpu); + selectedNode->GetBoolProperty("volumerendering.useray",useray); + selectedNode->GetBoolProperty("volumerendering.usemip",usemip); int blendMode; - if (m_SelectedNode->GetIntProperty("volumerendering.blendmode", blendMode)) + if (selectedNode->GetIntProperty("volumerendering.blendmode", blendMode)) m_Controls->m_BlendMode->setCurrentIndex(blendMode); if (usemip) m_Controls->m_BlendMode->setCurrentIndex(vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND); int mode = DEFAULT_RENDERMODE; if (useray) mode = RAYCAST_RENDERMODE; else if(usegpu) mode = GPU_RENDERMODE; m_Controls->m_RenderMode->setCurrentIndex(mode); } - m_Controls->m_TransferFunctionWidget->SetDataNode(m_SelectedNode); + m_Controls->m_TransferFunctionWidget->SetDataNode(selectedNode); m_Controls->m_TransferFunctionWidget->setEnabled(true); - m_Controls->m_TransferFunctionGeneratorWidget->SetDataNode(m_SelectedNode); + m_Controls->m_TransferFunctionGeneratorWidget->SetDataNode(selectedNode); m_Controls->m_TransferFunctionGeneratorWidget->setEnabled(true); } void QmitkVolumeVisualizationView::OnEnableRendering(bool state) { - if(m_SelectedNode.IsNull()) + if(m_SelectedNode.IsExpired()) return; - m_SelectedNode->SetProperty("volumerendering",mitk::BoolProperty::New(state)); + m_SelectedNode.Lock()->SetProperty("volumerendering",mitk::BoolProperty::New(state)); UpdateInterface(); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::OnBlendMode(int mode) { - if (m_SelectedNode.IsNull()) + if (m_SelectedNode.IsExpired()) return; + auto selectedNode = m_SelectedNode.Lock(); + bool usemip = false; if (mode == vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND) usemip = true; - m_SelectedNode->SetProperty("volumerendering.usemip", mitk::BoolProperty::New(usemip)); - m_SelectedNode->SetProperty("volumerendering.blendmode", mitk::IntProperty::New(mode)); + selectedNode->SetProperty("volumerendering.usemip", mitk::BoolProperty::New(usemip)); + selectedNode->SetProperty("volumerendering.blendmode", mitk::IntProperty::New(mode)); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::OnRenderMode(int mode) { - if(m_SelectedNode.IsNull()) + if(m_SelectedNode.IsExpired()) return; + + auto selectedNode = m_SelectedNode.Lock(); bool usegpu = false; if (mode == GPU_RENDERMODE) usegpu = true; bool useray = false; if (mode == RAYCAST_RENDERMODE) useray = true; if (mode == DEFAULT_RENDERMODE) { useray = true; usegpu = true; } - m_SelectedNode->SetProperty("volumerendering.usegpu",mitk::BoolProperty::New(usegpu)); - m_SelectedNode->SetProperty("volumerendering.useray",mitk::BoolProperty::New(useray)); + selectedNode->SetProperty("volumerendering.usegpu",mitk::BoolProperty::New(usegpu)); + selectedNode->SetProperty("volumerendering.useray",mitk::BoolProperty::New(useray)); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::SetFocus() { } void QmitkVolumeVisualizationView::NodeRemoved(const mitk::DataNode* node) { if(m_SelectedNode == node) { - m_SelectedNode=0; + m_SelectedNode = nullptr; m_Controls->m_SelectedImageLabel->hide(); m_Controls->m_ErrorImageLabel->hide(); m_Controls->m_NoSelectedImageLabel->show(); UpdateInterface(); } }