diff --git a/Modules/ModelFit/files.cmake b/Modules/ModelFit/files.cmake index 4db6c02121..737f34fe1a 100644 --- a/Modules/ModelFit/files.cmake +++ b/Modules/ModelFit/files.cmake @@ -1,75 +1,76 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES Common/mitkExtractTimeGrid.cpp Common/mitkTimeGridHelper.cpp Common/mitkMaskedDynamicImageStatisticsGenerator.cpp Common/mitkModelFitConstants.cpp Common/mitkModelFitParameter.cpp Common/mitkModelFitCmdAppsHelper.cpp Common/mitkParameterFitImageGeneratorBase.cpp Common/mitkPixelBasedParameterFitImageGenerator.cpp Common/mitkROIBasedParameterFitImageGenerator.cpp Common/mitkModelFitInfo.cpp Common/mitkModelFitStaticParameterMap.cpp Common/mitkModelGenerator.cpp Common/mitkModelFitUIDHelper.cpp Common/mitkModelFitResultHelper.cpp Common/mitkScalarListLookupTable.cpp Common/mitkScalarListLookupTableProperty.cpp Common/mitkScalarListLookupTablePropertySerializer.cpp Common/mitkIModelFitProvider.cpp Common/mitkModelFitParameterValueExtraction.cpp Common/mitkBinaryImageToLabelSetImageFilter.cpp Common/mitkFormulaParser.cpp Common/mitkFresnel.cpp + Common/mitkModelFitPlotDataHelper.cpp Functors/mitkSimpleFunctorBase.cpp Functors/mitkSimpleFunctorPolicy.cpp Functors/mitkChiSquareFitCostFunction.cpp Functors/mitkReducedChiSquareFitCostFunction.cpp Functors/mitkConstraintCheckerBase.cpp Functors/mitkSimpleBarrierConstraintChecker.cpp Functors/mitkSquaredDifferencesFitCostFunction.cpp Functors/mitkSumOfSquaredDifferencesFitCostFunction.cpp Functors/mitkMVConstrainedCostFunctionDecorator.cpp Functors/mitkMVModelFitCostFunction.cpp Functors/mitkNormalizedSumOfSquaredDifferencesFitCostFunction.cpp Functors/mitkSVModelFitCostFunction.cpp Functors/mitkModelFitFunctorBase.cpp Functors/mitkLevenbergMarquardtModelFitFunctor.cpp Functors/mitkDummyModelFitFunctor.cpp Functors/mitkModelFitInfoSignalGenerationFunctor.cpp Functors/mitkIndexedValueFunctorPolicy.cpp Functors/mitkModelDataGenerationFunctor.cpp Models/mitkModelBase.cpp Models/mitkModelFactoryBase.cpp Models/mitkModelParameterizerBase.cpp Models/mitkLinearModel.cpp Models/mitkLinearModelFactory.cpp Models/mitkInitialParameterizationDelegateBase.cpp Models/mitkImageBasedParameterizationDelegate.cpp Models/mitkGenericParamModel.cpp Models/mitkGenericParamModelFactory.cpp Models/mitkGenericParamModelParameterizer.cpp Models/mitkValueBasedParameterizationDelegate.cpp Models/mitkT2DecayModel.cpp Models/mitkT2DecayModelFactory.cpp Models/mitkT2DecayModelParameterizer.cpp TestingHelper/mitkTestArtifactGenerator.cpp TestingHelper/mitkTestModel.cpp TestingHelper/mitkTestModelFactory.cpp ) set(TPP_FILES include/itkMultiOutputNaryFunctorImageFilter.tpp include/itkMaskedStatisticsImageFilter.hxx include/itkMaskedNaryStatisticsImageFilter.hxx include/mitkModelFitProviderBase.tpp ) set(HXX_FILES ) set(MOC_H_FILES ) diff --git a/Modules/ModelFit/include/mitkModelFitPlotDataHelper.h b/Modules/ModelFit/include/mitkModelFitPlotDataHelper.h new file mode 100644 index 0000000000..d1824b2f04 --- /dev/null +++ b/Modules/ModelFit/include/mitkModelFitPlotDataHelper.h @@ -0,0 +1,103 @@ +/*=================================================================== + +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 __MITK_MODEL_FIT_PLOT_DATA_HELPER_H_ +#define __MITK_MODEL_FIT_PLOT_DATA_HELPER_H_ + +#include + +#include "mitkPoint.h" +#include "mitkModelBase.h" + +#include "MitkModelFitExports.h" + +namespace mitk +{ + + class ModelParameterizerBase; + class Image; + + namespace modelFit + { + class ModelFitInfo; + + } + + using PlotDataValues = std::vector>; + + /** Simple helper structure that represents a curve for plots and its generation time*/ + struct MITKMODELFIT_EXPORT PlotDataCurve + { + /** values of the curve */ + PlotDataValues values; + + /** Timestamp indicating the last change of the values.*/ + itk::TimeStamp time; + + PlotDataCurve() = default; + PlotDataCurve(const PlotDataValues& values); + PlotDataCurve(const PlotDataCurve& other); + PlotDataCurve(PlotDataCurve&& other); + + PlotDataCurve& operator=(const PlotDataCurve& rhs); + PlotDataCurve& operator=(PlotDataCurve&& rhs) noexcept; + + void Reset(); + }; + + /** Collection of plot curves, e.g. every plot curve for a certain world coordinate position*/ + using PlotDataCurveCollection = std::map; + + using PlotDataCurveCollectionMap = std::map; + + + /** Function generates curve data for the signal defined by the passed informations. + @param position The position in world coordinates the curve should be generated for. + @param fitInfo Pointer to the fit info that defines the model/fit that produces the signal. + @param timeGrid Defines the time grid of the generated signal. + @param parameterizer Pointer to a parameterizer instance that is used to configure the model to generate the signal. + If pointer is not set. The default parameterizer based on the fitInfo instance will be used. + @pre position must be within the model fit input image + @pre fitInfo must be a valid pointer. + */ + MITKMODELFIT_EXPORT PlotDataCurve + GenerateModelSignalPlotData(const mitk::Point3D& position, const mitk::modelFit::ModelFitInfo* fitInfo, const mitk::ModelBase::TimeGridType& timeGrid, mitk::ModelParameterizerBase* parameterizer = nullptr); + + /** Function generates curve data for all additinal inputs (e.g. ROI signal, AIF) + stored in the fit information. The keys in the map are the same keys like in the fitInfo. + @param position The position in world coordinates the curve should be generated for. + @param fitInfo Pointer to the fit info that defines the model/fit that produces the signal. + @param timeGrid Defines the time grid of the generated signal. + @pre position must be within the model fit input image + @pre fitInfo must be a valid pointer. + */ + MITKMODELFIT_EXPORT PlotDataCurveCollection + GenerateAdditionalModelFitPlotData(const mitk::Point3D& position, const mitk::modelFit::ModelFitInfo* fitInfo, const mitk::ModelBase::TimeGridType& timeGrid); + + /** Function generates curve data for a given image instance. + @param position The position in world coordinates the curve should be generated for. + @param image Pointer to the image the signal should be extracted from. + @param timeGrid Defines the time grid of the generated signal. + @pre position must be within the model fit input image + @pre image must be a valid pointer. + @pre image time steps must equal the timeGrid size. + */ + MITKMODELFIT_EXPORT PlotDataCurve + GenerateImageSamplePlotData(const mitk::Point3D& position, const mitk::Image* image, const mitk::ModelBase::TimeGridType& timeGrid); + +} + +#endif diff --git a/Modules/ModelFit/src/Common/mitkModelFitPlotDataHelper.cpp b/Modules/ModelFit/src/Common/mitkModelFitPlotDataHelper.cpp new file mode 100644 index 0000000000..a01014fc96 --- /dev/null +++ b/Modules/ModelFit/src/Common/mitkModelFitPlotDataHelper.cpp @@ -0,0 +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 "mitkModelFitPlotDataHelper.h" + +#include "mitkExceptionMacro.h" +#include "mitkImage.h" +#include "mitkModelFitParameterValueExtraction.h" +#include "mitkModelGenerator.h" + +#include "mitkFormulaParser.h" + +mitk::PlotDataCurve::PlotDataCurve(const PlotDataValues& values) : values(values) +{ + time.Modified(); +}; + +mitk::PlotDataCurve::PlotDataCurve(const PlotDataCurve& other) : values(other.values), time(other.time) +{ +}; + +mitk::PlotDataCurve::PlotDataCurve(PlotDataCurve&& other) : values(std::move(other.values)) +{ + this->time = other.time; + other.time = itk::TimeStamp(); +}; + +mitk::PlotDataCurve & mitk::PlotDataCurve::operator=(const PlotDataCurve& rhs) +{ + this->values = rhs.values; + this->time = rhs.time; + return *this; +}; + +mitk::PlotDataCurve& mitk::PlotDataCurve::operator=(PlotDataCurve&& rhs) noexcept +{ + this->values = std::move(rhs.values); + this->time = rhs.time; + rhs.time = itk::TimeStamp(); + + return *this; +}; + +void mitk::PlotDataCurve::Reset() +{ + this->values.clear(); + this->time = itk::TimeStamp(); +} + +/** Helper function that generates the curve based on a stored and on the fly parsed function string.*/ +mitk::PlotDataCurve +CalcSignalFromFunction(const mitk::Point3D& position, const mitk::modelFit::ModelFitInfo* fitInfo, const mitk::ModelBase::TimeGridType& timeGrid) +{ + if (!fitInfo) + { + mitkThrow() << "Cannot calc model curve from function for given fit. Passed ModelFitInfo instance is nullptr."; + } + + mitk::Image::Pointer inputImage = fitInfo->inputImage; + assert(inputImage.IsNotNull()); + + mitk::PlotDataCurve result; + + // Calculate index + ::itk::Index<3> index; + mitk::BaseGeometry::Pointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); + + geometry->WorldToIndex(position, index); + + mitk::ParameterValueMapType parameterMap = mitk::ExtractParameterValueMapFromModelFit(fitInfo, position); + + mitk::FormulaParser parser(¶meterMap); + + for (unsigned int t = 0; t < timeGrid.size(); ++t) + { + // Set up static parameters + for (const auto& var : fitInfo->staticParamMap) + { + const auto& list = var.second; + + if (list.size() == 1) + { + parameterMap[var.first] = list.front(); + } + else + { + parameterMap[var.first] = list.at(t); + } + } + + // Calculate curve data + double x = timeGrid[t]; + parameterMap[fitInfo->x] = x; + + double y = parser.parse(fitInfo->function); + result.values.push_back(std::make_pair(x, y)); + } + + result.time.Modified(); + return result; +} + +/** Helper function that generates the curve based on the model specified by the fit info.*/ +mitk::PlotDataCurve +CalcSignalFromModel(const mitk::Point3D& position, const mitk::modelFit::ModelFitInfo* fitInfo, const mitk::ModelParameterizerBase* parameterizer = nullptr) +{ + assert(fitInfo); + + if (!parameterizer) + { + parameterizer = mitk::ModelGenerator::GenerateModelParameterizer(*fitInfo); + } + + mitk::Image::Pointer inputImage = fitInfo->inputImage; + assert(inputImage.IsNotNull()); + + // Calculate index + ::itk::Index<3> index; + mitk::BaseGeometry::Pointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); + + geometry->WorldToIndex(position, index); + + //model generation + mitk::ModelBase::Pointer model = parameterizer->GenerateParameterizedModel(index); + + mitk::ParameterValueMapType parameterMap = mitk::ExtractParameterValueMapFromModelFit(fitInfo, position); + + mitk::ModelBase::ParametersType paramArray = mitk::ConvertParameterMapToParameterVector(parameterMap, model); + + mitk::ModelBase::ModelResultType curveDataY = model->GetSignal(paramArray); + mitk::PlotDataCurve result; + mitk::ModelBase::TimeGridType timeGrid = model->GetTimeGrid(); + + for (unsigned int t = 0; t < timeGrid.size(); ++t) + { + double x = timeGrid[t]; + double y = curveDataY[t]; + result.values.push_back(std::make_pair(x, y)); + } + + result.time.Modified(); + return result; +} + +mitk::PlotDataCurve +mitk::GenerateModelSignalPlotData(const mitk::Point3D& position, const mitk::modelFit::ModelFitInfo* fitInfo, const mitk::ModelBase::TimeGridType& timeGrid, mitk::ModelParameterizerBase* parameterizer) +{ + if (!fitInfo) + { + mitkThrow() << "Cannot calc model curve from function for given fit. Passed ModelFitInfo instance is nullptr."; + } + + PlotDataCurve result; + + if (!parameterizer) + { + parameterizer = ModelGenerator::GenerateModelParameterizer(*fitInfo); + } + + if (parameterizer) + { + // Use model instead of formula parser + parameterizer->SetDefaultTimeGrid(timeGrid); + result = CalcSignalFromModel(position, fitInfo, parameterizer); + } + else + { + // Use formula parser to parse function string + try + { + result = CalcSignalFromFunction(position, fitInfo, timeGrid); + } + catch (const mitk::FormulaParserException& e) + { + MITK_ERROR << "Error while parsing modelfit function: " << e.what(); + } + } + + return result; +} + +mitk::PlotDataCurveCollection +mitk::GenerateAdditionalModelFitPlotData(const mitk::Point3D& position, const mitk::modelFit::ModelFitInfo* fitInfo, const mitk::ModelBase::TimeGridType& timeGrid) +{ + if (!fitInfo) + { + mitkThrow() << "Cannot calc model curve from function for given fit. Passed ModelFitInfo instance is nullptr."; + } + + mitk::PlotDataCurveCollection result; + + for (const auto& additionalInput : fitInfo->inputData.GetLookupTable()) + { + if (additionalInput.second.size() != timeGrid.size()) + { + MITK_ERROR << + "Error while refreshing input data for visualization. Size of data and input image time grid differ. Invalid data name: " + << additionalInput.first; + } + else + { + mitk::PlotDataCurve pointData; + + for (unsigned int t = 0; t < timeGrid.size(); ++t) + { + const double x = timeGrid[t]; + const double y = additionalInput.second[t]; + pointData.values.push_back(std::make_pair(x, y)); + } + pointData.time.Modified(); + result.emplace(additionalInput.first, std::move(pointData)); + } + } + + return result; +} + +mitk::PlotDataCurve +mitk::GenerateImageSamplePlotData(const mitk::Point3D& position, const mitk::Image* image, const mitk::ModelBase::TimeGridType& timeGrid) +{ + if (!image) + { + mitkThrow() << "Cannot generate sample plot data. Passed image instance is nullptr."; + } + + mitk::PlotDataCurve result; + + for (unsigned int t = 0; t < timeGrid.size(); ++t) + { + const double x = timeGrid[t]; + const double y = ReadVoxel(image, position, t); + result.values.push_back(std::make_pair(x, y)); + } + + result.time.Modified(); + return result; +} diff --git a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp index 7a5c2abbab..8b2b5ca27a 100644 --- a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp +++ b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp @@ -1,1342 +1,1203 @@ /*=================================================================== 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. ===================================================================*/ // Blueberry #include #include #include // mitk #include // Qt #include #include #include +#include #include +#include "QmitkPlotWidget.h" -#include "mitkFormulaParser.h" #include "mitkScalarListLookupTableProperty.h" #include "mitkModelFitConstants.h" #include "mitkExtractTimeGrid.h" #include "mitkModelGenerator.h" #include "mitkModelFitException.h" #include "mitkModelFitParameterValueExtraction.h" +#include "mitkModelFitPlotDataHelper.h" + #include "ModelFitInspectorView.h" #include + const std::string ModelFitInspectorView::VIEW_ID = "org.mitk.gui.gt.fit.inspector"; const unsigned int ModelFitInspectorView::INTERPOLATION_STEPS = 100; ModelFitInspectorView::ObserverInfo::ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, const std::string& renderWindowName, mitk::IRenderWindowPart* part) : controller(controller), observerTag(observerTag), renderWindowName(renderWindowName), renderWindowPart(part) { } ModelFitInspectorView::ModelFitInspectorView() : m_renderWindowPart(nullptr), m_PendingSliceChangedEvent(false), m_internalUpdateFlag(false), m_currentFit(nullptr), m_currentModelParameterizer(nullptr), m_currentModelProviderService(nullptr), m_currentSelectedTimeStep(0), m_currentSelectedNode(nullptr) { m_currentSelectedPosition.Fill(0.0); m_modelfitList.clear(); } ModelFitInspectorView::~ModelFitInspectorView() { this->RemoveAllObservers(); } bool ModelFitInspectorView::InitObservers() { bool result = true; typedef QHash WindowMapType; WindowMapType windowMap = m_renderWindowPart->GetQmitkRenderWindows(); auto i = windowMap.begin(); while (i != windowMap.end()) { mitk::SliceNavigationController* sliceNavController = i.value()->GetSliceNavigationController(); if (sliceNavController) { itk::ReceptorMemberCommand::Pointer cmdSliceEvent = itk::ReceptorMemberCommand::New(); cmdSliceEvent->SetCallbackFunction(this, &ModelFitInspectorView::OnSliceChanged); int tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), cmdSliceEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); itk::ReceptorMemberCommand::Pointer cmdTimeEvent = itk::ReceptorMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &ModelFitInspectorView::OnSliceChanged); tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometryTimeEvent(nullptr, 0), cmdTimeEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); itk::MemberCommand::Pointer cmdDelEvent = itk::MemberCommand::New(); cmdDelEvent->SetCallbackFunction(this, &ModelFitInspectorView::OnSliceNavigationControllerDeleted); tag = sliceNavController->AddObserver( itk::DeleteEvent(), cmdDelEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); } ++i; result = result && sliceNavController; } return result; } void ModelFitInspectorView::RemoveObservers(const mitk::SliceNavigationController* deletedSlicer) { std::pair < ObserverMapType::const_iterator, ObserverMapType::const_iterator> obsRange = m_ObserverMap.equal_range(deletedSlicer); for (ObserverMapType::const_iterator pos = obsRange.first; pos != obsRange.second; ++pos) { pos->second.controller->RemoveObserver(pos->second.observerTag); } m_ObserverMap.erase(deletedSlicer); } void ModelFitInspectorView::RemoveAllObservers(mitk::IRenderWindowPart* deletedPart) { for (ObserverMapType::const_iterator pos = m_ObserverMap.begin(); pos != m_ObserverMap.end(); ) { ObserverMapType::const_iterator delPos = pos++; if (deletedPart == nullptr || deletedPart == delPos->second.renderWindowPart) { delPos->second.controller->RemoveObserver(delPos->second.observerTag); m_ObserverMap.erase(delPos); } } } void ModelFitInspectorView::OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/) { const mitk::SliceNavigationController* sendingSlicer = dynamic_cast(sender); this->RemoveObservers(sendingSlicer); } void ModelFitInspectorView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_renderWindowPart != renderWindowPart) { m_renderWindowPart = renderWindowPart; if (!InitObservers()) { QMessageBox::information(nullptr, "Error", "Unable to set up the event observers. The " \ "plot will not be triggered on changing the crosshair, " \ "position or time step."); } } } void ModelFitInspectorView::RenderWindowPartDeactivated( mitk::IRenderWindowPart* renderWindowPart) { m_renderWindowPart = nullptr; this->RemoveAllObservers(renderWindowPart); } void ModelFitInspectorView::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); connect(m_Controls.cmbFit, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFitSelectionChanged(int))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.sbFixMin, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.sbFixMax, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.labelFixMin, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.labelFixMax, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.btnScaleToData, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), this, SLOT(OnScaleFixedYChecked(bool))); connect(m_Controls.btnScaleToData, SIGNAL(clicked()), this, SLOT(OnScaleToDataYClicked())); connect(m_Controls.sbFixMax, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingYChanged(double))); connect(m_Controls.sbFixMin, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingYChanged(double))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.sbFixMin_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.sbFixMax_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.labelFixMin_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.labelFixMax_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.btnScaleToData_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), this, SLOT(OnScaleFixedXChecked(bool))); connect(m_Controls.btnScaleToData_x, SIGNAL(clicked()), this, SLOT(OnScaleToDataXClicked())); connect(m_Controls.sbFixMax_x, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingXChanged(double))); connect(m_Controls.sbFixMin_x, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingXChanged(double))); connect(m_Controls.btnExport, SIGNAL(clicked()), this, SLOT(OnExportClicked())); // Add SIGNAL and SLOT for "Copy to clipboard" functionality connect(m_Controls.btnCopyResultsToClipboard, SIGNAL(clicked()), this, SLOT(OnClipboardResultsButtonClicked())); // For some reason this needs to be called to set the plot widget's minimum width to an // acceptable level (since Qwt 6). // Otherwise it tries to keep both axes equal in length, resulting in a minimum width of // 400-500px which is way too much. m_Controls.widgetPlot->GetPlot()->updateAxes(); m_Controls.cmbFit->clear(); mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart(); RenderWindowPartActivated(renderWindowPart); } void ModelFitInspectorView::SetFocus() { } void ModelFitInspectorView::OnScaleFixedYChecked(bool checked) { m_Controls.widgetPlot->GetPlot()->setAxisAutoScale(QwtPlot::yLeft, !checked); if (checked) { OnScaleToDataYClicked(); } m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnScaleFixedXChecked(bool checked) { m_Controls.widgetPlot->GetPlot()->setAxisAutoScale(QwtPlot::xBottom, !checked); if (checked) { OnScaleToDataXClicked(); } m_Controls.widgetPlot->GetPlot()->replot(); }; void CheckYMinMaxFromXYData(const QmitkPlotWidget::XYDataVector& data, double& min, double& max) { for (const auto & pos : data) { if (max < pos.second) { max = pos.second; } if (min > pos.second) { min = pos.second; } } } void CheckXMinMaxFromXYData(const QmitkPlotWidget::XYDataVector& data, double& min, double& max) { for (const auto & pos : data) { if (max < pos.first) { max = pos.first; } if (min > pos.first) { min = pos.first; } } } void ModelFitInspectorView::OnScaleToDataYClicked() { double max = itk::NumericTraits::NonpositiveMin(); double min = itk::NumericTraits::max(); - CheckYMinMaxFromXYData(this->m_ImagePlotCurve, min, max); - CheckYMinMaxFromXYData(this->m_ModelPlotCurve, min, max); + CheckYMinMaxFromXYData(this->m_ImagePlotCurve.values, min, max); + CheckYMinMaxFromXYData(this->m_ModelPlotCurve.values, min, max); - for (CurveMapType::const_iterator pos = this->m_InputDataPlotCurves.begin(); + for (mitk::PlotDataCurveCollection::const_iterator pos = this->m_InputDataPlotCurves.begin(); pos != this->m_InputDataPlotCurves.end(); ++pos) { - CheckYMinMaxFromXYData(pos->second, min, max); + CheckYMinMaxFromXYData(pos->second.values, min, max); } min -= abs(min) * 0.01; max += abs(max) * 0.01; m_Controls.sbFixMax->setValue(max); m_Controls.sbFixMin->setValue(min); }; void ModelFitInspectorView::OnScaleToDataXClicked() { double max = itk::NumericTraits::NonpositiveMin(); double min = itk::NumericTraits::max(); - CheckXMinMaxFromXYData(this->m_ImagePlotCurve, min, max); - CheckXMinMaxFromXYData(this->m_ModelPlotCurve, min, max); + CheckXMinMaxFromXYData(this->m_ImagePlotCurve.values, min, max); + CheckXMinMaxFromXYData(this->m_ModelPlotCurve.values, min, max); - for (CurveMapType::const_iterator pos = this->m_InputDataPlotCurves.begin(); + for (mitk::PlotDataCurveCollection::const_iterator pos = this->m_InputDataPlotCurves.begin(); pos != this->m_InputDataPlotCurves.end(); ++pos) { - CheckXMinMaxFromXYData(pos->second, min, max); + CheckXMinMaxFromXYData(pos->second.values, min, max); } min -= abs(min) * 0.01; max += abs(max) * 0.01; m_Controls.sbFixMax_x->setValue(max); m_Controls.sbFixMin_x->setValue(min); }; void ModelFitInspectorView::OnFixedScalingYChanged(double /*value*/) { m_Controls.widgetPlot->GetPlot()->setAxisScale(QwtPlot::yLeft, m_Controls.sbFixMin->value(), m_Controls.sbFixMax->value()); m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnFixedScalingXChanged(double /*value*/) { m_Controls.widgetPlot->GetPlot()->setAxisScale(QwtPlot::xBottom, m_Controls.sbFixMin_x->value(), m_Controls.sbFixMax_x->value()); m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnExportClicked() { QString fileName = QFileDialog::getSaveFileName(nullptr, tr("Save File")); if (fileName.isEmpty()) { QMessageBox::critical(nullptr, tr("No file selected!"), tr("Cannot export pixel dump. Please selected a file.")); } else { std::ofstream file; std::ios_base::openmode iOpenFlag = std::ios_base::out | std::ios_base::trunc; file.open(fileName.toStdString().c_str(), iOpenFlag); if (!file.is_open()) { QMessageBox::critical(nullptr, tr("Cannot create/open selected file!"), tr("Cannot open or create the selected file. Export will be aborted. Selected file name: ") + fileName); return; } for (int i = 0; i < m_Controls.tableData->columnCount(); ++i) { file << m_Controls.tableData->horizontalHeaderItem(i)->text().toStdString(); if (i < m_Controls.tableData->columnCount() - 1) { file << ","; } } file << std::endl; for (int row = 0; row < m_Controls.tableData->rowCount(); ++row) { for (int i = 0; i < m_Controls.tableData->columnCount(); ++i) { file << m_Controls.tableData->item(row, i)->text().toStdString(); if (i < m_Controls.tableData->columnCount() - 1) { file << ","; } } file << std::endl; } file.close(); } } void ModelFitInspectorView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList& nodes) { if (nodes.size() > 0) { if (nodes.front() != this->m_currentSelectedNode) { m_internalUpdateFlag = true; this->m_currentSelectedNode = nodes.front(); mitk::DataStorage::Pointer storage = this->GetDataStorage(); mitk::NodeUIDType selectedFitUD = ""; bool isModelFitNode = this->m_currentSelectedNode->GetData()->GetPropertyList()->GetStringProperty( mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), selectedFitUD); if (isModelFitNode) { this->m_currentSelectedNode = this->GetParentNode(this->m_currentSelectedNode); } mitk::modelFit::NodeUIDSetType fitUIDs = mitk::modelFit::GetFitUIDsOfNode( this->m_currentSelectedNode, storage); this->m_modelfitList.clear(); this->m_Controls.cmbFit->clear(); for (const auto & fitUID : fitUIDs) { mitk::modelFit::ModelFitInfo::ConstPointer info = mitk::modelFit::CreateFitInfoFromNode(fitUID, storage).GetPointer(); if (info.IsNotNull()) { this->m_modelfitList.insert(std::make_pair(info->uid, info)); QVariant data(info->uid.c_str()); m_Controls.cmbFit->addItem(QString::fromStdString(info->modelName), data); } else { MITK_ERROR << "Was not able to extract model fit information from storage. Node properties in storage may be invalid. Failed fit UID:" << fitUID; } } int cmbIndex = 0; if (m_modelfitList.empty()) { cmbIndex = -1; }; if (isModelFitNode) { //model was selected, thus select this one in combobox QVariant data(selectedFitUD.c_str()); cmbIndex = m_Controls.cmbFit->findData(data); if (cmbIndex == -1) { MITK_WARN << "Model fit Inspector in invalid state. Selected fit seems to be not avaible in plugin selection. Failed fit UID:" << selectedFitUD; } }; m_Controls.cmbFit->setCurrentIndex(cmbIndex); m_internalUpdateFlag = false; m_selectedNodeTime.Modified(); if (cmbIndex == -1) { //only raw 4D data selected. Just update plots for current position m_currentFit = nullptr; m_currentFitTime.Modified(); OnSliceChangedDelayed(); } else { //refresh fit selection (and implicitly update plots) OnFitSelectionChanged(cmbIndex); } } } else { if (this->m_currentSelectedNode.IsNotNull()) { m_internalUpdateFlag = true; this->m_currentSelectedNode = nullptr; this->m_currentFit = nullptr; this->m_modelfitList.clear(); this->m_Controls.cmbFit->clear(); m_internalUpdateFlag = false; m_selectedNodeTime.Modified(); OnFitSelectionChanged(0); } } } mitk::DataNode::ConstPointer ModelFitInspectorView::GetParentNode(mitk::DataNode::ConstPointer node) { if (node.IsNotNull()) { mitk::DataStorage::SetOfObjects::ConstPointer parentNodeList = GetDataStorage()->GetSources(node); if (parentNodeList->size() > 0) { return parentNodeList->front().GetPointer(); } } return mitk::DataNode::ConstPointer(); } void ModelFitInspectorView::ValidateAndSetCurrentPosition() { mitk::Point3D currentSelectedPosition = GetRenderWindowPart()->GetSelectedPosition(nullptr); unsigned int currentSelectedTimestep = m_renderWindowPart->GetTimeNavigationController()->GetTime()-> GetPos(); if (m_currentSelectedPosition != currentSelectedPosition || m_currentSelectedTimeStep != currentSelectedTimestep || 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_currentSelectedTimeStep = currentSelectedTimestep; m_currentPositionTime.Modified(); m_validSelectedPosition = false; mitk::Image::Pointer inputImage = this->GetCurrentInputImage(); if (inputImage.IsNull()) { return; } mitk::BaseGeometry::Pointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep( m_currentSelectedTimeStep); // check for invalid time step if (geometry.IsNull()) { geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); } if (geometry.IsNull()) { return; } m_validSelectedPosition = geometry->IsInside(m_currentSelectedPosition); } } mitk::Image::Pointer ModelFitInspectorView::GetCurrentInputImage() const { mitk::Image::Pointer result = nullptr; if (this->m_currentFit.IsNotNull()) { result = m_currentFit->inputImage; } else if (this->m_currentSelectedNode.IsNotNull()) { result = dynamic_cast(this->m_currentSelectedNode->GetData()); if (result.IsNotNull() && result->GetTimeSteps() <= 1) { //if the image is not dynamic, we can't use it. result = nullptr; } } return result; }; const mitk::ModelBase::TimeGridType ModelFitInspectorView::GetCurrentTimeGrid() const { if (m_currentModelProviderService && m_currentFit.IsNotNull()) { return m_currentModelProviderService->GetVariableGrid(m_currentFit); } else { //fall back if there is no model provider we assume to use the normal time grid. return ExtractTimeGrid(GetCurrentInputImage()); } }; void ModelFitInspectorView::OnSliceChanged(const itk::EventObject&) { // Taken from QmitkStdMultiWidget::HandleCrosshairPositionEvent(). // Since there are always 3 events arriving (one for each render window) every time the slice // or time changes, the slot OnSliceChangedDelayed is triggered - and only if it hasn't been // triggered yet - so it is only executed once for every slice/time change. if (!m_PendingSliceChangedEvent) { m_PendingSliceChangedEvent = true; QTimer::singleShot(0, this, SLOT(OnSliceChangedDelayed())); } } void ModelFitInspectorView::OnSliceChangedDelayed() { m_PendingSliceChangedEvent = false; ValidateAndSetCurrentPosition(); m_Controls.widgetPlot->setEnabled(m_validSelectedPosition); if (m_currentSelectedNode.IsNotNull()) { if (RefreshPlotData()) { RenderPlot(); RenderFitInfo(); } } } +/** Super sample passed time grid with the factor INTERPOLATION_STEPS and interpolates linear in between.*/ mitk::ModelBase::TimeGridType -ModelFitInspectorView::GenerateInterpolatedTimeGrid(const mitk::ModelBase::TimeGridType& grid) -const +GenerateInterpolatedTimeGrid(const mitk::ModelBase::TimeGridType& grid, const unsigned int interpolation_steps = 100) { unsigned int origGridSize = grid.size(); - mitk::ModelBase::TimeGridType interpolatedTimeGrid(((origGridSize - 1) * INTERPOLATION_STEPS) + 1); + mitk::ModelBase::TimeGridType interpolatedTimeGrid(((origGridSize - 1) * interpolation_steps) + 1); for (unsigned int t = 0; t < origGridSize - 1; ++t) { - double delta = (grid[t + 1] - grid[t]) / INTERPOLATION_STEPS; + double delta = (grid[t + 1] - grid[t]) / interpolation_steps; - for (unsigned int i = 0; i < INTERPOLATION_STEPS; ++i) + for (unsigned int i = 0; i < interpolation_steps; ++i) { - interpolatedTimeGrid[(t * INTERPOLATION_STEPS) + i] = grid[t] + i * delta; + interpolatedTimeGrid[(t * interpolation_steps) + i] = grid[t] + i * delta; } } interpolatedTimeGrid[interpolatedTimeGrid.size() - 1] = grid[grid.size() - 1]; return interpolatedTimeGrid; }; - -QmitkPlotWidget::XYDataVector -ModelFitInspectorView::CalcCurveFromModel(const mitk::Point3D& position) -{ - assert(m_currentModelParameterizer.IsNotNull()); - assert(m_currentFit.IsNotNull()); - - mitk::Image::Pointer inputImage = this->GetCurrentInputImage(); - assert(inputImage.IsNotNull()); - - // Calculate index - ::itk::Index<3> index; - mitk::BaseGeometry::Pointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); - - geometry->WorldToIndex(position, index); - - //model generation - mitk::ModelBase::Pointer model = m_currentModelParameterizer->GenerateParameterizedModel(index); - - mitk::ParameterValueMapType parameterMap = mitk::ExtractParameterValueMapFromModelFit(m_currentFit, position); - - mitk::ModelBase::ParametersType paramArray = mitk::ConvertParameterMapToParameterVector(parameterMap, model); - - mitk::ModelBase::ModelResultType curveDataY = model->GetSignal(paramArray); - QmitkPlotWidget::XYDataVector result; - mitk::ModelBase::TimeGridType timeGrid = model->GetTimeGrid(); - - for (unsigned int t = 0; t < timeGrid.size(); ++t) - { - double x = timeGrid[t]; - double y = curveDataY[t]; - result.push_back(std::make_pair(x, y)); - } - - return result; -} - -QmitkPlotWidget::XYDataVector -ModelFitInspectorView::CalcCurveFromFunction(const mitk::Point3D& position, - const mitk::ModelBase::TimeGridType& timeGrid) -{ - assert(m_currentFit.IsNotNull()); - assert(m_renderWindowPart != nullptr); - - mitk::Image::Pointer inputImage = this->GetCurrentInputImage(); - assert(inputImage.IsNotNull()); - - QmitkPlotWidget::XYDataVector result; - - // Calculate index - ::itk::Index<3> index;; - mitk::BaseGeometry::Pointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); - - geometry->WorldToIndex(position, index); - - mitk::ParameterValueMapType parameterMap = mitk::ExtractParameterValueMapFromModelFit(m_currentFit, position); - - mitk::FormulaParser parser(¶meterMap); - unsigned int timestep = m_renderWindowPart->GetTimeNavigationController()->GetTime()-> - GetPos(); - - for (unsigned int t = 0; t < timeGrid.size(); ++t) - { - // Set up static parameters - foreach (const mitk::modelFit::StaticParameterMap::StaticParameterType& var, - m_currentFit->staticParamMap) - { - const std::string& name = var.first; - const mitk::modelFit::StaticParameterMap::ValueType& list = var.second; - - if (list.size() == 1) - { - parameterMap[name] = list.front(); - } - else - { - parameterMap[name] = list.at(timestep); - } - } - - // Calculate curve data - double x = timeGrid[t]; - parameterMap[m_currentFit->x] = x; - - double y = parser.parse(m_currentFit->function); - result.push_back(std::make_pair(x, y)); - } - - return result; -} - void ModelFitInspectorView::OnFitSelectionChanged(int index) { if (!m_internalUpdateFlag) { MITK_DEBUG << "selected fit index: " << index; std::string uid = ""; if (m_Controls.cmbFit->count() > index) { uid = m_Controls.cmbFit->itemData(index).toString().toStdString(); } mitk::modelFit::ModelFitInfo::ConstPointer newFit = nullptr; ModelFitInfoListType::iterator finding = m_modelfitList.find(uid); if (finding != m_modelfitList.end()) { newFit = finding->second; } if (m_currentFit != newFit) { m_currentModelParameterizer = nullptr; m_currentModelProviderService = nullptr; if (newFit.IsNotNull()) { m_currentModelParameterizer = mitk::ModelGenerator::GenerateModelParameterizer(*newFit); m_currentModelProviderService = mitk::ModelGenerator::GetProviderService(newFit->functionClassID); } m_currentFit = newFit; m_currentFitTime.Modified(); OnSliceChangedDelayed(); } } } bool ModelFitInspectorView::RefreshPlotData() { bool changed = false; if (m_currentSelectedNode.IsNull()) { - this->m_ImagePlotCurve.clear(); - this->m_ModelPlotCurve.clear(); + this->m_ImagePlotCurve.Reset(); + this->m_ModelPlotCurve.Reset(); this->m_InputDataPlotCurves.clear(); changed = m_selectedNodeTime > m_lastRefreshTime; m_lastRefreshTime.Modified(); } else { assert(GetRenderWindowPart() != NULL); const mitk::Image* input = GetCurrentInputImage(); const mitk::ModelBase::TimeGridType timeGrid = GetCurrentTimeGrid(); //image data curve if (m_selectedNodeTime > m_lastRefreshTime || m_currentFitTime > m_lastRefreshTime || m_currentPositionTime > m_lastRefreshTime) { - m_ImagePlotCurve.clear(); + m_ImagePlotCurve.Reset(); if (input && m_validSelectedPosition) { - QmitkPlotWidget::XYDataVector pointData; - - for (unsigned int t = 0; t < timeGrid.size(); ++t) - { - double x = timeGrid[t]; - double y = ReadVoxel(input, m_currentSelectedPosition, t); - pointData.push_back(std::make_pair(x, y)); - } - - m_ImagePlotCurve = pointData; + m_ImagePlotCurve = GenerateImageSamplePlotData(m_currentSelectedPosition, input, timeGrid); } changed = true; } //model curve if (m_currentFitTime > m_lastRefreshTime || m_currentPositionTime > m_lastRefreshTime) { - m_ModelPlotCurve.clear(); + m_ModelPlotCurve.Reset(); if (m_currentFit.IsNotNull() && m_validSelectedPosition) { - QmitkPlotWidget::XYDataVector curveData; // Interpolate time grid (x values) so the curve looks smooth - const mitk::ModelBase::TimeGridType interpolatedTimeGrid = GenerateInterpolatedTimeGrid(timeGrid); + const mitk::ModelBase::TimeGridType interpolatedTimeGrid = GenerateInterpolatedTimeGrid(timeGrid, INTERPOLATION_STEPS); - if (m_currentModelParameterizer.IsNotNull()) - { - // Use model instead of formula parser - m_currentModelParameterizer->SetDefaultTimeGrid(interpolatedTimeGrid); - curveData = CalcCurveFromModel(m_currentSelectedPosition); - } - else - { - // Use formula parser to parse function string - try - { - curveData = CalcCurveFromFunction(m_currentSelectedPosition, interpolatedTimeGrid); - } - catch (const mitk::FormulaParserException& e) - { - MITK_ERROR << "Error while parsing modelfit function: " << e.what(); - } - } - - m_ModelPlotCurve = curveData; + m_ModelPlotCurve = GenerateModelSignalPlotData(m_currentSelectedPosition, m_currentFit, interpolatedTimeGrid, m_currentModelParameterizer); } changed = true; } // input data curve if (m_currentFitTime > m_lastRefreshTime) { m_InputDataPlotCurves.clear(); if (m_currentFit.IsNotNull()) { - for (mitk::ScalarListLookupTable::LookupTableType::const_iterator pos = - m_currentFit->inputData.GetLookupTable().begin(); - pos != m_currentFit->inputData.GetLookupTable().end(); ++pos) - { - if (pos->second.size() != timeGrid.size()) - { - MITK_ERROR << - "Error while refreshing input data for visualization. Size of data and input image time grid differ. Invalid data name: " - << pos->first; - } - else - { - QmitkPlotWidget::XYDataVector pointData; - - for (unsigned int t = 0; t < timeGrid.size(); ++t) - { - double x = timeGrid[t]; - double y = pos->second[t]; - pointData.push_back(std::make_pair(x, y)); - } - - m_InputDataPlotCurves.insert(std::make_pair(pos->first, pointData)); - } - } + m_InputDataPlotCurves = GenerateAdditionalModelFitPlotData(m_currentSelectedPosition, m_currentFit, timeGrid); } changed = true; } if (m_selectedNodeTime > m_lastRefreshTime || m_currentFitTime > m_lastRefreshTime || m_currentPositionTime > m_lastRefreshTime) { InitDataTable(); } m_lastRefreshTime.Modified(); } return changed; } void ModelFitInspectorView::RenderFitInfo() { assert(m_renderWindowPart != nullptr); // configure fit information if (m_currentFit.IsNull()) { m_Controls.lFitType->setText(""); m_Controls.lFitUID->setText(""); m_Controls.lModelName->setText(""); m_Controls.lModelType->setText(""); } else { m_Controls.lFitType->setText(QString::fromStdString(m_currentFit->fitType)); m_Controls.lFitUID->setText(QString::fromStdString(m_currentFit->uid)); m_Controls.lModelName->setText(QString::fromStdString(m_currentFit->modelName)); m_Controls.lModelType->setText(QString::fromStdString(m_currentFit->modelType)); } // print results std::stringstream infoOutput; m_Controls.grpParams->setVisible(false); m_Controls.groupSettings->setVisible(false); m_Controls.tableResults->clearContents(); if (m_currentFit.IsNull()) { infoOutput << "No fit selected. Only raw image data is plotted."; } else if (!m_validSelectedPosition) { infoOutput << "Current position is outside of the input image of the selected fit.\nInspector is deactivated."; } else { m_Controls.grpParams->setVisible(true); // Set sorting to false. Wait with sorting until all parameters are set in the table. m_Controls.tableResults->setSortingEnabled(false); m_Controls.tableResults->setRowCount(m_currentFit->GetParameters().size() + m_currentFit->staticParamMap.Size()); unsigned int timestep = m_renderWindowPart->GetTimeNavigationController()->GetTime()->GetPos(); unsigned int rowIndex = 0; assert(GetRenderWindowPart() != NULL); for (mitk::modelFit::ModelFitInfo::ConstIterType paramIter = m_currentFit->GetParameters().begin(); paramIter != m_currentFit->GetParameters().end(); ++paramIter, ++rowIndex) { mitk::modelFit::Parameter::ConstPointer p = static_cast(*paramIter); double value = ReadVoxel(p->image, m_currentSelectedPosition, timestep); std::string paramType = mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_PARAMETER(); if (p->type == mitk::modelFit::Parameter::DerivedType) { paramType = mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_DERIVED_PARAMETER(); } else if (p->type == mitk::modelFit::Parameter::CriterionType) { paramType = mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_CRITERION(); } else if (p->type == mitk::modelFit::Parameter::EvaluationType) { paramType = mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_EVALUATION_PARAMETER(); } QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString(p->name)); m_Controls.tableResults->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(QString::number(value)); m_Controls.tableResults->setItem(rowIndex, 1, newItem); newItem = new QTableWidgetItem(QString::fromStdString(paramType)); m_Controls.tableResults->setItem(rowIndex, 2, newItem); } foreach(const mitk::modelFit::StaticParameterMap::StaticParameterType& var, m_currentFit->staticParamMap) { const std::string& name = var.first; const mitk::modelFit::StaticParameterMap::ValueType& list = var.second; QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString(name)); m_Controls.tableResults->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(QString::number(list.front())); m_Controls.tableResults->setItem(rowIndex, 1, newItem); newItem = new QTableWidgetItem(tr("static")); m_Controls.tableResults->setItem(rowIndex, 2, newItem); ++rowIndex; } // Here the sorting function is enabled and the table entries are sorted alphabetically in descending order. // This way the parameters are always displayed in the same order. m_Controls.tableResults->setSortingEnabled(true); m_Controls.tableResults->sortItems(0, Qt::DescendingOrder); } // configure data table m_Controls.tableInputData->clearContents(); if (m_currentFit.IsNull()) { infoOutput << "No fit selected. Only raw image data is plotted."; } else { m_Controls.groupSettings->setVisible(true); m_Controls.tableInputData->setRowCount(m_InputDataPlotCurves.size()); unsigned int rowIndex = 0; - for (CurveMapType::const_iterator pos = m_InputDataPlotCurves.begin(); + for (mitk::PlotDataCurveCollection::const_iterator pos = m_InputDataPlotCurves.begin(); pos != m_InputDataPlotCurves.end(); ++pos, ++rowIndex) { QColor dataColor; if (pos->first == "ROI") { dataColor = QColor(0, 190, 0); } else { //Use HSV schema of QColor to calculate a different color depending on the //number of already existing free iso lines. dataColor.setHsv(((rowIndex + 1) * 85) % 360, 255, 255); } QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString(pos->first)); m_Controls.tableInputData->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(); newItem->setBackgroundColor(dataColor); m_Controls.tableInputData->setItem(rowIndex, 1, newItem); } } m_Controls.lInfo->setText(QString::fromStdString(infoOutput.str())); } void ModelFitInspectorView::RenderPlot() { m_Controls.widgetPlot->Clear(); std::string xAxis = "Time [s]"; std::string yAxis = "Intensity"; std::string plotTitle = "Raw data plot: no data"; if (m_currentSelectedNode.IsNotNull()) { plotTitle = "Raw data plot: " + m_currentSelectedNode->GetName(); } if (m_currentFit.IsNotNull()) { plotTitle = m_currentFit->modelName.c_str(); xAxis = m_currentFit->xAxisName; if (!m_currentFit->xAxisUnit.empty()) { xAxis += " [" + m_currentFit->xAxisUnit + "]"; } yAxis = m_currentFit->yAxisName; if (!m_currentFit->yAxisUnit.empty()) { yAxis += " [" + m_currentFit->yAxisUnit + "]"; } } m_Controls.widgetPlot->SetAxisTitle(QwtPlot::xBottom, xAxis.c_str()); m_Controls.widgetPlot->SetAxisTitle(QwtPlot::yLeft, yAxis.c_str()); m_Controls.widgetPlot->SetPlotTitle(plotTitle.c_str()); // Draw input data points unsigned int colorIndex = 0; - for (CurveMapType::const_iterator pos = m_InputDataPlotCurves.begin(); + for (mitk::PlotDataCurveCollection::const_iterator pos = m_InputDataPlotCurves.begin(); pos != m_InputDataPlotCurves.end(); ++pos) { QColor dataColor; unsigned int curveId = m_Controls.widgetPlot->InsertCurve(pos->first.c_str()); - m_Controls.widgetPlot->SetCurveData(curveId, pos->second); + m_Controls.widgetPlot->SetCurveData(curveId, pos->second.values); if (pos->first == "ROI") { dataColor = QColor(0, 190, 0); QPen pen; pen.setColor(dataColor); pen.setStyle(Qt::SolidLine); m_Controls.widgetPlot->SetCurvePen(curveId, pen); } else { //Use HSV schema of QColor to calculate a different color depending on the //number of already existing free iso lines. dataColor.setHsv((++colorIndex * 85) % 360, 255, 150); m_Controls.widgetPlot->SetCurvePen(curveId, QPen(Qt::NoPen)); } // QwtSymbol needs to passed as a real pointer from MITK v2013.09.0 on // (QwtPlotCurve deletes it on destruction and assignment). QwtSymbol* dataSymbol = new QwtSymbol(QwtSymbol::Triangle, dataColor, dataColor, QSize(8, 8)); m_Controls.widgetPlot->SetCurveSymbol(curveId, dataSymbol); // Again, there is no way to set a curve's legend attributes via QmitkPlotWidget so this // gets unnecessarily complicated. QwtPlotCurve* measurementCurve = dynamic_cast(m_Controls.widgetPlot-> GetPlot()->itemList(QwtPlotItem::Rtti_PlotCurve).back()); measurementCurve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol); measurementCurve->setLegendIconSize(QSize(8, 8)); } // Draw image points - if (!m_ImagePlotCurve.empty()) + if (!m_ImagePlotCurve.values.empty()) { unsigned int curveId = m_Controls.widgetPlot->InsertCurve("measurement"); - m_Controls.widgetPlot->SetCurveData(curveId, m_ImagePlotCurve); + m_Controls.widgetPlot->SetCurveData(curveId, m_ImagePlotCurve.values); m_Controls.widgetPlot->SetCurvePen(curveId, QPen(Qt::NoPen)); // QwtSymbol needs to passed as a real pointer from MITK v2013.09.0 on // (QwtPlotCurve deletes it on destruction and assignment). QwtSymbol* redSymbol = new QwtSymbol(QwtSymbol::Diamond, QColor(Qt::red), QColor(Qt::red), QSize(8, 8)); m_Controls.widgetPlot->SetCurveSymbol(curveId, redSymbol); // Again, there is no way to set a curve's legend attributes via QmitkPlotWidget so this // gets unnecessarily complicated. QwtPlotCurve* measurementCurve = dynamic_cast(m_Controls.widgetPlot-> GetPlot()->itemList(QwtPlotItem::Rtti_PlotCurve).back()); measurementCurve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol); measurementCurve->setLegendIconSize(QSize(8, 8)); // Highlight the current time step unsigned int timestep = m_renderWindowPart->GetTimeNavigationController()->GetTime()-> GetPos(); - if (timestep < m_ImagePlotCurve.size()) + if (timestep < m_ImagePlotCurve.values.size()) { QwtSymbol* blueSymbol = new QwtSymbol(QwtSymbol::Diamond, QColor(Qt::blue), QColor(Qt::blue), QSize(8, 8)); QwtPlotMarker* marker = new QwtPlotMarker(); marker->setSymbol(blueSymbol); - marker->setValue(m_ImagePlotCurve[timestep].first, m_ImagePlotCurve[timestep].second); + marker->setValue(m_ImagePlotCurve.values[timestep].first, m_ImagePlotCurve.values[timestep].second); marker->attach(m_Controls.widgetPlot->GetPlot()); } - for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.size(); ++i) + for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.values.size(); ++i) { - m_Controls.tableData->item(i, 1)->setText(QString::number(m_ImagePlotCurve[i].second)); + m_Controls.tableData->item(i, 1)->setText(QString::number(m_ImagePlotCurve.values[i].second)); } } //draw model curve - if (!m_ModelPlotCurve.empty()) + if (!m_ModelPlotCurve.values.empty()) { QPen pen; pen.setColor(QColor(Qt::black)); pen.setWidth(2); unsigned int curveId = m_Controls.widgetPlot->InsertCurve("fit"); - m_Controls.widgetPlot->SetCurveData(curveId, m_ModelPlotCurve); + m_Controls.widgetPlot->SetCurveData(curveId, m_ModelPlotCurve.values); m_Controls.widgetPlot->SetCurvePen(curveId, pen); // Manually set the legend attribute to use the symbol as the legend icon and alter its // size. Otherwise it would revert to default which is drawing a square which is the color // of the curve's pen, so in this case none which defaults to black. // Unfortunately, QmitkPlotWidget offers no way to set the legend attribute and icon size so // this looks a bit hacky. QwtPlotCurve* fitCurve = dynamic_cast(m_Controls.widgetPlot->GetPlot()-> itemList(QwtPlotItem::Rtti_PlotCurve).back()); fitCurve->setLegendAttribute(QwtPlotCurve::LegendShowLine); - for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.size(); ++i) + for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.values.size(); ++i) { - m_Controls.tableData->item(i, 2)->setText(QString::number(m_ModelPlotCurve[i * + m_Controls.tableData->item(i, 2)->setText(QString::number(m_ModelPlotCurve.values[i * INTERPOLATION_STEPS].second)); } } QwtLegend* legend = new QwtLegend(); legend->setFrameShape(QFrame::Box); legend->setFrameShadow(QFrame::Sunken); legend->setLineWidth(1); m_Controls.widgetPlot->SetLegend(legend, QwtPlot::BottomLegend); m_Controls.widgetPlot->Replot(); } void ModelFitInspectorView::InitDataTable() { QStringList headers; headers.push_back(QString("Time")); headers.push_back(QString("Input image")); - if (!this->m_ModelPlotCurve.empty()) + if (!this->m_ModelPlotCurve.values.empty()) { headers.push_back(QString("Model")); } - for (CurveMapType::const_iterator pos = this->m_InputDataPlotCurves.begin(); + for (mitk::PlotDataCurveCollection::const_iterator pos = this->m_InputDataPlotCurves.begin(); pos != this->m_InputDataPlotCurves.end(); ++pos) { headers.push_back(QString::fromStdString(pos->first)); } - m_Controls.tableData->setRowCount(m_ImagePlotCurve.size()); + m_Controls.tableData->setRowCount(m_ImagePlotCurve.values.size()); m_Controls.tableData->setColumnCount(headers.size()); m_Controls.tableData->setHorizontalHeaderLabels(headers); m_Controls.tableData->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); - for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.size(); ++i) + for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.values.size(); ++i) { int column = 0; - QTableWidgetItem* newItem = new QTableWidgetItem(QString::number(m_ImagePlotCurve[i].first)); + QTableWidgetItem* newItem = new QTableWidgetItem(QString::number(m_ImagePlotCurve.values[i].first)); m_Controls.tableData->setItem(i, column++, newItem); - newItem = new QTableWidgetItem(QString::number(m_ImagePlotCurve[i].second)); + newItem = new QTableWidgetItem(QString::number(m_ImagePlotCurve.values[i].second)); m_Controls.tableData->setItem(i, column++, newItem); - if (!m_ModelPlotCurve.empty()) + if (!m_ModelPlotCurve.values.empty()) { - newItem = new QTableWidgetItem(QString::number(m_ModelPlotCurve[i * INTERPOLATION_STEPS].second)); + newItem = new QTableWidgetItem(QString::number(m_ModelPlotCurve.values[i * INTERPOLATION_STEPS].second)); m_Controls.tableData->setItem(i, column++, newItem); } - for (CurveMapType::const_iterator pos = this->m_InputDataPlotCurves.begin(); + for (mitk::PlotDataCurveCollection::const_iterator pos = this->m_InputDataPlotCurves.begin(); pos != this->m_InputDataPlotCurves.end(); ++pos, ++column) { - newItem = new QTableWidgetItem(QString::number(pos->second[i].second)); + newItem = new QTableWidgetItem(QString::number(pos->second.values[i].second)); m_Controls.tableData->setItem(i, column, newItem); } } } void ModelFitInspectorView::OnClipboardResultsButtonClicked() { QLocale tempLocal; QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); std::stringstream infoOutput; QString clipboard; QVector< QVector > resultsTable; if (m_currentFit.IsNotNull()) { int rowNumber = m_currentFit->GetParameters().size() + m_currentFit->staticParamMap.Size(); // set headline as in results table: "Name", "Value", "Type" QStringList headlineText; QVector headline; // Create Headline headlineText << "Name" << "Value" << "Type"; for (int i = 0; i < headlineText.size(); i++) { headline.append(headlineText.at(i)); } resultsTable.append(headline); // fill table with values from the displayed results table for (int i = 0; i < rowNumber; i++) { QVector rowValues; for (int j = 0; j < 3; j++) { rowValues.append((m_Controls.tableResults->item(i, j)->text())); } resultsTable.append(rowValues); } // Create output string for clipboard for (int i = 0; i < resultsTable.size(); i++) { for (int t = 0; t < resultsTable.at(i).size(); t++) { clipboard.append(resultsTable.at(i).at(t)); clipboard.append("\t"); } clipboard.append("\n"); } QApplication::clipboard()->setText(clipboard, QClipboard::Clipboard); } else { infoOutput << "Results table is empty."; } QLocale::setDefault(tempLocal); } diff --git a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.h b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.h index 6b57146539..bca597b2a4 100644 --- a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.h +++ b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.h @@ -1,232 +1,227 @@ /*=================================================================== 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 ModelFitInspectorView_h #define ModelFitInspectorView_h // Blueberry //#include #include // mitk #include #include #include "mitkModelFitStaticParameterMap.h" #include "mitkModelParameterizerBase.h" #include "mitkModelFitInfo.h" #include "mitkIModelFitProvider.h" +#include "mitkModelFitPlotDataHelper.h" // Qt #include "ui_ModelFitInspectorViewControls.h" /** * @brief View class defining the UI part of the ModelFitInspector plug-in. */ class ModelFitInspectorView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: ModelFitInspectorView(); ~ModelFitInspectorView(); static const std::string VIEW_ID; protected slots: /** * @brief Triggered when the selection of the "Modelfit" combo box changes. * Sets the selected fit as the current one. * @param index The index (in the combo box) of the selected item. */ void OnFitSelectionChanged(int index); /** * @brief Triggered when the voxel or time step selection changes. * Calculates the curve and points for the current fit if the visualization is running. */ void OnSliceChangedDelayed(); /** Triggered when the selection of "fixed" y axis scaling changes*/ void OnScaleFixedYChecked(bool checked); void OnScaleToDataYClicked(); void OnFixedScalingYChanged(double value); /** Triggered when the selection of "fixed" x axis scaling changes*/ void OnScaleFixedXChecked(bool checked); void OnScaleToDataXClicked(); void OnFixedScalingXChanged(double value); void OnExportClicked(); /** @brief Saves the results table to clipboard */ void OnClipboardResultsButtonClicked(); protected: virtual void CreateQtPartControl(QWidget* parent); virtual void SetFocus(); /** @brief called by QmitkFunctionality when DataManager's selection has changed */ virtual void OnSelectionChanged(berry::IWorkbenchPart::Pointer source, const QList& nodes); /** @brief Calls OnSliceChangedDelayed so the event isn't triggered multiple times. */ void OnSliceChanged(const itk::EventObject& e); void OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/); virtual void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart); virtual void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart); /** Initializes and sets the observers that are used to monitor changes in the selected position or time point in order to actualize the view.h*/ bool InitObservers(); void RemoveObservers(const mitk::SliceNavigationController* deletedSlicer); /** Removes all observers of the deletedPart. If null pointer is passed all observers will be removed.*/ void RemoveAllObservers(mitk::IRenderWindowPart* deletedPart = NULL); /** * @brief Calculates the curve data using the current fit's model and parameterizer. * @param position Indicating the point in the input image for which the model curve should be calculated. * @return A vector of points for the curve data. */ QmitkPlotWidget::XYDataVector CalcCurveFromModel(const mitk::Point3D& position); /** * @brief Calculates the curve data using the current fit's function string. * @param timeGrid The time grid containing the (interpolated) x values for the curve data. * @return A vector of points for the curve data. */ QmitkPlotWidget::XYDataVector CalcCurveFromFunction(const mitk::Point3D& position, const mitk::ModelBase::TimeGridType& timeGrid); /** * @brief Returns the parent node of the given node if it exists. * @param node The node whose parent node should be returned. * @return The parent node of the given node or NULL if it doesn't exist. */ mitk::DataNode::ConstPointer GetParentNode(mitk::DataNode::ConstPointer node); - /** Super sample passed time grid with the factor INTERPOLATION_STEPS and interpolates linear in between.*/ - mitk::ModelBase::TimeGridType GenerateInterpolatedTimeGrid(const mitk::ModelBase::TimeGridType& - grid) const; - /** Sets m_currentSelectedPosition to the current selection and validates if this position is valid * for the input image of the currently selected fit. If it is valid, m_validSelectedPosition is set to true. * If the fit, his input image or geometry is not specified, it will also handled as invalid.*/ void ValidateAndSetCurrentPosition(); /** Returns the current input image. If a current fit is set it will be its input image. * Otherwise it will be the image stored in the currently selected node. If the node is not set, contains no image * or the image is not 4D, NULL will be returned.*/ mitk::Image::Pointer GetCurrentInputImage() const; /** Returns the variable/time grid of the GetCurrentInputImage(). If a model fit is selected its provider will be used to get the correct grid, otherwise just a simple time grid will be extracted.*/ const mitk::ModelBase::TimeGridType GetCurrentTimeGrid() const; Ui::ModelFitInspectorViewControls m_Controls; mitk::IRenderWindowPart* m_renderWindowPart; // Needed for observing the events for when a slice or time step is changed. bool m_PendingSliceChangedEvent; /**Helper structure to manage the registered observer events.*/ struct ObserverInfo { mitk::SliceNavigationController* controller; int observerTag; std::string renderWindowName; mitk::IRenderWindowPart* renderWindowPart; ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, const std::string& renderWindowName, mitk::IRenderWindowPart* part); }; typedef std::multimap ObserverMapType; ObserverMapType m_ObserverMap; /** @brief Is a visualization currently running? */ bool m_internalUpdateFlag; /** @brief List of modelfits currently in the data manager */ typedef std::map ModelFitInfoListType; ModelFitInfoListType m_modelfitList; /** @brief The currently selected modelfit */ mitk::modelFit::ModelFitInfo::ConstPointer m_currentFit; /** @brief Pointer to the instance of the model parameterizer for the current fit */ mitk::ModelParameterizerBase::Pointer m_currentModelParameterizer; mitk::IModelFitProvider* m_currentModelProviderService; /** @brief currently valid selected position in the inspector*/ mitk::Point3D m_currentSelectedPosition; /** @brief indicates if the currently selected position is valid for the currently selected fit. * This it is within the input image */ bool m_validSelectedPosition; /** @brief currently selected time step of the selected node for the visualization logic*/ unsigned int m_currentSelectedTimeStep; /** @brief currently selected node for the visualization logic*/ mitk::DataNode::ConstPointer m_currentSelectedNode; /** @brief Number of interpolation steps between two x values */ static const unsigned int INTERPOLATION_STEPS; /*************************************/ /* Members for visualizing the model */ itk::TimeStamp m_selectedNodeTime; itk::TimeStamp m_currentFitTime; itk::TimeStamp m_currentPositionTime; itk::TimeStamp m_lastRefreshTime; - QmitkPlotWidget::XYDataVector m_ImagePlotCurve; - QmitkPlotWidget::XYDataVector m_ModelPlotCurve; - - typedef std::map CurveMapType; - CurveMapType m_InputDataPlotCurves; + mitk::PlotDataCurve m_ImagePlotCurve; + mitk::PlotDataCurve m_ModelPlotCurve; + mitk::PlotDataCurveCollection m_InputDataPlotCurves; /** Check and updates the plot data if needed. * @return indicates if something was refreshed (true)*/ bool RefreshPlotData(); void RenderPlot(); void RenderFitInfo(); /** (re)initializes the headers of the data table*/ void InitDataTable(); }; #endif // ModelFitInspectorView_h