diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp index 8bc428671a..ca5d63f2ff 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp @@ -1,51 +1,57 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkDataGenerationJobBase.h" +bool QmitkDataGenerationJobBase::IsRunning() const +{ + return m_IsRunning; +} std::string QmitkDataGenerationJobBase::GetLastErrorMessage() const { return m_LastErrorMessage; } bool QmitkDataGenerationJobBase::GetComputationSuccessFlag() const { return m_ComputationSuccessful; } void QmitkDataGenerationJobBase::run() { try { + m_IsRunning = true; m_ComputationSuccessful = this->RunComputation(); if (m_ComputationSuccessful) { emit ResultsAvailable(this->GetResults(), this); } else { emit Error(QStringLiteral("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); } } catch (const std::exception& e) { m_LastErrorMessage = e.what(); emit Error(QStringLiteral("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); } catch (...) { m_LastErrorMessage = "Unknown exception"; emit Error(QStringLiteral("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); } + m_IsRunning = false; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h index 6f3f248b4b..4ba799839f 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h @@ -1,74 +1,77 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkDataGenerationJobBase_h #define QmitkDataGenerationJobBase_h //QT #include #include #include //MITK #include #include /*! \brief QmitkDataGenerationJobBase Base class for generation jobs used by QmitkDataGenerationBase and derived classes. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGenerationJobBase : public QObject, public QRunnable { Q_OBJECT public: /** Result map that indicates all results generated by the job. The key is a job specific label for the results.*/ using ResultMapType = std::map; virtual ResultMapType GetResults() const = 0; /** Calls RunComputation() and takes care of the error handling and result signalling.*/ void run() final; /*! /brief Returns a flag the indicates if the job computation was successfull.*/ bool GetComputationSuccessFlag() const; std::string GetLastErrorMessage() const; + bool IsRunning() const; + signals: void Error(QString err, const QmitkDataGenerationJobBase* job); /*! @brief Signal is emitted when results are available. @param results produced by the job and ready to be used. @param job the job that produced the data */ void ResultsAvailable(ResultMapType results, const QmitkDataGenerationJobBase* job); protected: QmitkDataGenerationJobBase() = default; virtual ~QmitkDataGenerationJobBase() = default; /**Does the real computation. Returns true if there where results produced.*/ virtual bool RunComputation() = 0; std::string m_LastErrorMessage; private: bool m_ComputationSuccessful = false; + bool m_IsRunning = false; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.cpp deleted file mode 100644 index f3cf23d68b..0000000000 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkImageStatisticsCalculationJob.h" - -#include "mitkImageStatisticsCalculator.h" -#include -#include -#include - -QmitkImageStatisticsCalculationJob::QmitkImageStatisticsCalculationJob() - : QThread() - , m_StatisticsImage(nullptr) - , m_BinaryMask(nullptr) - , m_PlanarFigureMask(nullptr) - , m_IgnoreZeros(false) - , m_HistogramNBins(100) - , m_CalculationSuccessful(false) -{ -} - -QmitkImageStatisticsCalculationJob::~QmitkImageStatisticsCalculationJob() -{ -} - -void QmitkImageStatisticsCalculationJob::Initialize(const mitk::Image *image, - const mitk::Image *binaryImage, - const mitk::PlanarFigure *planarFig) -{ - this->m_StatisticsImage = image; - this->m_BinaryMask = binaryImage; - this->m_PlanarFigureMask = planarFig; -} - -mitk::ImageStatisticsContainer* QmitkImageStatisticsCalculationJob::GetStatisticsData() const -{ - return this->m_StatisticsContainer.GetPointer(); -} - -const mitk::Image* QmitkImageStatisticsCalculationJob::GetStatisticsImage() const -{ - return this->m_StatisticsImage.GetPointer(); -} - -const mitk::Image* QmitkImageStatisticsCalculationJob::GetMaskImage() const -{ - return this->m_BinaryMask.GetPointer(); -} - -const mitk::PlanarFigure* QmitkImageStatisticsCalculationJob::GetPlanarFigure() const -{ - return this->m_PlanarFigureMask.GetPointer(); -} - -void QmitkImageStatisticsCalculationJob::SetIgnoreZeroValueVoxel(bool _arg) -{ - this->m_IgnoreZeros = _arg; -} - -bool QmitkImageStatisticsCalculationJob::GetIgnoreZeroValueVoxel() const -{ - return this->m_IgnoreZeros; -} - -void QmitkImageStatisticsCalculationJob::SetHistogramNBins(unsigned int nbins) -{ - this->m_HistogramNBins = nbins; -} - -unsigned int QmitkImageStatisticsCalculationJob::GetHistogramNBins() const -{ - return this->m_HistogramNBins; -} - -std::string QmitkImageStatisticsCalculationJob::GetLastErrorMessage() const -{ - return m_message; -} - -const QmitkImageStatisticsCalculationJob::HistogramType* -QmitkImageStatisticsCalculationJob::GetTimeStepHistogram(unsigned int t) const -{ - if (t >= this->m_HistogramVector.size()) - return nullptr; - - return this->m_HistogramVector.at(t).GetPointer(); -} - -bool QmitkImageStatisticsCalculationJob::GetStatisticsUpdateSuccessFlag() const -{ - return m_CalculationSuccessful; -} - -void QmitkImageStatisticsCalculationJob::run() -{ - bool statisticCalculationSuccessful = true; - mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); - - if(this->m_StatisticsImage.IsNotNull()) - { - calculator->SetInputImage(m_StatisticsImage); - } - else - { - statisticCalculationSuccessful = false; - } - - // Bug 13416 : The ImageStatistics::SetImageMask() method can throw exceptions, i.e. when the dimensionality - // of the masked and input image differ, we need to catch them and mark the calculation as failed - // the same holds for the ::SetPlanarFigure() - try - { - if(this->m_BinaryMask.IsNotNull()) - { - mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); - imgMask->SetInputImage(m_StatisticsImage); - imgMask->SetImageMask(m_BinaryMask); - calculator->SetMask(imgMask.GetPointer()); - } - if(this->m_PlanarFigureMask.IsNotNull()) - { - mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); - pfMaskGen->SetInputImage(m_StatisticsImage); - pfMaskGen->SetPlanarFigure(m_PlanarFigureMask->Clone()); - calculator->SetMask(pfMaskGen.GetPointer()); - } - } - catch (const mitk::Exception& e) - { - MITK_ERROR << "MITK Exception: " << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } - catch( const itk::ExceptionObject& e) - { - MITK_ERROR << "ITK Exception:" << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } - catch ( const std::runtime_error &e ) - { - MITK_ERROR<< "Runtime Exception: " << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } - catch ( const std::exception &e ) - { - MITK_ERROR<< "Standard Exception: " << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } - - if (this->m_IgnoreZeros) - { - mitk::IgnorePixelMaskGenerator::Pointer ignorePixelValueMaskGen = mitk::IgnorePixelMaskGenerator::New(); - ignorePixelValueMaskGen->SetIgnoredPixelValue(0); - ignorePixelValueMaskGen->SetInputImage(m_StatisticsImage); - calculator->SetSecondaryMask(ignorePixelValueMaskGen.GetPointer()); - } - else - { - calculator->SetSecondaryMask(nullptr); - } - - calculator->SetNBinsForHistogramStatistics(m_HistogramNBins); - - try - { - calculator->GetStatistics(); - } - catch ( mitk::Exception& e) - { - m_message = e.GetDescription(); - MITK_ERROR<< "MITK Exception: " << e.what(); - statisticCalculationSuccessful = false; - } - catch ( const std::runtime_error &e ) - { - m_message = "Failure: " + std::string(e.what()); - MITK_ERROR<< "Runtime Exception: " << e.what(); - statisticCalculationSuccessful = false; - } - catch ( const std::exception &e ) - { - m_message = "Failure: " + std::string(e.what()); - MITK_ERROR<< "Standard Exception: " << e.what(); - statisticCalculationSuccessful = false; - } - - this->m_CalculationSuccessful = statisticCalculationSuccessful; - - if(statisticCalculationSuccessful) - { - m_StatisticsContainer = calculator->GetStatistics(); - this->m_HistogramVector.clear(); - - for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) - { - HistogramType::ConstPointer tempHistogram; - try { - if(calculator->GetStatistics()->TimeStepExists(i)) - { - tempHistogram = calculator->GetStatistics()->GetStatisticsForTimeStep(i).m_Histogram; - this->m_HistogramVector.push_back(tempHistogram); - } - } catch (mitk::Exception&) { - MITK_WARN << ":-("; - } - - } - - } -} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.h deleted file mode 100644 index b4623497db..0000000000 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.h +++ /dev/null @@ -1,98 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ -#ifndef QmitkImageStatisticsCalculationJob_h -#define QmitkImageStatisticsCalculationJob_h - -//QT headers -#include - - -//mitk headers -#include "mitkImage.h" -#include "mitkPlanarFigure.h" -#include "mitkImageStatisticsContainer.h" -#include - -// itk headers -#ifndef __itkHistogram_h -#include -#endif - - -/** /brief This class is executed as background thread for image statistics calculation. - * Documentation: This class is derived from QThread and is intended to be used by QmitkImageStatisticsView - to run the image statistics calculation in a background thread keeping the gui usable. - */ - -class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsCalculationJob : public QThread -{ - Q_OBJECT - -public: - - typedef itk::Statistics::Histogram HistogramType; - - /*! - /brief standard constructor. */ - QmitkImageStatisticsCalculationJob(); - /*! - /brief standard destructor. */ - ~QmitkImageStatisticsCalculationJob() override; - - /*! - /brief Initializes the object with necessary data. */ - void Initialize(const mitk::Image* image, const mitk::Image* binaryImage, const mitk::PlanarFigure* planarFig ); - /*! - /brief returns the calculated image statistics. */ - mitk::ImageStatisticsContainer* GetStatisticsData() const; - - const mitk::Image* GetStatisticsImage() const; - const mitk::Image* GetMaskImage() const; - const mitk::PlanarFigure* GetPlanarFigure() const; - - /*! - /brief Set flag to ignore zero valued voxels */ - void SetIgnoreZeroValueVoxel( bool _arg ); - /*! - /brief Get status of zero value voxel ignoring. */ - bool GetIgnoreZeroValueVoxel() const; - /*! - /brief Set bin size for histogram resolution.*/ - void SetHistogramNBins( unsigned int nbins); - /*! - /brief Get bin size for histogram resolution.*/ - unsigned int GetHistogramNBins() const; - /*! - /brief Returns the histogram of the currently selected time step. */ - const HistogramType* GetTimeStepHistogram(unsigned int t = 0) const; - - /*! - /brief Returns a flag the indicates if the statistics are updated successfully */ - bool GetStatisticsUpdateSuccessFlag() const; - /*! - /brief Method called once the thread is executed. */ - void run() override; - - std::string GetLastErrorMessage() const; - -private: - mitk::Image::ConstPointer m_StatisticsImage; ///< member variable holds the input image for which the statistics need to be calculated. - mitk::Image::ConstPointer m_BinaryMask; ///< member variable holds the binary mask image for segmentation image statistics calculation. - mitk::PlanarFigure::ConstPointer m_PlanarFigureMask; ///< member variable holds the planar figure for segmentation image statistics calculation. - mitk::ImageStatisticsContainer::Pointer m_StatisticsContainer; - bool m_IgnoreZeros; ///< member variable holds flag to indicate if zero valued voxel should be suppressed - unsigned int m_HistogramNBins; ///< member variable holds the bin size for histogram resolution. - bool m_CalculationSuccessful; ///< flag set if statistics calculation was successful - std::vector m_HistogramVector; ///< member holds the histograms of all time steps. - std::string m_message; -}; -#endif diff --git a/Modules/ImageStatisticsUI/files.cmake b/Modules/ImageStatisticsUI/files.cmake index 4d0f2f5d6a..5b95e45f78 100644 --- a/Modules/ImageStatisticsUI/files.cmake +++ b/Modules/ImageStatisticsUI/files.cmake @@ -1,38 +1,36 @@ set(CPP_FILES Qmitk/QmitkHistogramVisualizationWidget.cpp Qmitk/QmitkImageStatisticsCalculationRunnable.cpp Qmitk/QmitkIntensityProfileVisualizationWidget.cpp Qmitk/QmitkImageStatisticsTreeModel.cpp - Qmitk/QmitkImageStatisticsCalculationJob.cpp Qmitk/QmitkStatisticsModelToStringConverter.cpp Qmitk/QmitkImageStatisticsWidget.cpp Qmitk/QmitkImageStatisticsTreeItem.cpp Qmitk/QmitkDataGenerationJobBase.cpp Qmitk/QmitkDataGeneratorBase.cpp Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp Qmitk/QmitkImageStatisticsDataGenerator.cpp ) set(H_FILES Qmitk/QmitkStatisticsModelToStringConverter.h Qmitk/QmitkImageStatisticsTreeItem.h Qmitk/QmitkImageAndRoiDataGeneratorBase.h Qmitk/QmitkImageStatisticsDataGenerator.h ) set(UI_FILES Qmitk/QmitkHistogramVisualizationWidget.ui Qmitk/QmitkIntensityProfileVisualizationWidget.ui Qmitk/QmitkImageStatisticsWidget.ui ) set(MOC_H_FILES Qmitk/QmitkHistogramVisualizationWidget.h Qmitk/QmitkImageStatisticsCalculationRunnable.h Qmitk/QmitkIntensityProfileVisualizationWidget.h Qmitk/QmitkImageStatisticsTreeModel.h - Qmitk/QmitkImageStatisticsCalculationJob.h Qmitk/QmitkImageStatisticsWidget.h Qmitk/QmitkDataGenerationJobBase.h Qmitk/QmitkDataGeneratorBase.h ) diff --git a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.cpp b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.cpp index 8f4366b639..d41c562a0b 100644 --- a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.cpp @@ -1,816 +1,817 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // itk #include "itksys/SystemTools.hxx" #include #include // Blueberry #include #include // Qmitk #include "QmitkCESTStatisticsView.h" // Qt #include +#include #include // qwt #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include // boost #include #include // stl #include #include #include #include #include #include namespace { template void GetSortPermutation(std::vector &out, const std::vector &determiningVector, Compare compare = std::less()) { out.resize(determiningVector.size()); std::iota(out.begin(), out.end(), 0); std::sort(out.begin(), out.end(), [&](unsigned i, unsigned j) { return compare(determiningVector[i], determiningVector[j]); }); } template void ApplyPermutation(const std::vector &order, std::vector &vectorToSort) { assert(order.size() == vectorToSort.size()); std::vector tempVector(vectorToSort.size()); for (unsigned i = 0; i < vectorToSort.size(); i++) { tempVector[i] = vectorToSort[order[i]]; } vectorToSort = tempVector; } template void ApplyPermutation(const std::vector &order, std::vector ¤tVector, std::vector &... remainingVectors) { ApplyPermutation(order, currentVector); ApplyPermutation(order, remainingVectors...); } template void SortVectors(const std::vector &orderDeterminingVector, Compare comparison, std::vector &... vectorsToBeSorted) { std::vector order; GetSortPermutation(order, orderDeterminingVector, comparison); ApplyPermutation(order, vectorsToBeSorted...); } } // namespace const std::string QmitkCESTStatisticsView::VIEW_ID = "org.mitk.views.cest.statistics"; QmitkCESTStatisticsView::QmitkCESTStatisticsView(QObject * /*parent*/, const char * /*name*/) { - this->m_CalculatorJob = new QmitkImageStatisticsCalculationJob(); + this->m_CalculatorJob = new QmitkImageStatisticsCalculationRunnable(); m_currentSelectedPosition.Fill(0.0); m_currentSelectedTimePoint = 0.; m_CrosshairPointSet = mitk::PointSet::New(); } QmitkCESTStatisticsView::~QmitkCESTStatisticsView() { - while (this->m_CalculatorJob->isRunning()) // wait until thread has finished + while (this->m_CalculatorJob->IsRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } delete this->m_CalculatorJob; } void QmitkCESTStatisticsView::SetFocus() { m_Controls.threeDimToFourDimPushButton->setFocus(); } void QmitkCESTStatisticsView::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); connect( m_Controls.threeDimToFourDimPushButton, SIGNAL(clicked()), this, SLOT(OnThreeDimToFourDimPushButtonClicked())); connect((QObject *)this->m_CalculatorJob, - SIGNAL(finished()), + SIGNAL(ResultsAvailable()), this, SLOT(OnThreadedStatisticsCalculationEnds()), Qt::QueuedConnection); connect((QObject *)(this->m_Controls.fixedRangeCheckBox), SIGNAL(toggled(bool)), (QObject *)this, SLOT(OnFixedRangeCheckBoxToggled(bool))); connect((QObject *)(this->m_Controls.fixedRangeLowerDoubleSpinBox), SIGNAL(editingFinished()), (QObject *)this, SLOT(OnFixedRangeDoubleSpinBoxChanged())); connect((QObject *)(this->m_Controls.fixedRangeUpperDoubleSpinBox), SIGNAL(editingFinished()), (QObject *)this, SLOT(OnFixedRangeDoubleSpinBoxChanged())); m_Controls.threeDimToFourDimPushButton->setEnabled(false); m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); this->m_SliceChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); connect(&m_SliceChangeListener, SIGNAL(SliceChanged()), this, SLOT(OnSliceChanged())); } void QmitkCESTStatisticsView::RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartActivated(renderWindowPart); } void QmitkCESTStatisticsView::RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void QmitkCESTStatisticsView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList &nodes) { if (nodes.empty()) { std::stringstream message; message << "Please select an image."; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); this->Clear(); return; } // iterate all selected objects bool atLeastOneWasCESTImage = false; foreach (mitk::DataNode::Pointer node, nodes) { if (node.IsNull()) { continue; } if (dynamic_cast(node->GetData()) != nullptr) { m_Controls.labelWarning->setVisible(false); bool zSpectrumSet = SetZSpectrum(dynamic_cast( node->GetData()->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str()).GetPointer())); atLeastOneWasCESTImage = atLeastOneWasCESTImage || zSpectrumSet; if (zSpectrumSet) { m_ZImage = dynamic_cast(node->GetData()); m_Controls.widget_statistics->SetImageNodes({node.GetPointer()}); } else { m_MaskImage = dynamic_cast(node->GetData()); m_Controls.widget_statistics->SetMaskNodes({node.GetPointer()}); } } if (dynamic_cast(node->GetData()) != nullptr) { m_MaskPlanarFigure = dynamic_cast(node->GetData()); m_Controls.widget_statistics->SetMaskNodes({node.GetPointer()}); } if (dynamic_cast(node->GetData()) != nullptr) { m_PointSet = dynamic_cast(node->GetData()); } } // We only want to offer normalization or timestep copying if one object is selected if (nodes.size() == 1) { if (dynamic_cast(nodes.front()->GetData())) { m_Controls.threeDimToFourDimPushButton->setDisabled(atLeastOneWasCESTImage); } else { m_Controls.threeDimToFourDimPushButton->setEnabled(false); std::stringstream message; message << "The selected node is not an image."; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); } this->Clear(); return; } // we always need a mask, either image or planar figure as well as an image for further processing if (nodes.size() != 2) { this->Clear(); return; } m_Controls.threeDimToFourDimPushButton->setEnabled(false); if (!atLeastOneWasCESTImage) { std::stringstream message; message << "None of the selected data nodes contains required CEST meta information"; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); this->Clear(); return; } bool bothAreImages = (m_ZImage.GetPointer() != nullptr) && (m_MaskImage.GetPointer() != nullptr); if (bothAreImages) { bool geometriesMatch = mitk::Equal(*(m_ZImage->GetTimeGeometry()), *(m_MaskImage->GetTimeGeometry()), mitk::eps, false); if (!geometriesMatch) { std::stringstream message; message << "The selected images have different geometries."; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); this->Clear(); return; } } if (!this->DataSanityCheck()) { this->Clear(); return; } if (m_PointSet.IsNull()) { // initialize thread and trigger it this->m_CalculatorJob->SetIgnoreZeroValueVoxel(false); this->m_CalculatorJob->Initialize(m_ZImage.GetPointer(), m_MaskImage.GetPointer(), m_MaskPlanarFigure.GetPointer()); std::stringstream message; message << "Calculating statistics..."; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); try { // Compute statistics - this->m_CalculatorJob->start(); + QThreadPool::globalInstance()->start(m_CalculatorJob); } catch (const mitk::Exception &e) { std::stringstream message; message << "" << e.GetDescription() << ""; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); } catch (const std::runtime_error &e) { // In case of exception, print error message on GUI std::stringstream message; message << "" << e.what() << ""; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); } catch (const std::exception &e) { MITK_ERROR << "Caught exception: " << e.what(); // In case of exception, print error message on GUI std::stringstream message; message << "Error! Unequal Dimensions of Image and Segmentation. No recompute possible "; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); } - while (this->m_CalculatorJob->isRunning()) // wait until thread has finished + while (this->m_CalculatorJob->IsRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } } if (m_PointSet.IsNotNull()) { if (m_ZImage->GetDimension() == 4) { AccessFixedDimensionByItk(m_ZImage, PlotPointSet, 4); } else { MITK_WARN << "Expecting a 4D image."; } } } void QmitkCESTStatisticsView::OnThreadedStatisticsCalculationEnds() { this->m_Controls.m_DataViewWidget->SetAxisTitle(QwtPlot::Axis::xBottom, "delta w"); this->m_Controls.m_DataViewWidget->SetAxisTitle(QwtPlot::Axis::yLeft, "z"); - if (this->m_CalculatorJob->GetStatisticsUpdateSuccessFlag()) + if (this->m_CalculatorJob->GetComputationSuccessFlag()) { auto statistics = this->m_CalculatorJob->GetStatisticsData(); std::string statisticsNodeName = "CEST_statistics"; auto statisticsNode = mitk::CreateImageStatisticsNode(statistics, statisticsNodeName); auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(statistics, m_CalculatorJob->GetStatisticsImage()); if (m_CalculatorJob->GetMaskImage()) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); maskRule->Connect(statistics, m_CalculatorJob->GetMaskImage()); } else if (m_CalculatorJob->GetPlanarFigure()) { auto planarFigureRule = mitk::StatisticsToMaskRelationRule::New(); planarFigureRule->Connect(statistics, m_CalculatorJob->GetPlanarFigure()); } this->GetDataStorage()->Add(statisticsNode); QmitkPlotWidget::DataVector::size_type numberOfSpectra = this->m_zSpectrum.size(); QmitkPlotWidget::DataVector means(numberOfSpectra); QmitkPlotWidget::DataVector stdevs(numberOfSpectra); for (unsigned int index = 0; index < numberOfSpectra; ++index) { means[index] = - statistics->GetStatisticsForTimeStep(index).GetValueConverted( + statistics->GetStatistics(1,index).GetValueConverted( mitk::ImageStatisticsConstants::MEAN()); stdevs[index] = - statistics->GetStatisticsForTimeStep(index).GetValueConverted( + statistics->GetStatistics(1,index).GetValueConverted( mitk::ImageStatisticsConstants::STANDARDDEVIATION()); } QmitkPlotWidget::DataVector xValues = this->m_zSpectrum; RemoveMZeros(xValues, means, stdevs); ::SortVectors(xValues, std::less(), xValues, means, stdevs); unsigned int curveId = this->m_Controls.m_DataViewWidget->InsertCurve("Spectrum"); this->m_Controls.m_DataViewWidget->SetCurveData(curveId, xValues, means, stdevs, stdevs); this->m_Controls.m_DataViewWidget->SetErrorPen(curveId, QPen(Qt::blue)); QwtSymbol *blueSymbol = new QwtSymbol(QwtSymbol::Rect, QColor(Qt::blue), QColor(Qt::blue), QSize(8, 8)); this->m_Controls.m_DataViewWidget->SetCurveSymbol(curveId, blueSymbol); this->m_Controls.m_DataViewWidget->SetLegendAttribute(curveId, QwtPlotCurve::LegendShowSymbol); QwtLegend *legend = new QwtLegend(); legend->setFrameShape(QFrame::Box); legend->setFrameShadow(QFrame::Sunken); legend->setLineWidth(1); this->m_Controls.m_DataViewWidget->SetLegend(legend, QwtPlot::BottomLegend); m_Controls.m_DataViewWidget->GetPlot() ->axisScaleEngine(QwtPlot::Axis::xBottom) ->setAttributes(QwtScaleEngine::Inverted); this->m_Controls.m_DataViewWidget->Replot(); m_Controls.labelWarning->setVisible(false); m_Controls.m_StatisticsGroupBox->setEnabled(true); m_Controls.m_StatisticsGroupBox->setEnabled(true); if (this->m_Controls.fixedRangeCheckBox->isChecked()) { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, false); this->m_Controls.m_DataViewWidget->GetPlot()->setAxisScale( 2, this->m_Controls.fixedRangeLowerDoubleSpinBox->value(), this->m_Controls.fixedRangeUpperDoubleSpinBox->value()); } else { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, true); } } else { m_Controls.labelWarning->setText(m_CalculatorJob->GetLastErrorMessage().c_str()); m_Controls.labelWarning->setVisible(true); this->Clear(); } } void QmitkCESTStatisticsView::OnFixedRangeDoubleSpinBoxChanged() { if (this->m_Controls.fixedRangeCheckBox->isChecked()) { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, false); this->m_Controls.m_DataViewWidget->GetPlot()->setAxisScale(2, this->m_Controls.fixedRangeLowerDoubleSpinBox->value(), this->m_Controls.fixedRangeUpperDoubleSpinBox->value()); } this->m_Controls.m_DataViewWidget->Replot(); } template void QmitkCESTStatisticsView::PlotPointSet(itk::Image *image) { this->m_Controls.m_DataViewWidget->SetAxisTitle(QwtPlot::Axis::xBottom, "delta w"); this->m_Controls.m_DataViewWidget->SetAxisTitle(QwtPlot::Axis::yLeft, "z"); QmitkPlotWidget::DataVector::size_type numberOfSpectra = this->m_zSpectrum.size(); mitk::PointSet::Pointer internalPointset; if (m_PointSet.IsNotNull()) { internalPointset = m_PointSet; } else { internalPointset = m_CrosshairPointSet; } if (internalPointset.IsNull()) { return; } if (!this->DataSanityCheck()) { m_Controls.labelWarning->setText("Data can not be plotted, internally inconsistent."); m_Controls.labelWarning->show(); return; } auto maxIndex = internalPointset->GetMaxId().Index(); for (std::size_t number = 0; number < maxIndex + 1; ++number) { mitk::PointSet::PointType point; if (!internalPointset->GetPointIfExists(number, &point)) { continue; } if (!this->m_ZImage->GetGeometry()->IsInside(point)) { continue; } itk::Index<3> itkIndex; this->m_ZImage->GetGeometry()->WorldToIndex(point, itkIndex); itk::Index itkIndexTime; itkIndexTime[0] = itkIndex[0]; itkIndexTime[1] = itkIndex[1]; itkIndexTime[2] = itkIndex[2]; QmitkPlotWidget::DataVector values(numberOfSpectra); for (std::size_t step = 0; step < numberOfSpectra; ++step) { if (VImageDimension == 4) { itkIndexTime[3] = step; } values[step] = image->GetPixel(itkIndexTime); } std::stringstream name; name << "Point " << number; // Qcolor enums go from 0 to 19, but 19 is transparent and 0,1 are for bitmaps // 3 is white and thus not visible QColor color(static_cast(number % 17 + 4)); QmitkPlotWidget::DataVector xValues = this->m_zSpectrum; RemoveMZeros(xValues, values); ::SortVectors(xValues, std::less(), xValues, values); unsigned int curveId = this->m_Controls.m_DataViewWidget->InsertCurve(name.str().c_str()); this->m_Controls.m_DataViewWidget->SetCurveData(curveId, xValues, values); this->m_Controls.m_DataViewWidget->SetCurvePen(curveId, QPen(color)); QwtSymbol *symbol = new QwtSymbol(QwtSymbol::Rect, color, color, QSize(8, 8)); this->m_Controls.m_DataViewWidget->SetCurveSymbol(curveId, symbol); this->m_Controls.m_DataViewWidget->SetLegendAttribute(curveId, QwtPlotCurve::LegendShowSymbol); } if (this->m_Controls.fixedRangeCheckBox->isChecked()) { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, false); this->m_Controls.m_DataViewWidget->GetPlot()->setAxisScale(2, this->m_Controls.fixedRangeLowerDoubleSpinBox->value(), this->m_Controls.fixedRangeUpperDoubleSpinBox->value()); } else { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, true); } QwtLegend *legend = new QwtLegend(); legend->setFrameShape(QFrame::Box); legend->setFrameShadow(QFrame::Sunken); legend->setLineWidth(1); this->m_Controls.m_DataViewWidget->SetLegend(legend, QwtPlot::BottomLegend); m_Controls.m_DataViewWidget->GetPlot() ->axisScaleEngine(QwtPlot::Axis::xBottom) ->setAttributes(QwtScaleEngine::Inverted); this->m_Controls.m_DataViewWidget->Replot(); m_Controls.labelWarning->setVisible(false); } void QmitkCESTStatisticsView::OnFixedRangeCheckBoxToggled(bool state) { this->m_Controls.fixedRangeLowerDoubleSpinBox->setEnabled(state); this->m_Controls.fixedRangeUpperDoubleSpinBox->setEnabled(state); } void QmitkCESTStatisticsView::RemoveMZeros(QmitkPlotWidget::DataVector &xValues, QmitkPlotWidget::DataVector &yValues) { QmitkPlotWidget::DataVector tempX; QmitkPlotWidget::DataVector tempY; for (std::size_t index = 0; index < xValues.size(); ++index) { if ((xValues.at(index) < -299) || (xValues.at(index)) > 299) { // do not include } else { tempX.push_back(xValues.at(index)); tempY.push_back(yValues.at(index)); } } xValues = tempX; yValues = tempY; } void QmitkCESTStatisticsView::RemoveMZeros(QmitkPlotWidget::DataVector &xValues, QmitkPlotWidget::DataVector &yValues, QmitkPlotWidget::DataVector &stdDevs) { QmitkPlotWidget::DataVector tempX; QmitkPlotWidget::DataVector tempY; QmitkPlotWidget::DataVector tempDevs; for (std::size_t index = 0; index < xValues.size(); ++index) { if ((xValues.at(index) < -299) || (xValues.at(index)) > 299) { // do not include } else { tempX.push_back(xValues.at(index)); tempY.push_back(yValues.at(index)); tempDevs.push_back(stdDevs.at(index)); } } xValues = tempX; yValues = tempY; stdDevs = tempDevs; } void QmitkCESTStatisticsView::OnThreeDimToFourDimPushButtonClicked() { QList nodes = this->GetDataManagerSelection(); if (nodes.empty()) return; mitk::DataNode *node = nodes.front(); if (!node) { // Nothing selected. Inform the user and return QMessageBox::information(nullptr, "CEST View", "Please load and select an image before starting image processing."); return; } // here we have a valid mitk::DataNode // a node itself is not very useful, we need its data item (the image) mitk::BaseData *data = node->GetData(); if (data) { // test if this data item is an image or not (could also be a surface or something totally different) mitk::Image *image = dynamic_cast(data); if (image) { if (image->GetDimension() == 4) { AccessFixedDimensionByItk(image, CopyTimesteps, 4); } this->Clear(); } } } template void QmitkCESTStatisticsView::CopyTimesteps(itk::Image *image) { typedef itk::Image ImageType; // typedef itk::PasteImageFilter PasteImageFilterType; unsigned int numberOfTimesteps = image->GetLargestPossibleRegion().GetSize(3); typename ImageType::RegionType sourceRegion = image->GetLargestPossibleRegion(); sourceRegion.SetSize(3, 1); typename ImageType::RegionType targetRegion = image->GetLargestPossibleRegion(); targetRegion.SetSize(3, 1); for (unsigned int timestep = 1; timestep < numberOfTimesteps; ++timestep) { targetRegion.SetIndex(3, timestep); itk::ImageRegionConstIterator sourceIterator(image, sourceRegion); itk::ImageRegionIterator targetIterator(image, targetRegion); while (!sourceIterator.IsAtEnd()) { targetIterator.Set(sourceIterator.Get()); ++sourceIterator; ++targetIterator; } } } bool QmitkCESTStatisticsView::SetZSpectrum(mitk::StringProperty *zSpectrumProperty) { if (zSpectrumProperty == nullptr) { return false; } mitk::LocaleSwitch localeSwitch("C"); std::string zSpectrumString = zSpectrumProperty->GetValueAsString(); std::istringstream iss(zSpectrumString); std::vector zSpectra; std::copy( std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(zSpectra)); m_zSpectrum.clear(); m_zSpectrum.resize(0); for (auto const &spectrumString : zSpectra) { m_zSpectrum.push_back(std::stod(spectrumString)); } return (m_zSpectrum.size() > 0); } bool QmitkCESTStatisticsView::DataSanityCheck() { QmitkPlotWidget::DataVector::size_type numberOfSpectra = m_zSpectrum.size(); // if we do not have a spectrum, the data can not be processed if (numberOfSpectra == 0) { return false; } // if we do not have CEST image data, the data can not be processed if (m_ZImage.IsNull()) { return false; } // if the CEST image data and the meta information do not match, the data can not be processed if (numberOfSpectra != m_ZImage->GetTimeSteps()) { MITK_INFO << "CEST meta information and number of volumes does not match."; return false; } // if we have neither a mask image, a point set nor a mask planar figure, we can not do statistics // statistics on the whole image would not make sense if (m_MaskImage.IsNull() && m_MaskPlanarFigure.IsNull() && m_PointSet.IsNull() && m_CrosshairPointSet->IsEmpty()) { return false; } // if we have a mask image and a mask planar figure, we can not do statistics // we do not know which one to use if (m_MaskImage.IsNotNull() && m_MaskPlanarFigure.IsNotNull()) { return false; } return true; } void QmitkCESTStatisticsView::Clear() { this->m_zSpectrum.clear(); this->m_zSpectrum.resize(0); this->m_ZImage = nullptr; this->m_MaskImage = nullptr; this->m_MaskPlanarFigure = nullptr; this->m_PointSet = nullptr; this->m_Controls.m_DataViewWidget->Clear(); this->m_Controls.m_StatisticsGroupBox->setEnabled(false); this->m_Controls.widget_statistics->SetImageNodes({}); this->m_Controls.widget_statistics->SetMaskNodes({}); } void QmitkCESTStatisticsView::OnSliceChanged() { auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto currentSelectedPosition = renderWindowPart->GetSelectedPosition(nullptr); auto currentSelectedTimePoint = renderWindowPart->GetSelectedTimePoint(); if (m_currentSelectedPosition != currentSelectedPosition || m_currentSelectedTimePoint != currentSelectedTimePoint) //|| m_selectedNodeTime > m_currentPositionTime) { // the current position has been changed or the selected node has been changed since the last position validation -> // check position m_currentSelectedPosition = currentSelectedPosition; m_currentSelectedTimePoint = currentSelectedTimePoint; m_currentPositionTime.Modified(); m_CrosshairPointSet->Clear(); m_CrosshairPointSet->SetPoint(0, m_currentSelectedPosition); QList nodes = this->GetDataManagerSelection(); if (nodes.empty() || nodes.size() > 1) return; mitk::DataNode *node = nodes.front(); if (!node) { return; } if (dynamic_cast(node->GetData()) != nullptr) { m_Controls.labelWarning->setVisible(false); bool zSpectrumSet = SetZSpectrum(dynamic_cast( node->GetData()->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str()).GetPointer())); if (zSpectrumSet) { m_ZImage = dynamic_cast(node->GetData()); } else { return; } } else { return; } this->m_Controls.m_DataViewWidget->Clear(); AccessFixedDimensionByItk(m_ZImage, PlotPointSet, 4); } } diff --git a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.h b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.h index 773a267db5..13a2bf797c 100644 --- a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.h +++ b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.h @@ -1,130 +1,130 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkCESTStatisticsView_h #define QmitkCESTStatisticsView_h #include #include #include #include "ui_QmitkCESTStatisticsViewControls.h" -#include +#include #include #include /** \brief QmitkCESTStatisticsView \warning Basic statistics view for CEST data. \sa QmitkAbstractView \ingroup ${plugin_target}_internal */ class QmitkCESTStatisticsView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; /*! \brief default constructor */ QmitkCESTStatisticsView(QObject *parent = nullptr, const char *name = nullptr); /*! \brief default destructor */ ~QmitkCESTStatisticsView() override; protected slots: /// \brief Called when the user clicks the GUI button void OnThreeDimToFourDimPushButtonClicked(); /// \brief takes care of processing the computed data void OnThreadedStatisticsCalculationEnds(); /// \brief Toggle whether or not the plot uses a fixed x range void OnFixedRangeCheckBoxToggled(bool state); /// \brief Adapt axis scale when manual ranges are set void OnFixedRangeDoubleSpinBoxChanged(); /// \brief What to do if the crosshair moves void OnSliceChanged(); protected: void CreateQtPartControl(QWidget *parent) override; void SetFocus() override; void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; void OnSelectionChanged( berry::IWorkbenchPart::Pointer source, const QList& nodes ) override; /// parse string and set data vector returns true if successful bool SetZSpectrum(mitk::StringProperty* zSpectrumProperty); /** Checks whether the currently set data appears reasonable */ bool DataSanityCheck(); /** Fills the plot based on a point set * * This will only use the first timestep */ template void PlotPointSet(itk::Image* image); /** Deletes all data */ void Clear(); /** Remove MZeros * * Will remove the data for the M0 images from the given input */ void RemoveMZeros(QmitkPlotWidget::DataVector& xValues, QmitkPlotWidget::DataVector& yValues); void RemoveMZeros(QmitkPlotWidget::DataVector& xValues, QmitkPlotWidget::DataVector& yValues, QmitkPlotWidget::DataVector& stdDevs); /** Copies the first timestep of a segmentation to all others */ template void CopyTimesteps(itk::Image* image); Ui::QmitkCESTStatisticsViewControls m_Controls; - QmitkImageStatisticsCalculationJob* m_CalculatorJob; + QmitkImageStatisticsCalculationRunnable* m_CalculatorJob; QmitkPlotWidget::DataVector m_zSpectrum; mitk::Image::Pointer m_ZImage; mitk::Image::Pointer m_MaskImage; mitk::PlanarFigure::Pointer m_MaskPlanarFigure; mitk::PointSet::Pointer m_PointSet; mitk::PointSet::Pointer m_CrosshairPointSet; QmitkSliceNavigationListener m_SliceChangeListener; itk::TimeStamp m_selectedNodeTime; itk::TimeStamp m_currentPositionTime; /** @brief currently valid selected position in the inspector*/ mitk::Point3D m_currentSelectedPosition; mitk::TimePointType m_currentSelectedTimePoint; }; #endif