diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.cpp index 775e7427a5..ed60c4d903 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.cpp @@ -1,220 +1,228 @@ /*============================================================================ 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 "QmitkHistogramVisualizationWidget.h" #include QmitkHistogramVisualizationWidget::QmitkHistogramVisualizationWidget(QWidget* parent) : QWidget(parent) { m_Controls.setupUi(this); m_Controls.checkBoxShowSubchart->setChecked(false); m_Controls.spinBoxNBins->setValue(m_DefaultNBins); m_Controls.spinBoxNBins->setMinimum(m_MinNBins); m_Controls.spinBoxNBins->setMaximum(m_MaxNBins); m_Controls.chartWidget->SetXAxisLabel("Gray value"); m_Controls.chartWidget->SetYAxisLabel("Frequency"); SetGUIElementsEnabled(false); CreateConnections(); } void QmitkHistogramVisualizationWidget::SetHistogram(itk::Statistics::Histogram::ConstPointer histogram, const std::string& dataLabel) { if (histogram == nullptr) return; + bool histogramWasEmpty = m_Histograms.empty(); + if (m_Histograms.find(dataLabel) == m_Histograms.end()) { m_Histograms.insert(std::make_pair(dataLabel, histogram)); m_Controls.chartWidget->AddData2D(ConvertHistogramToPairList(histogram), dataLabel); + m_Controls.chartWidget->SetChartType(dataLabel, QmitkChartWidget::ChartType::bar); } else { m_Histograms[dataLabel] = histogram; m_Controls.chartWidget->UpdateData2D(ConvertHistogramToPairList(histogram), dataLabel); } - m_Controls.chartWidget->SetChartType(dataLabel, QmitkChartWidget::ChartType::bar); - - m_Controls.chartWidget->Show(m_Controls.checkBoxShowSubchart->isChecked()); - SetGUIElementsEnabled(!m_Histograms.empty()); + if (m_Histograms.empty() != histogramWasEmpty) + { + m_Controls.chartWidget->Show(m_Controls.checkBoxShowSubchart->isChecked()); + SetGUIElementsEnabled(!m_Histograms.empty()); + } } void QmitkHistogramVisualizationWidget::Reset() { m_Controls.chartWidget->Clear(); SetGUIElementsEnabled(false); } int QmitkHistogramVisualizationWidget::GetBins() { return m_Controls.spinBoxNBins->value(); } void QmitkHistogramVisualizationWidget::ResetDefault() { m_Controls.checkBoxUseDefaultNBins->setChecked(true); m_Controls.spinBoxNBins->setEnabled(false); m_Controls.spinBoxNBins->setValue(100); m_Controls.checkBoxShowSubchart->setChecked(false); } void QmitkHistogramVisualizationWidget::SetTheme(QmitkChartWidget::ColorTheme style) { m_Controls.chartWidget->SetTheme(style); } void QmitkHistogramVisualizationWidget::CreateConnections() { connect(m_Controls.buttonCopyHistogramToClipboard, &QPushButton::clicked, this, &QmitkHistogramVisualizationWidget::OnClipboardButtonClicked); connect(m_Controls.checkBoxUseDefaultNBins, &QCheckBox::clicked, this, &QmitkHistogramVisualizationWidget::OnDefaultNBinsCheckBoxChanged); connect(m_Controls.spinBoxNBins, &QSpinBox::editingFinished, this, &QmitkHistogramVisualizationWidget::OnNBinsSpinBoxValueChanged); connect(m_Controls.checkBoxShowSubchart, &QCheckBox::clicked, this, &QmitkHistogramVisualizationWidget::OnShowSubchartCheckBoxChanged); connect(m_Controls.checkBoxViewMinMax, &QCheckBox::clicked, this, &QmitkHistogramVisualizationWidget::OnViewMinMaxCheckBoxChanged); connect(m_Controls.doubleSpinBoxMaxValue, &QSpinBox::editingFinished, this, &QmitkHistogramVisualizationWidget::OnMaxValueSpinBoxValueChanged); connect(m_Controls.doubleSpinBoxMinValue, &QSpinBox::editingFinished, this, &QmitkHistogramVisualizationWidget::OnMinValueSpinBoxValueChanged); } void QmitkHistogramVisualizationWidget::SetGUIElementsEnabled(bool enabled) { this->setEnabled(enabled); m_Controls.tabWidgetPlot->setEnabled(enabled); m_Controls.checkBoxShowSubchart->setEnabled(enabled); m_Controls.checkBoxUseDefaultNBins->setEnabled(enabled); m_Controls.spinBoxNBins->setEnabled(!m_Controls.checkBoxUseDefaultNBins->isChecked()); m_Controls.buttonCopyHistogramToClipboard->setEnabled(enabled); m_Controls.checkBoxViewMinMax->setEnabled(enabled); m_Controls.doubleSpinBoxMaxValue->setEnabled(m_Controls.checkBoxViewMinMax->isChecked()); m_Controls.doubleSpinBoxMinValue->setEnabled(m_Controls.checkBoxViewMinMax->isChecked()); } std::vector< std::pair > QmitkHistogramVisualizationWidget::ConvertHistogramToPairList(itk::Statistics::Histogram::ConstPointer histogram) const { std::map histogramMap; if (histogram) { auto endIt = histogram->End(); auto it = histogram->Begin(); // generating Lists of measurement and frequencies for (; it != endIt; ++it) { double frequency = it.GetFrequency(); double measurement = it.GetMeasurementVector()[0]; histogramMap.emplace(measurement, frequency); } } std::vector< std::pair > histogram_list; for(auto iter = histogramMap.begin(); iter != histogramMap.end(); ++iter) histogram_list.emplace_back( iter->first, iter->second ); return histogram_list; } void QmitkHistogramVisualizationWidget::OnClipboardButtonClicked() { if (!m_Histograms.empty()) { QString clipboard; for (const auto& histogram : m_Histograms) { clipboard.append(QString::fromStdString(histogram.first)); clipboard.append("Measurement \t Frequency\n"); auto iter = histogram.second->Begin(); auto iterEnd = histogram.second->End(); for (; iter != iterEnd; ++iter) { clipboard = clipboard.append("%L1 \t %L2\n") .arg(iter.GetMeasurementVector()[0], 0, 'f', 2) .arg(iter.GetFrequency()); } clipboard.append("\n\n"); } QApplication::clipboard()->clear(); QApplication::clipboard()->setText(clipboard, QClipboard::Clipboard); } } void QmitkHistogramVisualizationWidget::OnDefaultNBinsCheckBoxChanged() { if (m_Controls.checkBoxUseDefaultNBins->isChecked()) { m_Controls.spinBoxNBins->setEnabled(false); if (m_Controls.spinBoxNBins->value() != static_cast(m_DefaultNBins) ) { m_Controls.spinBoxNBins->setValue(m_DefaultNBins); OnNBinsSpinBoxValueChanged(); } } else { m_Controls.spinBoxNBins->setEnabled(true); } } void QmitkHistogramVisualizationWidget::OnNBinsSpinBoxValueChanged() { emit RequestHistogramUpdate(m_Controls.spinBoxNBins->value()); } void QmitkHistogramVisualizationWidget::OnShowSubchartCheckBoxChanged() { m_Controls.chartWidget->Show(m_Controls.checkBoxShowSubchart->isChecked()); } void QmitkHistogramVisualizationWidget::OnViewMinMaxCheckBoxChanged() { double min = itk::NumericTraits::max(); double max = itk::NumericTraits::NonpositiveMin(); for (const auto& histogram : m_Histograms) { auto aMin = histogram.second->GetBinMin(0, 0); if (min > aMin) min = aMin; auto maxVector = histogram.second->GetDimensionMaxs(0); if (m_Controls.checkBoxUseDefaultNBins->isChecked()) + { if (max < maxVector[m_DefaultNBins - 1]) max = maxVector[m_DefaultNBins - 1]; + } else + { if (max < maxVector[m_Controls.spinBoxNBins->value() - 1]) max = maxVector[m_Controls.spinBoxNBins->value() - 1]; + } } if (!m_Controls.checkBoxViewMinMax->isChecked()) { m_Controls.doubleSpinBoxMaxValue->setEnabled(false); m_Controls.doubleSpinBoxMinValue->setEnabled(false); m_Controls.chartWidget->Reload(); } else { m_Controls.doubleSpinBoxMinValue->setMinimum(min); m_Controls.doubleSpinBoxMinValue->setValue(min); m_Controls.doubleSpinBoxMaxValue->setMaximum(max); m_Controls.doubleSpinBoxMaxValue->setValue(max); m_Controls.doubleSpinBoxMaxValue->setEnabled(true); m_Controls.doubleSpinBoxMinValue->setEnabled(true); } } void QmitkHistogramVisualizationWidget::OnMinValueSpinBoxValueChanged() { m_Controls.doubleSpinBoxMaxValue->setMinimum(m_Controls.doubleSpinBoxMinValue->value()+1); m_Controls.chartWidget->SetMinMaxValueXView(m_Controls.doubleSpinBoxMinValue->value(),m_Controls.doubleSpinBoxMaxValue->value()); m_Controls.chartWidget->Show(); } void QmitkHistogramVisualizationWidget::OnMaxValueSpinBoxValueChanged() { m_Controls.doubleSpinBoxMinValue->setMaximum(m_Controls.doubleSpinBoxMaxValue->value()-1); m_Controls.chartWidget->SetMinMaxValueXView(m_Controls.doubleSpinBoxMinValue->value(),m_Controls.doubleSpinBoxMaxValue->value()); m_Controls.chartWidget->Show(); } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp index e1a1c1261f..8771895c49 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,378 +1,379 @@ /*============================================================================ 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 "QmitkImageStatisticsView.h" #include // berry includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mitkPlanarFigureMaskGenerator.h" #include "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainerManager.h" #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; QmitkImageStatisticsView::~QmitkImageStatisticsView() { if (nullptr != m_selectedPlanarFigure) { m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); } } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { m_Controls.setupUi(parent); m_Controls.widget_histogram->SetTheme(GetColorTheme()); m_Controls.widget_intensityProfile->SetTheme(GetColorTheme()); m_Controls.groupBox_histogram->setVisible(true); m_Controls.groupBox_intensityProfile->setVisible(false); m_Controls.label_currentlyComputingStatistics->setVisible(false); m_Controls.sliderWidget_histogram->setPrefix("Time: "); m_Controls.sliderWidget_histogram->setDecimals(0); m_Controls.sliderWidget_histogram->setVisible(false); m_Controls.sliderWidget_intensityProfile->setPrefix("Time: "); m_Controls.sliderWidget_intensityProfile->setDecimals(0); m_Controls.sliderWidget_intensityProfile->setVisible(false); ResetGUI(); m_DataGenerator = new QmitkImageStatisticsDataGenerator(parent); m_DataGenerator->SetDataStorage(this->GetDataStorage()); m_DataGenerator->SetAutoUpdate(true); m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); + m_Controls.widget_histogram->SetTheme(GetColorTheme()); CreateConnections(); } void QmitkImageStatisticsView::CreateConnections() { connect(m_Controls.checkBox_ignoreZero, &QCheckBox::stateChanged, this, &QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged); connect(m_Controls.buttonSelection, &QAbstractButton::clicked, this, &QmitkImageStatisticsView::OnButtonSelectionPressed); connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, this, &QmitkImageStatisticsView::OnRequestHistogramUpdate); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::DataGenerationStarted, this, &QmitkImageStatisticsView::OnGenerationStarted); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::GenerationFinished, this, &QmitkImageStatisticsView::OnGenerationFinished); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::JobError, this, &QmitkImageStatisticsView::OnJobError); } void QmitkImageStatisticsView::UpdateIntensityProfile() { m_Controls.groupBox_intensityProfile->setVisible(false); if (m_selectedImageNodes.size()==1) { //only supported for one image and roi currently auto image = dynamic_cast(m_selectedImageNodes.front()->GetData()); if (m_selectedPlanarFigure.IsNotNull()) { if (!m_selectedPlanarFigure->IsClosed()) { mitk::Image::Pointer inputImage; if (image->GetDimension() == 4) { m_Controls.sliderWidget_intensityProfile->setVisible(true); unsigned int maxTimestep = image->GetTimeSteps(); m_Controls.sliderWidget_intensityProfile->setMaximum(maxTimestep - 1); // Intensity profile can only be calculated on 3D, so extract if 4D mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); int currentTimestep = static_cast(m_Controls.sliderWidget_intensityProfile->value()); timeSelector->SetInput(image); timeSelector->SetTimeNr(currentTimestep); timeSelector->Update(); inputImage = timeSelector->GetOutput(); } else { m_Controls.sliderWidget_intensityProfile->setVisible(false); inputImage = image; } auto intensityProfile = mitk::ComputeIntensityProfile(inputImage, m_selectedPlanarFigure); m_Controls.groupBox_intensityProfile->setVisible(true); m_Controls.widget_intensityProfile->Reset(); m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), "Intensity Profile of " + m_selectedImageNodes.front()->GetName()); } } } } void QmitkImageStatisticsView::UpdateHistogramWidget() { m_Controls.groupBox_histogram->setVisible(true); if (m_selectedImageNodes.size() == 1 && m_selectedMaskNodes.size()<=1) { //currently only supported for one image and roi due to histogram widget limitations. auto imageNode = m_selectedImageNodes.front(); const mitk::DataNode* roiNode = nullptr; if (!m_selectedMaskNodes.empty()) { roiNode = m_selectedMaskNodes.front(); } auto statisticsNode = m_DataGenerator->GetLatestResult(imageNode, roiNode, true); if (statisticsNode.IsNotNull()) { auto statistics = dynamic_cast(statisticsNode->GetData()); if (statistics) { std::stringstream label; label << "Histogram " << imageNode->GetName(); if (imageNode->GetData()->GetTimeSteps() > 1) { label << "[0]"; } if (roiNode) { label << " with " << roiNode->GetName(); } - m_Controls.widget_histogram->SetTheme(GetColorTheme()); m_Controls.widget_histogram->SetHistogram(statistics->GetHistogramForTimeStep(0), label.str()); } m_Controls.groupBox_histogram->setVisible(statisticsNode.IsNotNull()); } } } QmitkChartWidget::ColorTheme QmitkImageStatisticsView::GetColorTheme() const { ctkPluginContext *context = berry::WorkbenchPlugin::GetDefault()->GetPluginContext(); ctkServiceReference styleManagerRef = context->getServiceReference(); if (styleManagerRef) { auto styleManager = context->getService(styleManagerRef); if (styleManager->GetStyle().name == "Dark") { return QmitkChartWidget::ColorTheme::darkstyle; } else { return QmitkChartWidget::ColorTheme::lightstyle; } } return QmitkChartWidget::ColorTheme::darkstyle; } void QmitkImageStatisticsView::ResetGUI() { m_Controls.widget_statistics->Reset(); m_Controls.widget_statistics->setEnabled(false); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->setEnabled(false); + m_Controls.widget_histogram->SetTheme(GetColorTheme()); } void QmitkImageStatisticsView::OnGenerationStarted(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) { m_Controls.label_currentlyComputingStatistics->setVisible(true); } void QmitkImageStatisticsView::OnGenerationFinished() { m_Controls.label_currentlyComputingStatistics->setVisible(false); mitk::StatusBar::GetInstance()->Clear(); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnJobError(QString error, const QmitkDataGenerationJobBase* /*failedJob*/) { mitk::StatusBar::GetInstance()->DisplayErrorText(error.toStdString().c_str()); MITK_WARN << "Error when calculating statistics: " << error; } void QmitkImageStatisticsView::OnRequestHistogramUpdate(unsigned int nbins) { m_Controls.widget_statistics->SetHistogramNBins(nbins); m_DataGenerator->SetHistogramNBins(nbins); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged(int state) { auto ignoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; m_Controls.widget_statistics->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); m_DataGenerator->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnButtonSelectionPressed() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(nullptr, "Select input for the statistic","You may select images and ROIs to compute their statistic. ROIs may be segmentations or planar figures."); dialog->SetDataStorage(GetDataStorage()); dialog->SetSelectionCheckFunction(CheckForSameGeometry()); // set predicates auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); auto isImageOrMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isMaskOrPlanarFigurePredicate, isImagePredicate); dialog->SetNodePredicate(isImageOrMaskOrPlanarFigurePredicate); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); dialog->SetCurrentSelection(m_SelectedNodeList); if (dialog->exec()) { std::vector imageNodes; std::vector maskNodes; m_SelectedNodeList = dialog->GetSelectedNodes(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); for (const auto& node : m_SelectedNodeList) { if (isImagePredicate->CheckNode(node)) { imageNodes.push_back(node.GetPointer()); } else { maskNodes.push_back(node.GetPointer()); } } m_selectedImageNodes = imageNodes; m_selectedMaskNodes = maskNodes; if (nullptr != m_selectedPlanarFigure) { m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); m_selectedPlanarFigure = nullptr; } mitk::PlanarFigure* maskPlanarFigure = nullptr; if (m_selectedMaskNodes.size() == 1) { //currently we support this only with one ROI selected maskPlanarFigure = dynamic_cast(m_selectedMaskNodes.front()->GetData()); } if (nullptr != maskPlanarFigure) { m_selectedPlanarFigure = maskPlanarFigure; ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction(this, &QmitkImageStatisticsView::UpdateIntensityProfile); m_PlanarFigureObserverTag = m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); this->UpdateIntensityProfile(); } m_Controls.widget_statistics->SetImageNodes(m_selectedImageNodes); m_Controls.widget_statistics->SetMaskNodes(m_selectedMaskNodes); m_DataGenerator->SetAutoUpdate(false); m_DataGenerator->SetImageNodes(m_selectedImageNodes); m_DataGenerator->SetROINodes(m_selectedMaskNodes); m_DataGenerator->Generate(); m_DataGenerator->SetAutoUpdate(true); m_Controls.widget_statistics->setEnabled(!m_selectedImageNodes.empty()); } delete dialog; } QmitkNodeSelectionDialog::SelectionCheckFunctionType QmitkImageStatisticsView::CheckForSameGeometry() const { auto lambda = [](const QmitkNodeSelectionDialog::NodeList& nodes) { if (nodes.empty()) { return std::string(); } const mitk::Image* imageNodeData = nullptr; for (auto& node : nodes) { imageNodeData = dynamic_cast(node->GetData()); if (imageNodeData) { break; } } if (imageNodeData == nullptr) { std::stringstream ss; ss << "

Select at least one image.

"; return ss.str(); } auto imageGeoPredicate = mitk::NodePredicateGeometry::New(imageNodeData->GetGeometry()); for (auto& rightNode : nodes) { if (imageNodeData != rightNode->GetData()) { bool sameGeometry = true; if (dynamic_cast(rightNode->GetData())) { sameGeometry = imageGeoPredicate->CheckNode(rightNode); } else { const mitk::PlanarFigure* planar2 = dynamic_cast(rightNode->GetData()); if (planar2) { sameGeometry = mitk::PlanarFigureMaskGenerator::CheckPlanarFigureIsNotTilted(planar2->GetPlaneGeometry(), imageNodeData->GetGeometry()); } } if (!sameGeometry) { std::stringstream ss; ss << "

Invalid selection: All selected nodes must have the same geometry.

Differing node i.a.: \""; ss << rightNode->GetName() <<"\"

"; return ss.str(); } } } return std::string(); }; return lambda; }