diff --git a/Modules/Chart/include/QmitkChartWidget.h b/Modules/Chart/include/QmitkChartWidget.h index 3f379f8a5f..2cb3b26b3e 100644 --- a/Modules/Chart/include/QmitkChartWidget.h +++ b/Modules/Chart/include/QmitkChartWidget.h @@ -1,245 +1,245 @@ /*=================================================================== 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 QmitkC3jsWidget_h #define QmitkC3jsWidget_h #include #include #include #include /*! \brief QmitkChartWidget is a widget to display various charts based on the javascript chart library C3js. * \details Data is added via AddData1D() or AddData2D().\n * There can be multiple charts (of different types with different properties) created by calling AddData1D or AddData2D multiple times.\n\n * The following chart types are supported: * * line chart: http://c3js.org/samples/simple_multiple.html * * bar chart: http://c3js.org/samples/chart_bar.html * * spline chart: http://c3js.org/samples/chart_spline.html * * pie chart: http://c3js.org/samples/chart_pie.html * * scatter chart: http://c3js.org/samples/chart_scatter.html * * area chart: http://c3js.org/samples/chart_area.html * * area spline chart: http://c3js.org/samples/chart_area.html * * Technical details: The javascript code is embedded in a QWebEngineView. The actual js code is implemented in resource\Chart.js. * \sa http://c3js.org for further information about the used javaScript library. * \ingroup Modules/Chart */ class MITKCHART_EXPORT QmitkChartWidget : public QWidget { Q_OBJECT public: /*! * \brief enum of diagram types. */ enum class ChartType { bar, /*!< bar chart, see http://c3js.org/samples/chart_bar.html */ line, /*!< line chart, see http://c3js.org/samples/simple_multiple.html */ spline, /*!< spline chart (smoothed line chart), see http://c3js.org/samples/chart_spline.html */ pie, /*!< pie chart, see http://c3js.org/samples/chart_pie.html*/ area, /*!< area chart, see http://c3js.org/samples/chart_area.html*/ area_spline, /*!< area-spline chart, see http://c3js.org/samples/chart_area.html*/ scatter /*!< scatter chart, see http://c3js.org/samples/chart_scatter.html*/ }; /*! * \brief enum of chart style (modifies background and line color). */ - enum class ChartStyle { + enum class ColorTheme { darkstyle, /*!< background color: dark gray, line color: blue */ lightstyle /*!< background color: white, line color: blue */ }; enum class LineStyle { solid, dashed }; enum class AxisScale { linear, log }; /*! * \brief enum of legend position. * See http://c3js.org/reference.html#legend-position */ enum class LegendPosition { bottomMiddle, bottomRight, topRight, topLeft, middleRight }; explicit QmitkChartWidget(QWidget* parent = nullptr); //for UnitTests explicit QmitkChartWidget(QWidget *parent, bool unitTest); ~QmitkChartWidget() override; /*! * \brief Adds 1D data to the widget * \details internally, the list is converted to a map with increasing integers keys starting at 0. * \param label the name of the data that is also used as identifier. * \param chartType the chart type that should be used for this data entry * \note the data can be cleared with ClearDiagram() * \note If the label name already exists, the name is replaced with a unique one by concatenating numbers to it. * \warning Pie chart is significantly different than the other chart types. Here, the data given by AddData1D is summed. Each entry represents a different category. */ void AddData1D(const std::vector& data1D, const std::string& label, ChartType chartType = ChartType::bar); /*! * \brief Adds 2D data to the widget. Call repeatedly for displaying multiple charts. * \details each entry represents a data point: key: value --> x-value: y-value. * \param label the name of the data that is also used as identifier. * \param chartType the chart type that should be used for this data entry * \note the data can be cleared with ClearDiagram() * \note If the label name already exists, the name is replaced with a unique one by concatenating numbers to it. * \warning Pie chart is significantly different than the other chart types. Here, the data given by AddData1D is summed. Each entry represents a different category. */ void AddData2D(const std::map &data2D, const std::string &label, ChartType chartType = ChartType::bar); /*! * \brief Removes data from the widget, works for 1D and 2D Data * \param label the name of the data that is also used as identifier. * \note All data can be cleared with ClearDiagram() * \throws Invalid Argument Exception when the label cannot be found */ void RemoveData(const std::string& label); /*! * \brief Sets the color of one data entry (identifier is previously assigned label) * \details the color name can be "red" or a hex number (#FF0000). * \warning Either define all data entries with a color or no data entry. If a mixed approach is used, * C3 choses the color of the data entry (that could be the same as a user defined color). * \note If an unknown label is given, nothing happens. * \sa https://www.w3schools.com/cssref/css_colors.asp */ void SetColor(const std::string& label, const std::string& colorName); /*! * \brief Sets the line style of one data entry (identifier is previously assigned label) * \details two line styles are possible: LineStyle::solid and LineStyle::dashed. * The default line style is solid. * \note If an unknown label is given, nothing happens. * \warning only sets the line style if the current chart type is ChartType::line. * However, the line style remains also if the chart changes (e.g. new chart type) */ void SetLineStyle(const std::string& label, LineStyle style); /*! * \brief Sets the axis scale to either linear (default) or logarithmic. */ void SetYAxisScale(AxisScale scale); void SetXAxisLabel(const std::string& label); void SetYAxisLabel(const std::string& label); /*! * \brief Sets a title for the chart. */ void SetTitle(const std::string &title); /*! * \brief Changes the chart type for all data entries and reloads the chart */ void SetChartTypeForAllDataAndReload(ChartType type); /*! * \brief Sets the chart type for a data entry * \details for available types, see ChartType * \note If an unknown label is given, nothing happens. * \warning Pie chart is significantly different than the other chart types. Here, the data given by AddData1D is summed. Each entry represents a different category. * \sa DiagramType for available types */ void SetChartType(const std::string& label, ChartType type); void SetErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector& errorMinus = std::vector()); /*! * \brief Sets the legend position. * \details Default position is bottom. * \sa LegendPosition for available types */ void SetLegendPosition(LegendPosition position); void SetShowLegend(bool show); void SetStackedData(bool stacked); /*! * \brief Displays the chart in the widget * \param showSubChart if a subchart is displayed inside the widget or not (see http://c3js.org/samples/options_subchart.html). * \exception if no data has been provided (\sa AddData1D AddData2D) */ void Show(bool showSubChart=false); /*! * \brief Either displays the dataPoints or not * \param showDataPoints if dataPoints are displayed inside the widget or not. * \details: example for not showing points: http://c3js.org/samples/point_show.html * example for showing the points: http://c3js.org/samples/simple_multiple.html */ void SetShowDataPoints(bool showDataPoints); /*! * \brief Clears all data inside and resets the widget. */ void Clear(); /*! * \brief Sets the theme of the widget. */ - void SetTheme(ChartStyle themeEnabled); + void SetTheme(ColorTheme themeEnabled); /*! * \brief Sets whether the subchart shall be shown. * \details Changes the state of the current chart object. Needs to be reloaded with Reload() to display changes. * \param showSubChart if a subchart is displayed inside the widget or not. */ void SetShowSubchart(bool showSubChart); /*! * \brief Sets whether the error bars shall be shown. * \details Changes the state of the current chart object. Needs to be reloaded with Reload() to display changes. * \param showErrorBars if error bars are displayed or not. */ void SetShowErrorBars(bool showErrorBars); /*! * \brief Reloads the chart in the widget * \details reloading may be needed to display added data in an existing chart */ void Reload(); QSize sizeHint() const override; public slots: void OnLoadFinished(bool isLoadSuccessful); void OnPageSuccessfullyLoaded(); signals: void PageSuccessfullyLoaded(); private: /*! source: https://stackoverflow.com/questions/29383/converting-bool-to-text-in-c*/ std::string convertBooleanValue(bool value) const; class Impl; std::unique_ptr m_Impl; }; #endif diff --git a/Modules/Chart/src/QmitkChartWidget.cpp b/Modules/Chart/src/QmitkChartWidget.cpp index 8d9f482574..bac90536df 100644 --- a/Modules/Chart/src/QmitkChartWidget.cpp +++ b/Modules/Chart/src/QmitkChartWidget.cpp @@ -1,619 +1,619 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include #include #include "mitkExceptionMacro.h" #include #include class CustomPage : public QWebEnginePage { public: CustomPage(QObject *parent = 0) : QWebEnginePage(parent) {} virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel /*level*/, const QString &message, int lineNumber, const QString & /*sourceID*/) { MITK_INFO << "JS > " << lineNumber << ": " << message.toStdString(); } }; class QmitkChartWidget::Impl final { public: explicit Impl(QWidget *parent); ~Impl(); Impl(const Impl &) = delete; Impl &operator=(const Impl &) = delete; void AddData1D(const std::vector &data1D, const std::string &label, QmitkChartWidget::ChartType chartType); void AddData2D(const std::map &data2D, const std::string &label, QmitkChartWidget::ChartType chartType); void RemoveData(const std::string &label); void ClearData(); void SetColor(const std::string &label, const std::string &colorName); void SetLineStyle(const std::string &label, LineStyle style); void SetYAxisScale(AxisScale scale); void SetXAxisLabel(const std::string &label); void SetYAxisLabel(const std::string &label); void SetTitle(const std::string &title); void SetErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector& errorMinus = std::vector()); std::string GetThemeName() const; - void SetThemeName(ChartStyle style); + void SetThemeName(ColorTheme style); void SetChartType(QmitkChartWidget::ChartType chartType); void SetLegendPosition(LegendPosition position); void SetChartTypeByLabel(const std::string &label, QmitkChartWidget::ChartType chartType); void Show(bool showSubChart); void SetShowLegend(bool show); void SetShowErrorBars(bool show); void SetShowDataPoints(bool showDataPoints = false); void SetChartType(const std::string &label, QmitkChartWidget::ChartType chartType); std::string ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const; void ClearJavaScriptChart(); void InitializeJavaScriptChart(); void CallJavaScriptFuntion(const QString &command); QSize sizeHint() const; private: using ChartxyDataVector = std::vector>; std::string GetUniqueLabelName(const QList &labelList, const std::string &label) const; QmitkChartxyData *GetDataElementByLabel(const std::string &label) const; QList GetDataLabels(const ChartxyDataVector &c3xyData) const; QWebChannel *m_WebChannel; QWebEngineView *m_WebEngineView; QmitkChartData m_C3Data; ChartxyDataVector m_C3xyData; std::map m_ChartTypeToName; - std::map m_ChartStyleToName; + std::map m_ColorThemeToName; std::map m_LegendPositionToName; std::map m_LineStyleToName; std::map m_AxisScaleToName; }; std::string QmitkChartWidget::Impl::GetThemeName() const { return m_C3Data.GetThemeName().toString().toStdString(); } QmitkChartWidget::Impl::Impl(QWidget *parent) : m_WebChannel(new QWebChannel(parent)), m_WebEngineView(new QWebEngineView(parent)) { // disable context menu for QWebEngineView m_WebEngineView->setContextMenuPolicy(Qt::NoContextMenu); m_WebEngineView->setPage(new CustomPage()); // Set the webengineview to an initial empty page. The actual chart will be loaded once the data is calculated. m_WebEngineView->load(QUrl(QStringLiteral("qrc:///C3js/empty.html"))); m_WebEngineView->page()->setWebChannel(m_WebChannel); m_WebEngineView->settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); connect(m_WebEngineView, SIGNAL(loadFinished(bool)), parent, SLOT(OnLoadFinished(bool))); auto layout = new QGridLayout(parent); layout->setMargin(0); layout->addWidget(m_WebEngineView); m_ChartTypeToName.emplace(ChartType::bar, "bar"); m_ChartTypeToName.emplace(ChartType::line, "line"); m_ChartTypeToName.emplace(ChartType::spline, "spline"); m_ChartTypeToName.emplace(ChartType::pie, "pie"); m_ChartTypeToName.emplace(ChartType::area, "area"); m_ChartTypeToName.emplace(ChartType::area_spline, "area-spline"); m_ChartTypeToName.emplace(ChartType::scatter, "scatter"); m_LegendPositionToName.emplace(LegendPosition::bottomMiddle, "bottomMiddle"); m_LegendPositionToName.emplace(LegendPosition::bottomRight, "bottomRight"); m_LegendPositionToName.emplace(LegendPosition::topRight, "topRight"); m_LegendPositionToName.emplace(LegendPosition::topLeft, "topLeft"); m_LegendPositionToName.emplace(LegendPosition::middleRight, "middleRight"); m_LineStyleToName.emplace(LineStyle::solid, "solid"); m_LineStyleToName.emplace(LineStyle::dashed, "dashed"); m_AxisScaleToName.emplace(AxisScale::linear, ""); m_AxisScaleToName.emplace(AxisScale::log, "log"); - m_ChartStyleToName.emplace(ChartStyle::lightstyle, "light"); - m_ChartStyleToName.emplace(ChartStyle::darkstyle, "dark"); + m_ColorThemeToName.emplace(ColorTheme::lightstyle, "light"); + m_ColorThemeToName.emplace(ColorTheme::darkstyle, "dark"); } QmitkChartWidget::Impl::~Impl() {} std::string CheckForCorrectHex(const std::string &colorName) { std::regex rgx("([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"); std::smatch match; if (!colorName.empty() && colorName.at(0) != '#' && std::regex_search(colorName.begin(), colorName.end(), match, rgx)) { return "#" + colorName; } else { return colorName; } } void QmitkChartWidget::Impl::AddData1D(const std::vector &data1D, const std::string &label, QmitkChartWidget::ChartType type) { std::map transformedData2D; unsigned int count = 0; // transform the 1D data to 2D data for (const auto &ele : data1D) { transformedData2D[count] = ele; count++; } AddData2D(transformedData2D, label, type); } void QmitkChartWidget::Impl::AddData2D(const std::map &data2D, const std::string &label, QmitkChartWidget::ChartType type) { QMap data2DConverted; for (const auto &aValue : data2D) { data2DConverted.insert(aValue.first, aValue.second); } const std::string chartTypeName(m_ChartTypeToName.at(type)); auto definedLabels = GetDataLabels(m_C3xyData); auto uniqueLabel = GetUniqueLabelName(definedLabels, label); if (type == ChartType::scatter) { SetShowDataPoints(true); MITK_INFO << "Enabling data points for all because of scatter plot"; } m_C3xyData.push_back(std::make_unique( data2DConverted, QVariant(QString::fromStdString(uniqueLabel)), QVariant(QString::fromStdString(chartTypeName)))); } void QmitkChartWidget::Impl::RemoveData(const std::string &label) { for (ChartxyDataVector::iterator iter = m_C3xyData.begin(); iter != m_C3xyData.end(); ++iter) { if ((*iter)->GetLabel().toString().toStdString() == label) { m_C3xyData.erase(iter); return; } } throw std::invalid_argument("Cannot Remove Data because the label does not exist."); } void QmitkChartWidget::Impl::ClearData() { for (auto &xyData : m_C3xyData) { m_WebChannel->deregisterObject(xyData.get()); } m_C3xyData.clear(); } void QmitkChartWidget::Impl::SetColor(const std::string &label, const std::string &colorName) { auto element = GetDataElementByLabel(label); if (element) { auto colorChecked = CheckForCorrectHex(colorName); element->SetColor(QVariant(QString::fromStdString(colorChecked))); } } void QmitkChartWidget::Impl::SetLineStyle(const std::string &label, LineStyle style) { auto element = GetDataElementByLabel(label); // only has effect with chart type line if (element && element->GetChartType() == QVariant(QString::fromStdString(ConvertChartTypeToString(ChartType::line)))) { const std::string lineStyleName(m_LineStyleToName.at(style)); element->SetLineStyle(QVariant(QString::fromStdString(lineStyleName))); } } void QmitkChartWidget::Impl::SetYAxisScale(AxisScale scale) { const std::string axisScaleName(m_AxisScaleToName.at(scale)); m_C3Data.SetYAxisScale(QString::fromStdString(axisScaleName)); } QmitkChartxyData *QmitkChartWidget::Impl::GetDataElementByLabel(const std::string &label) const { for (const auto &qmitkChartxyData : m_C3xyData) { if (qmitkChartxyData->GetLabel().toString() == label.c_str()) { return qmitkChartxyData.get(); } } MITK_WARN << "label " << label << " not found in QmitkChartWidget"; return nullptr; } QList QmitkChartWidget::Impl::GetDataLabels(const ChartxyDataVector &c3xyData) const { QList dataLabels; for (auto element = c3xyData.begin(); element != c3xyData.end(); ++element) { dataLabels.push_back((*element)->GetLabel()); } return dataLabels; } void QmitkChartWidget::Impl::SetXAxisLabel(const std::string &label) { m_C3Data.SetXAxisLabel(QString::fromStdString(label)); } void QmitkChartWidget::Impl::SetYAxisLabel(const std::string &label) { m_C3Data.SetYAxisLabel(QString::fromStdString(label)); } void QmitkChartWidget::Impl::SetTitle(const std::string &title) { m_C3Data.SetTitle(QString::fromStdString(title)); } -void QmitkChartWidget::Impl::SetThemeName(QmitkChartWidget::ChartStyle style) +void QmitkChartWidget::Impl::SetThemeName(QmitkChartWidget::ColorTheme style) { - const std::string themeName(m_ChartStyleToName.at(style)); + const std::string themeName(m_ColorThemeToName.at(style)); m_C3Data.SetThemeName(QString::fromStdString(themeName)); } void QmitkChartWidget::Impl::SetChartType(QmitkChartWidget::ChartType chartType) { for (auto iterator = m_C3xyData.begin(); iterator != m_C3xyData.end(); ++iterator) { SetChartTypeByLabel((*iterator)->GetLabel().toString().toStdString(), chartType); } auto chartTypeName = ConvertChartTypeToString(chartType); const QString command = QString::fromStdString("transformView('" + chartTypeName + "')"); CallJavaScriptFuntion(command); } void QmitkChartWidget::Impl::SetChartTypeByLabel(const std::string &label, QmitkChartWidget::ChartType chartType) { auto element = GetDataElementByLabel(label); if (element) { if (chartType == ChartType::scatter) { SetShowDataPoints(true); MITK_INFO << "Enabling data points for all because of scatter plot"; } auto chartTypeName = ConvertChartTypeToString(chartType); element->SetChartType(QVariant(QString::fromStdString(chartTypeName))); } } void QmitkChartWidget::Impl::SetLegendPosition(QmitkChartWidget::LegendPosition legendPosition) { const std::string legendPositionName(m_LegendPositionToName.at(legendPosition)); m_C3Data.SetLegendPosition(QString::fromStdString(legendPositionName)); } void QmitkChartWidget::Impl::Show(bool showSubChart) { if (m_C3xyData.empty()) { MITK_WARN << "no data available for display in chart"; } else { m_C3Data.SetAppearance(showSubChart, m_C3xyData.front()->GetChartType() == QVariant("pie")); } InitializeJavaScriptChart(); } void QmitkChartWidget::Impl::SetShowLegend(bool show) { m_C3Data.SetShowLegend(show); } void QmitkChartWidget::Impl::SetShowErrorBars(bool show) { m_C3Data.SetShowErrorBars(show); } void QmitkChartWidget::Impl::SetShowDataPoints(bool showDataPoints) { if (showDataPoints == true) { m_C3Data.SetDataPointSize(3); } else { m_C3Data.SetDataPointSize(0); } } void QmitkChartWidget::Impl::SetChartType(const std::string &label, QmitkChartWidget::ChartType chartType) { auto element = GetDataElementByLabel(label); if (element) { if (chartType == ChartType::scatter) { SetShowDataPoints(true); MITK_INFO << "Enabling data points for all because of scatter plot"; } const std::string chartTypeName(m_ChartTypeToName.at(chartType)); element->SetChartType(QVariant(QString::fromStdString(chartTypeName))); } } void QmitkChartWidget::Impl::SetErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector& errorMinus) { auto element = GetDataElementByLabel(label); if (element) { QList errorConvertedPlus; for (const auto &aValue : errorPlus) { errorConvertedPlus.append(aValue); } QList errorConvertedMinus; for (const auto &aValue : errorMinus) { errorConvertedMinus.append(aValue); } element->SetErrorDataPlus(errorConvertedPlus); element->SetErrorDataMinus(errorConvertedMinus); } } std::string QmitkChartWidget::Impl::ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const { return m_ChartTypeToName.at(chartType); } QSize QmitkChartWidget::Impl::sizeHint() const { return QSize(400, 300); } void QmitkChartWidget::Impl::CallJavaScriptFuntion(const QString &command) { m_WebEngineView->page()->runJavaScript(command); } void QmitkChartWidget::Impl::ClearJavaScriptChart() { m_WebEngineView->load(QUrl(QStringLiteral("qrc:///C3js/empty.html"))); } void QmitkChartWidget::Impl::InitializeJavaScriptChart() { m_WebChannel->registerObject(QStringLiteral("chartData"), &m_C3Data); unsigned count = 0; for (auto &xyData : m_C3xyData) { QString variableName = "xyData" + QString::number(count); m_WebChannel->registerObject(variableName, xyData.get()); count++; } m_WebEngineView->load(QUrl(QStringLiteral("qrc:///C3js/QmitkChartWidget.html"))); } std::string QmitkChartWidget::Impl::GetUniqueLabelName(const QList &labelList, const std::string &label) const { QString currentLabel = QString::fromStdString(label); int counter = 0; while (labelList.contains(currentLabel)) { currentLabel = QString::fromStdString(label + std::to_string(counter)); counter++; } return currentLabel.toStdString(); } QmitkChartWidget::QmitkChartWidget(QWidget *parent) : QWidget(parent), m_Impl(new Impl(this)) { connect(this, &QmitkChartWidget::PageSuccessfullyLoaded, this, &QmitkChartWidget::OnPageSuccessfullyLoaded); } QmitkChartWidget::~QmitkChartWidget() {} void QmitkChartWidget::AddData2D(const std::map &data2D, const std::string &label, ChartType type) { m_Impl->AddData2D(data2D, label, type); } void QmitkChartWidget::SetColor(const std::string &label, const std::string &colorName) { m_Impl->SetColor(label, colorName); } void QmitkChartWidget::SetLineStyle(const std::string &label, LineStyle style) { m_Impl->SetLineStyle(label, style); } void QmitkChartWidget::SetYAxisScale(AxisScale scale) { m_Impl->SetYAxisScale(scale); } void QmitkChartWidget::AddData1D(const std::vector &data1D, const std::string &label, ChartType type) { m_Impl->AddData1D(data1D, label, type); } void QmitkChartWidget::RemoveData(const std::string &label) { m_Impl->RemoveData(label); } void QmitkChartWidget::SetXAxisLabel(const std::string &label) { m_Impl->SetXAxisLabel(label); } void QmitkChartWidget::SetYAxisLabel(const std::string &label) { m_Impl->SetYAxisLabel(label); } void QmitkChartWidget::SetTitle(const std::string &title) { m_Impl->SetTitle(title); } void QmitkChartWidget::SetShowDataPoints(bool showDataPoints) { m_Impl->SetShowDataPoints(showDataPoints); } void QmitkChartWidget::SetChartType(const std::string &label, ChartType type) { m_Impl->SetChartType(label, type); } void QmitkChartWidget::SetErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector& errorMinus) { m_Impl->SetErrorBars(label, errorPlus, errorMinus); } void QmitkChartWidget::SetLegendPosition(LegendPosition position) { m_Impl->SetLegendPosition(position); } void QmitkChartWidget::SetShowLegend(bool show) { m_Impl->SetShowLegend(show); } void QmitkChartWidget::SetStackedData(bool stacked) { Q_UNUSED(stacked) //this is a temporary solution and will be changed as soon as this method is implemented MITK_WARN << "not yet implemented"; } void QmitkChartWidget::Show(bool showSubChart) { m_Impl->Show(showSubChart); } void QmitkChartWidget::Clear() { m_Impl->ClearData(); m_Impl->ClearJavaScriptChart(); } void QmitkChartWidget::OnLoadFinished(bool isLoadSuccessfull) { if (isLoadSuccessfull) { emit PageSuccessfullyLoaded(); } } void QmitkChartWidget::OnPageSuccessfullyLoaded() { auto themeName = m_Impl->GetThemeName(); QString command; if (themeName == "dark") { command = QString("changeTheme('dark')"); } else { command = QString("changeTheme('light')"); } m_Impl->CallJavaScriptFuntion(command); } std::string QmitkChartWidget::convertBooleanValue(bool value) const { std::stringstream converter; converter << std::boolalpha << value; return converter.str(); } void QmitkChartWidget::SetChartTypeForAllDataAndReload(ChartType type) { m_Impl->SetChartType(type); } -void QmitkChartWidget::SetTheme(ChartStyle themeEnabled) +void QmitkChartWidget::SetTheme(ColorTheme themeEnabled) { m_Impl->SetThemeName(themeEnabled); } void QmitkChartWidget::SetShowSubchart(bool showSubChart) { - QString subChartString = convertBooleanValue(showSubChart); + QString subChartString = QString::fromStdString(convertBooleanValue(showSubChart)); const QString command = QString("SetShowSubchart(" + subChartString + ")"); m_Impl->CallJavaScriptFuntion(command); } void QmitkChartWidget::SetShowErrorBars(bool showErrorBars) { m_Impl->SetShowErrorBars(showErrorBars); //QString showErrorBarsString = convertBooleanValue(showErrorBars); //const QString command = QString("SetShowErrorBars(" + showErrorBarsString + ")"); //m_Impl->CallJavaScriptFuntion(command); } void QmitkChartWidget::Reload() { const QString command = QString("Reload()"); m_Impl->CallJavaScriptFuntion(command); } QSize QmitkChartWidget::sizeHint() const { return m_Impl->sizeHint(); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.cpp index 3c7b6464e6..83730a7775 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.cpp @@ -1,138 +1,138 @@ /*=================================================================== 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 "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); CreateConnections(); } void QmitkHistogramVisualizationWidget::SetHistogram(itk::Statistics::Histogram::ConstPointer histogram, const std::string& dataLabel) { if (histogram == nullptr) return; m_Histogram = histogram; m_Controls.chartWidget->AddData2D(ConvertHistogramToMap(m_Histogram), dataLabel); m_Controls.chartWidget->SetChartType(dataLabel, QmitkChartWidget::ChartType::bar); m_Controls.chartWidget->SetXAxisLabel("Gray value"); m_Controls.chartWidget->SetYAxisLabel("Frequency"); m_Controls.chartWidget->Show(m_Controls.checkBoxShowSubchart->isChecked()); SetGUIElementsEnabled(true); } void QmitkHistogramVisualizationWidget::Reset() { m_Controls.chartWidget->Clear(); SetGUIElementsEnabled(false); } -void QmitkHistogramVisualizationWidget::SetTheme(QmitkChartWidget::ChartStyle style) +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); } void QmitkHistogramVisualizationWidget::SetGUIElementsEnabled(bool enabled) { this->setEnabled(enabled); m_Controls.groupBoxPlot->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); } std::map QmitkHistogramVisualizationWidget::ConvertHistogramToMap(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); } } return histogramMap; } void QmitkHistogramVisualizationWidget::OnClipboardButtonClicked() { if (m_Histogram) { QApplication::clipboard()->clear(); QString clipboard("Measurement \t Frequency\n"); auto iter = m_Histogram->Begin(); auto iterEnd = m_Histogram->End(); for (; iter != iterEnd; ++iter) { clipboard = clipboard.append("%L1 \t %L2\n") .arg(iter.GetMeasurementVector()[0], 0, 'f', 2) .arg(iter.GetFrequency()); } 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()); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.h b/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.h index a1a92aaccf..9f8ef94fbd 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkHistogramVisualizationWidget.h @@ -1,73 +1,73 @@ /*=================================================================== 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 QmitkHistogramVisualizationWidget_H__INCLUDED #define QmitkHistogramVisualizationWidget_H__INCLUDED #include #include //itk #include /** * \brief Widget for displaying Histograms. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkHistogramVisualizationWidget : public QWidget { Q_OBJECT public: QmitkHistogramVisualizationWidget(QWidget* parent = nullptr); /** \brief Draws the histogram and enables the GUI elements. */ void SetHistogram(itk::Statistics::Histogram::ConstPointer histogram, const std::string& dataLabel); /** \brief Clears the histogram and disables all GUI elements. */ void Reset(); /** \brief Sets the theme (either dark or light) */ - void SetTheme(QmitkChartWidget::ChartStyle style); + void SetTheme(QmitkChartWidget::ColorTheme style); signals: /** \brief Signal to be emitted when the number of bins is changed by the user. The HistogramCalculator should connect to this signal and recalculate the data accordingly. */ void RequestHistogramUpdate(unsigned int nBins); private: void CreateConnections(); void SetGUIElementsEnabled(bool enabled); /** \brief Helper function to convert the histogram in order to forward it to the ChartWidget. */ std::map ConvertHistogramToMap(itk::Statistics::Histogram::ConstPointer histogram) const; //slots /** \brief Saves the histogram to the clipboard. */ void OnClipboardButtonClicked(); /** \brief Enables / Disables SpinBox to change the number of bins. */ void OnDefaultNBinsCheckBoxChanged(); /** \brief Emits the signal RequestHistogramUpdate(unsigned int nBins) with the updated value. */ void OnNBinsSpinBoxValueChanged(); /** \brief Shows / Hides the subchart. */ void OnShowSubchartCheckBoxChanged(); private: Ui::QmitkHistogramVisualizationControls m_Controls; const unsigned int m_DefaultNBins = 100; const unsigned int m_MinNBins = 10; const unsigned int m_MaxNBins = 10000; itk::Statistics::Histogram::ConstPointer m_Histogram; }; #endif //QmitkHistogramVisualizationWidget_H__INCLUDED \ No newline at end of file diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkIntensityProfileVisualizationWidget.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkIntensityProfileVisualizationWidget.cpp index 355bf6bf02..16b6ecc019 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkIntensityProfileVisualizationWidget.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkIntensityProfileVisualizationWidget.cpp @@ -1,117 +1,117 @@ /*=================================================================== 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 "QmitkIntensityProfileVisualizationWidget.h" #include QmitkIntensityProfileVisualizationWidget::QmitkIntensityProfileVisualizationWidget(QWidget* parent) : QWidget(parent) { m_Controls.setupUi(this); m_Controls.checkBoxShowSubchart->setChecked(false); CreateConnections(); } void QmitkIntensityProfileVisualizationWidget::SetIntensityProfile(mitk::IntensityProfile::ConstPointer intensityProfile, const std::string& dataLabel) { if (intensityProfile == nullptr) return; m_IntensityProfileList = mitk::CreateVectorFromIntensityProfile(intensityProfile); if (m_IntensityProfileList.empty()) return; m_Controls.chartWidget->AddData1D(m_IntensityProfileList, dataLabel); m_Controls.chartWidget->SetChartType(dataLabel, QmitkChartWidget::ChartType::line); m_Controls.chartWidget->SetXAxisLabel("Distance"); m_Controls.chartWidget->SetYAxisLabel("Intensity"); m_Controls.chartWidget->Show(m_Controls.checkBoxShowSubchart->isChecked()); SetGUIElementsEnabled(true); } void QmitkIntensityProfileVisualizationWidget::Reset() { m_Controls.chartWidget->Clear(); SetGUIElementsEnabled(false); m_IntensityProfileList.clear(); } -void QmitkIntensityProfileVisualizationWidget::SetTheme(QmitkChartWidget::ChartStyle style) +void QmitkIntensityProfileVisualizationWidget::SetTheme(QmitkChartWidget::ColorTheme style) { m_ChartStyle = style; } void QmitkIntensityProfileVisualizationWidget::CreateConnections() { connect(m_Controls.checkBoxShowSubchart, &QCheckBox::clicked, this, &QmitkIntensityProfileVisualizationWidget::OnShowSubchartCheckBoxChanged); connect(m_Controls.buttonCopyToClipboard, &QPushButton::clicked, this, &QmitkIntensityProfileVisualizationWidget::OnClipboardButtonClicked); connect(m_Controls.chartWidget, &QmitkChartWidget::PageSuccessfullyLoaded, this, &QmitkIntensityProfileVisualizationWidget::OnPageSuccessfullyLoaded); } void QmitkIntensityProfileVisualizationWidget::SetGUIElementsEnabled(bool enabled) { this->setEnabled(enabled); m_Controls.groupBoxIntensityProfile->setEnabled(enabled); m_Controls.groupBoxPlot->setEnabled(enabled); m_Controls.buttonCopyToClipboard->setEnabled(enabled); m_Controls.checkBoxShowSubchart->setEnabled(enabled); m_Controls.chartWidget->setEnabled(enabled); m_Controls.labelInfo->setEnabled(enabled); } std::vector QmitkIntensityProfileVisualizationWidget::ConvertIntensityProfileToVector(mitk::IntensityProfile::ConstPointer intensityProfile) const { std::vector intensityProfileList; if (intensityProfile != nullptr) { auto end = intensityProfile->End(); for (auto it = intensityProfile->Begin(); it != end; ++it) { intensityProfileList.push_back(it.GetMeasurementVector()[0]); } } return intensityProfileList; } void QmitkIntensityProfileVisualizationWidget::OnClipboardButtonClicked() { if (m_IntensityProfileList.empty()) return; QApplication::clipboard()->clear(); QString clipboard("Pixel \t Intensity\n"); for (unsigned int i = 0; i < m_IntensityProfileList.size(); i++) { clipboard = clipboard.append("%L1 \t %L2\n") .arg(QString::number(i)) .arg(QString::number(m_IntensityProfileList.at(i))); } QApplication::clipboard()->setText(clipboard, QClipboard::Clipboard); } void QmitkIntensityProfileVisualizationWidget::OnShowSubchartCheckBoxChanged() { m_Controls.chartWidget->Show(m_Controls.checkBoxShowSubchart->isChecked()); } void QmitkIntensityProfileVisualizationWidget::OnPageSuccessfullyLoaded() { m_Controls.chartWidget->SetTheme(m_ChartStyle); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkIntensityProfileVisualizationWidget.h b/Modules/ImageStatisticsUI/Qmitk/QmitkIntensityProfileVisualizationWidget.h index 2b0a79cd1b..955e4d10d0 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkIntensityProfileVisualizationWidget.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkIntensityProfileVisualizationWidget.h @@ -1,68 +1,68 @@ /*=================================================================== 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 QmitkIntensityProfileVisualizationWidget_H__INCLUDED #define QmitkIntensityProfileVisualizationWidget_H__INCLUDED //Qt #include #include #include //mitk #include /** * \brief Widget for displaying intensity profiles. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkIntensityProfileVisualizationWidget : public QWidget { Q_OBJECT public: QmitkIntensityProfileVisualizationWidget(QWidget* parent = nullptr); void SetIntensityProfile(mitk::IntensityProfile::ConstPointer intensityProfile, const std::string& dataLabel); /** \brief Clears the intensity profile and disables all GUI elements. */ void Reset(); - void SetTheme(QmitkChartWidget::ChartStyle style); + void SetTheme(QmitkChartWidget::ColorTheme style); private: void CreateConnections(); void SetGUIElementsEnabled(bool enabled); std::vector ConvertIntensityProfileToVector(mitk::IntensityProfile::ConstPointer intensityProfile) const; /** \brief Saves the intensity profile to the clipboard. */ void OnClipboardButtonClicked(); /** \brief Shows / Hides the subchart. */ void OnShowSubchartCheckBoxChanged(); void OnPageSuccessfullyLoaded(); private: Ui::QmitkIntensityProfileControls m_Controls; - QmitkChartWidget::ChartStyle m_ChartStyle = QmitkChartWidget::ChartStyle::darkstyle; + QmitkChartWidget::ColorTheme m_ChartStyle = QmitkChartWidget::ColorTheme::darkstyle; std::vector m_IntensityProfileList; }; #endif //QmitkIntensityProfileVisualizationWidget_H__INCLUDED \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp index b214408158..cd7a596cca 100644 --- a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp +++ b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp @@ -1,190 +1,190 @@ /*=================================================================== 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 #include // Qmitk #include "ChartExample.h" // Qt #include #include const std::string ChartExample::VIEW_ID = "org.mitk.views.chartexample"; void ChartExample::SetFocus() { m_Controls.m_buttonCreateChart->setFocus(); } void ChartExample::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); connect(m_Controls.m_buttonCreateChart, &QPushButton::clicked, this, &ChartExample::CreateChart); connect(m_Controls.m_buttonClearChart, &QPushButton::clicked, this, &ChartExample::ClearChart); connect(m_Controls.m_buttonAddData, &QPushButton::clicked, this, &ChartExample::AddData); FillRandomDataValues(); auto chartStyle = GetColorTheme(); m_Controls.m_Chart->SetTheme(chartStyle); m_Controls.m_lineEditXAxisLabel->setText("xLabel"); m_Controls.m_lineEditYAxisLabel->setText("yLabel"); m_chartNameToChartType.emplace("bar", QmitkChartWidget::ChartType::bar); m_chartNameToChartType.emplace("line", QmitkChartWidget::ChartType::line); m_chartNameToChartType.emplace("spline", QmitkChartWidget::ChartType::spline); m_chartNameToChartType.emplace("pie", QmitkChartWidget::ChartType::pie); m_chartNameToChartType.emplace("area", QmitkChartWidget::ChartType::area); m_chartNameToChartType.emplace("area-spline", QmitkChartWidget::ChartType::area_spline); m_chartNameToChartType.emplace("scatter", QmitkChartWidget::ChartType::scatter); m_LineNameToLineType.emplace("solid", QmitkChartWidget::LineStyle::solid); m_LineNameToLineType.emplace("dashed", QmitkChartWidget::LineStyle::dashed); m_AxisScaleNameToAxisScaleType.emplace("linear", QmitkChartWidget::AxisScale::linear); m_AxisScaleNameToAxisScaleType.emplace("logarithmic", QmitkChartWidget::AxisScale::log); } void ChartExample::FillRandomDataValues() { std::vector numbers = generateRandomNumbers(10, 10.0); std::string text = convertToText(numbers, ";"); m_Controls.m_lineEditDataVector->setText(QString::fromStdString(text)); m_Controls.m_lineEditDataLabel->setText("test" + QString::number(countForUID)); countForUID++; } void ChartExample::CreateChart() { auto dataYAxisScaleType = m_AxisScaleNameToAxisScaleType.at(m_Controls.m_comboBoxYAxisScale->currentText().toStdString()); auto xAxisLabel = m_Controls.m_lineEditXAxisLabel->text().toStdString(); auto yAxisLabel = m_Controls.m_lineEditYAxisLabel->text().toStdString(); auto showLegend = m_Controls.m_checkBoxShowLegend->isChecked(); auto showDataPoints = m_Controls.m_checkBoxShowDataPoints->isChecked(); auto stackedData = m_Controls.m_checkBoxStackedData->isChecked(); auto showSubchart = m_Controls.m_checkBoxShowSubchart->isChecked(); m_Controls.m_Chart->SetYAxisScale(dataYAxisScaleType); m_Controls.m_Chart->SetXAxisLabel(xAxisLabel); m_Controls.m_Chart->SetYAxisLabel(yAxisLabel); m_Controls.m_Chart->SetShowLegend(showLegend); m_Controls.m_Chart->SetShowErrorBars(true); m_Controls.m_Chart->SetShowDataPoints(showDataPoints); m_Controls.m_Chart->SetStackedData(stackedData); m_Controls.m_Chart->Show(showSubchart); } void ChartExample::ClearChart() { m_Controls.m_Chart->Clear(); m_Controls.m_plainTextEditDataView->clear(); } void ChartExample::AddData() { auto lineEditData = m_Controls.m_lineEditDataVector->text(); std::vector data; for(const QString entry : lineEditData.split(';')) { data.push_back(entry.toDouble()); } auto chartType = m_chartNameToChartType.at(m_Controls.m_comboBoxChartType->currentText().toStdString()); std::string dataLabel = m_Controls.m_lineEditDataLabel->text().toStdString(); std::string dataColor = m_Controls.m_lineEditColor->text().toStdString(); auto dataLineStyleType = m_LineNameToLineType.at(m_Controls.m_comboBoxLineStyle->currentText().toStdString()); m_Controls.m_Chart->AddData1D(data, dataLabel, chartType); if (!dataColor.empty()) { m_Controls.m_Chart->SetColor(dataLabel, dataColor); } auto errorValuesMinus = std::vector(data.size()); std::transform(data.begin(), data.end(), errorValuesMinus.begin(), [](double d) { return d / 2; }); m_Controls.m_Chart->SetErrorBars(dataLabel, data, errorValuesMinus); m_Controls.m_Chart->SetLineStyle(dataLabel, dataLineStyleType); QString dataOverview; dataOverview.append(m_Controls.m_lineEditDataLabel->text()); dataOverview.append("(").append(m_Controls.m_comboBoxChartType->currentText()); if (!dataColor.empty()) { dataOverview.append(", ").append(dataColor.c_str()); } dataOverview.append(", ").append(m_Controls.m_comboBoxLineStyle->currentText()); dataOverview.append(")"); dataOverview.append(":").append(lineEditData); m_Controls.m_plainTextEditDataView->appendPlainText(dataOverview); FillRandomDataValues(); } std::vector ChartExample::generateRandomNumbers(unsigned int amount, double max) const { QRandomGenerator gen; gen.seed(time(NULL)); std::vector data; for (unsigned int i = 0; i < amount; i++) { data.push_back(gen.bounded(max)); } return data; } std::string ChartExample::convertToText(std::vector numbers, std::string delimiter) const { std::ostringstream oss; oss.precision(3); if (!numbers.empty()) { for (auto number : numbers) { oss << number << delimiter; } } auto aString = oss.str(); aString.pop_back(); return aString; } -QmitkChartWidget::ChartStyle ChartExample::GetColorTheme() const +QmitkChartWidget::ColorTheme ChartExample::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::ChartStyle::darkstyle; + return QmitkChartWidget::ColorTheme::darkstyle; } else { - return QmitkChartWidget::ChartStyle::lightstyle; + return QmitkChartWidget::ColorTheme::lightstyle; } } - return QmitkChartWidget::ChartStyle::darkstyle; + return QmitkChartWidget::ColorTheme::darkstyle; } diff --git a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h index 14eacef16a..6bf8390862 100644 --- a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h +++ b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h @@ -1,67 +1,67 @@ /*=================================================================== 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 ChartExample_h #define ChartExample_h #include #include #include "ui_ChartExampleControls.h" /** \brief ChartExample \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. \sa QmitkAbstractView \ingroup ${plugin_target}_internal */ class ChartExample : public QmitkAbstractView { // 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: static const std::string VIEW_ID; protected: virtual void CreateQtPartControl(QWidget *parent) override; virtual void SetFocus() override; void CreateChart(); void ClearChart(); void AddData(); Ui::ChartExampleControls m_Controls; private: void FillRandomDataValues(); std::vector generateRandomNumbers(unsigned int amount, double max) const; std::string convertToText(std::vector numbers, std::string delimiter) const; - QmitkChartWidget::ChartStyle GetColorTheme() const; + QmitkChartWidget::ColorTheme GetColorTheme() const; std::map m_chartNameToChartType; std::map m_LineNameToLineType; std::map m_AxisScaleNameToAxisScaleType; unsigned int countForUID=0; }; #endif // ChartExample_h diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.cpp index 995c3ce01e..a018deb7e9 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.cpp @@ -1,415 +1,415 @@ /*=================================================================== 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 "QmitkImageStatisticsReloadedView.h" #include // berry includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mitkImageStatisticsContainerManager.h" const std::string QmitkImageStatisticsReloadedView::VIEW_ID = "org.mitk.views.imagestatisticsReloaded"; QmitkImageStatisticsReloadedView::QmitkImageStatisticsReloadedView(QObject* /*parent*/, const char* /*name*/) { this->m_CalculationThread = new QmitkImageStatisticsCalculationJob(); } QmitkImageStatisticsReloadedView::~QmitkImageStatisticsReloadedView() { if (m_selectedPlanarFigure) m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); } void QmitkImageStatisticsReloadedView::CreateQtPartControl(QWidget *parent) { m_Controls.setupUi(parent); m_Controls.widget_histogram->SetTheme(this->GetColorTheme()); m_Controls.widget_intensityProfile->SetTheme(this->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); PrepareDataStorageComboBoxes(); m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); CreateConnections(); } void QmitkImageStatisticsReloadedView::CreateConnections() { connect(this->m_CalculationThread, &QmitkImageStatisticsCalculationJob::finished, this, &QmitkImageStatisticsReloadedView::OnStatisticsCalculationEnds, Qt::QueuedConnection); connect(this->m_Controls.checkBox_ignoreZero, &QCheckBox::stateChanged, this, &QmitkImageStatisticsReloadedView::OnCheckBoxIgnoreZeroStateChanged); connect(this->m_Controls.sliderWidget_histogram, &ctkSliderWidget::valueChanged, this, &QmitkImageStatisticsReloadedView::OnSliderWidgetHistogramChanged); connect(this->m_Controls.imageSelector, static_cast(&QComboBox::currentIndexChanged), this, &QmitkImageStatisticsReloadedView::OnImageSelectorChanged); connect(this->m_Controls.maskImageSelector, static_cast(&QComboBox::currentIndexChanged), this, &QmitkImageStatisticsReloadedView::OnMaskSelectorChanged); } void QmitkImageStatisticsReloadedView::OnCheckBoxIgnoreZeroStateChanged(int state) { m_ForceRecompute = true; if (state != Qt::Unchecked) { this->m_CalculationThread->SetIgnoreZeroValueVoxel(true); } else { this->m_CalculationThread->SetIgnoreZeroValueVoxel(false); } CalculateOrGetStatistics(); } void QmitkImageStatisticsReloadedView::OnSliderWidgetHistogramChanged(double value) { unsigned int timeStep = static_cast(value); auto mask = m_selectedMaskNode ? m_selectedMaskNode->GetData() : nullptr; auto imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(this->GetDataStorage(), m_selectedImageNode->GetData(), mask); HistogramType::ConstPointer histogram = imageStatistics->GetStatisticsForTimeStep(timeStep).m_Histogram; if (histogram.IsNotNull() && this->m_CalculationThread->GetStatisticsUpdateSuccessFlag()) { this->FillHistogramWidget({ histogram }, { m_selectedImageNode->GetName() }); } } void QmitkImageStatisticsReloadedView::PartClosed(const berry::IWorkbenchPartReference::Pointer&) { } void QmitkImageStatisticsReloadedView::FillHistogramWidget(const std::vector& histogram, const std::vector& dataLabels) { m_Controls.groupBox_histogram->setVisible(true); m_Controls.widget_histogram->SetTheme(this->GetColorTheme()); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->SetHistogram(histogram.front(), dataLabels.front()); connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, this, &QmitkImageStatisticsReloadedView::OnRequestHistogramUpdate); } -QmitkChartWidget::ChartStyle QmitkImageStatisticsReloadedView::GetColorTheme() const +QmitkChartWidget::ColorTheme QmitkImageStatisticsReloadedView::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::ChartStyle::darkstyle; + return QmitkChartWidget::ColorTheme::darkstyle; } else { - return QmitkChartWidget::ChartStyle::lightstyle; + return QmitkChartWidget::ColorTheme::lightstyle; } } - return QmitkChartWidget::ChartStyle::darkstyle; + return QmitkChartWidget::ColorTheme::darkstyle; } void QmitkImageStatisticsReloadedView::OnImageSelectorChanged() { auto selectedImageNode = m_Controls.imageSelector->GetSelectedNode(); if (selectedImageNode != m_selectedImageNode) { m_selectedImageNode = selectedImageNode; if (m_selectedImageNode.IsNotNull()) { auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto hasSameGeometry = mitk::NodePredicateGeometry::New(m_selectedImageNode->GetData()->GetGeometry()); hasSameGeometry->SetCheckPrecision(1e-10); auto isMaskWithGeometryPredicate = mitk::NodePredicateAnd::New(isMaskPredicate, hasSameGeometry); auto isMaskOrPlanarFigureWithGeometryPredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskWithGeometryPredicate); //prevent triggering of computation as the predicate triggers a signalChanged event m_Controls.maskImageSelector->disconnect(); m_Controls.maskImageSelector->SetPredicate(isMaskOrPlanarFigureWithGeometryPredicate); m_Controls.maskImageSelector->SetZeroEntryText(""); CalculateOrGetStatistics(); m_Controls.widget_statistics->SetImageNodes({ m_selectedImageNode.GetPointer() }); connect(this->m_Controls.maskImageSelector, static_cast(&QComboBox::currentIndexChanged), this, &QmitkImageStatisticsReloadedView::OnMaskSelectorChanged); } else { m_Controls.widget_statistics->SetImageNodes({}); m_Controls.widget_statistics->Reset(); } } } void QmitkImageStatisticsReloadedView::OnMaskSelectorChanged() { auto selectedMaskNode = m_Controls.maskImageSelector->GetSelectedNode(); if (selectedMaskNode != m_selectedMaskNode) { m_selectedMaskNode = selectedMaskNode; if (m_selectedMaskNode.IsNotNull()) { m_Controls.widget_statistics->SetMaskNodes({ m_selectedMaskNode.GetPointer() }); } else { m_Controls.widget_statistics->SetMaskNodes({}); } CalculateOrGetStatistics(); } } void QmitkImageStatisticsReloadedView::CalculateOrGetStatistics() { if (this->m_selectedPlanarFigure) { this->m_selectedPlanarFigure->RemoveObserver(this->m_PlanarFigureObserverTag); this->m_selectedPlanarFigure = nullptr; } m_Controls.groupBox_intensityProfile->setVisible(false); m_Controls.widget_statistics->setEnabled(m_selectedImageNode.IsNotNull()); if (m_selectedImageNode != nullptr) { auto image = dynamic_cast(m_selectedImageNode->GetData()); mitk::Image::Pointer mask = nullptr; mitk::PlanarFigure::Pointer maskPlanarFigure = nullptr; if (image->GetDimension() == 4) { m_Controls.sliderWidget_histogram->setVisible(true); unsigned int maxTimestep = image->GetTimeSteps(); m_Controls.sliderWidget_histogram->setMaximum(maxTimestep - 1); } else { m_Controls.sliderWidget_histogram->setVisible(false); } if (m_selectedMaskNode != nullptr) { mask = dynamic_cast(m_selectedMaskNode->GetData()); if (mask == nullptr) { maskPlanarFigure = dynamic_cast(m_selectedMaskNode->GetData()); } } mitk::ImageStatisticsContainer::ConstPointer imageStatistics; if (mask) { auto imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(this->GetDataStorage(), image, mask.GetPointer()); } else if (maskPlanarFigure) { m_selectedPlanarFigure = maskPlanarFigure; ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction(this, &QmitkImageStatisticsReloadedView::CalculateOrGetStatistics); this->m_PlanarFigureObserverTag = m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); if (!maskPlanarFigure->IsClosed()) { ComputeAndDisplayIntensityProfile(image, maskPlanarFigure); } imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(this->GetDataStorage(), image, maskPlanarFigure.GetPointer()); } else { imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(this->GetDataStorage(),image); } bool imageStatisticsOlderThanInputs = false; if (imageStatistics && (imageStatistics->GetMTime() < image->GetMTime() || (mask && imageStatistics->GetMTime() < mask->GetMTime()) || (maskPlanarFigure && imageStatistics->GetMTime() < maskPlanarFigure->GetMTime()))) { imageStatisticsOlderThanInputs = true; } //statistics need to be computed if (!imageStatistics || imageStatisticsOlderThanInputs || m_ForceRecompute) { CalculateStatistics(image, mask.GetPointer(), maskPlanarFigure.GetPointer()); } //statistics already computed else { if (!(maskPlanarFigure && maskPlanarFigure->IsClosed())) { if (imageStatistics->TimeStepExists(0)) { auto histogram = imageStatistics->GetStatisticsForTimeStep(0).m_Histogram.GetPointer(); std::string imageNodeName = m_selectedImageNode->GetName(); this->FillHistogramWidget({histogram}, {imageNodeName }); } } } } else { ResetGUI(); } m_ForceRecompute = false; } void QmitkImageStatisticsReloadedView::ComputeAndDisplayIntensityProfile(mitk::Image * image, mitk::PlanarFigure::Pointer maskPlanarFigure) { auto intensityProfile = mitk::ComputeIntensityProfile(image, maskPlanarFigure); //Don't show histogram for intensity profiles m_Controls.groupBox_histogram->setVisible(false); m_Controls.groupBox_intensityProfile->setVisible(true); m_Controls.widget_intensityProfile->Reset(); m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), "Intensity Profile of " + m_selectedImageNode->GetName()); } void QmitkImageStatisticsReloadedView::ResetGUI() { m_Controls.widget_statistics->Reset(); //m_Controls.widget_statistics->setEnabled(false); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->setEnabled(false); } void QmitkImageStatisticsReloadedView::OnStatisticsCalculationEnds() { mitk::StatusBar::GetInstance()->Clear(); if (this->m_CalculationThread->GetStatisticsUpdateSuccessFlag()) { auto statistic = m_CalculationThread->GetStatisticsData(); mitk::PropertyRelations::RuleResultVectorType rulesForCurrentStatistic; auto statisticNonConst = statistic->Clone(); auto statisticsNodeName = m_selectedImageNode->GetName(); if (m_selectedMaskNode) { statisticsNodeName += "_" + m_selectedMaskNode->GetName(); } statisticsNodeName += "_statistics"; auto statisticsNode = mitk::CreateImageStatisticsNode(statisticNonConst, statisticsNodeName); auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(statisticNonConst.GetPointer(), m_CalculationThread->GetStatisticsImage().GetPointer()); rulesForCurrentStatistic.push_back(imageRule.GetPointer()); if (m_CalculationThread->GetMaskImage()) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); maskRule->Connect(statisticNonConst.GetPointer(), m_CalculationThread->GetMaskImage().GetPointer()); rulesForCurrentStatistic.push_back(maskRule.GetPointer()); } else if (m_CalculationThread->GetPlanarFigure()) { auto planarFigureRule = mitk::StatisticsToMaskRelationRule::New(); planarFigureRule->Connect(statisticNonConst.GetPointer(), m_CalculationThread->GetPlanarFigure().GetPointer()); rulesForCurrentStatistic.push_back(planarFigureRule.GetPointer()); } m_statisticContainerRules.push_back(rulesForCurrentStatistic); this->GetDataStorage()->Add(statisticsNode); if (!m_selectedPlanarFigure || m_selectedPlanarFigure->IsClosed()) { this->FillHistogramWidget({ m_CalculationThread->GetTimeStepHistogram() }, { m_selectedImageNode->GetName() }); } } else { mitk::StatusBar::GetInstance()->DisplayErrorText(m_CalculationThread->GetLastErrorMessage().c_str()); m_Controls.widget_histogram->setEnabled(false); } m_Controls.label_currentlyComputingStatistics->setVisible(false); } void QmitkImageStatisticsReloadedView::OnRequestHistogramUpdate(unsigned int nBins) { m_CalculationThread->SetHistogramNBins(nBins); m_CalculationThread->start(); } void QmitkImageStatisticsReloadedView::CalculateStatistics(mitk::Image::ConstPointer image, mitk::Image::ConstPointer mask, mitk::PlanarFigure::ConstPointer maskPlanarFigure) { this->m_StatisticsUpdatePending = true; auto renderPart = this->GetRenderWindowPart(); unsigned int timeStep = renderPart->GetTimeNavigationController()->GetTime()->GetPos(); this->m_CalculationThread->Initialize(image, mask, maskPlanarFigure); this->m_CalculationThread->SetTimeStep(timeStep); try { // Compute statistics this->m_CalculationThread->start(); m_Controls.label_currentlyComputingStatistics->setVisible(true); } catch (const mitk::Exception& e) { mitk::StatusBar::GetInstance()->DisplayErrorText(e.GetDescription()); this->m_StatisticsUpdatePending = false; m_Controls.label_currentlyComputingStatistics->setVisible(false); } catch (const std::runtime_error &e) { mitk::StatusBar::GetInstance()->DisplayErrorText(e.what()); this->m_StatisticsUpdatePending = false; m_Controls.label_currentlyComputingStatistics->setVisible(false); } catch (const std::exception &e) { mitk::StatusBar::GetInstance()->DisplayErrorText(e.what()); this->m_StatisticsUpdatePending = false; m_Controls.label_currentlyComputingStatistics->setVisible(false); } } void QmitkImageStatisticsReloadedView::OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &nodes) { Q_UNUSED(part); Q_UNUSED(nodes); } void QmitkImageStatisticsReloadedView::PrepareDataStorageComboBoxes() { auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); m_Controls.imageSelector->SetDataStorage(GetDataStorage()); m_Controls.imageSelector->SetPredicate(isImagePredicate); m_Controls.maskImageSelector->SetDataStorage(GetDataStorage()); m_Controls.maskImageSelector->SetPredicate(isMaskOrPlanarFigurePredicate); m_Controls.maskImageSelector->SetZeroEntryText(""); } void QmitkImageStatisticsReloadedView::Activated() { } void QmitkImageStatisticsReloadedView::Deactivated() { } void QmitkImageStatisticsReloadedView::Visible() { connect(this->m_Controls.imageSelector, static_cast(&QComboBox::currentIndexChanged), this, &QmitkImageStatisticsReloadedView::OnImageSelectorChanged); connect(this->m_Controls.maskImageSelector, static_cast(&QComboBox::currentIndexChanged), this, &QmitkImageStatisticsReloadedView::OnMaskSelectorChanged); OnImageSelectorChanged(); OnMaskSelectorChanged(); } void QmitkImageStatisticsReloadedView::Hidden() { m_Controls.imageSelector->disconnect(); m_Controls.maskImageSelector->disconnect(); } void QmitkImageStatisticsReloadedView::SetFocus() { } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.h index d4fafae072..05e506068b 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.h @@ -1,112 +1,112 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkImageStatisticsReloadedView_H__INCLUDED #define QmitkImageStatisticsReloadedView_H__INCLUDED #include "ui_QmitkImageStatisticsReloadedViewControls.h" // Qmitk includes #include #include #include #include #include #include /*! \brief QmitkImageStatisticsView is a bundle that allows statistics calculation from images. Three modes are supported: 1. Statistics of one image, 2. Statistics of an image and a segmentation, 3. Statistics of an image and a Planar Figure. The statistics calculation is realized in a separate thread to keep the gui accessible during calculation. \ingroup Plugins/org.mitk.gui.qt.measurementtoolbox */ class QmitkImageStatisticsReloadedView : public QmitkAbstractView, public mitk::ILifecycleAwarePart, public berry::IPartListener { Q_OBJECT public: using HistogramType = mitk::ImageStatisticsContainer::HistogramType; /*! \brief default constructor */ QmitkImageStatisticsReloadedView(QObject *parent = nullptr, const char *name = nullptr); /*! \brief default destructor */ virtual ~QmitkImageStatisticsReloadedView(); /*! \brief method for creating the widget containing the application controls, like sliders, buttons etc. */ virtual void CreateQtPartControl(QWidget *parent) override; /*! \brief method for creating the connections of main and control widget */ virtual void CreateConnections(); /*! \brief Is called from the selection mechanism once the data manager selection has changed*/ void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &selectedNodes) override; void PrepareDataStorageComboBoxes(); static const std::string VIEW_ID; void FillHistogramWidget(const std::vector& histogram, const std::vector& dataLabels); - QmitkChartWidget::ChartStyle GetColorTheme() const; + QmitkChartWidget::ColorTheme GetColorTheme() const; protected: virtual void Activated() override; virtual void Deactivated() override; virtual void Visible() override; virtual void Hidden() override; virtual void SetFocus() override; /** \brief Is called right before the view closes (before the destructor) */ virtual void PartClosed(const berry::IWorkbenchPartReference::Pointer&) override; /** \brief Required for berry::IPartListener */ virtual Events::Types GetPartEventTypes() const override { return Events::CLOSED; } void OnImageSelectorChanged(); void OnMaskSelectorChanged(); void CalculateOrGetStatistics(); void ComputeAndDisplayIntensityProfile(mitk::Image * image, mitk::PlanarFigure::Pointer maskPlanarFigure); void ResetGUI(); void OnStatisticsCalculationEnds(); void OnRequestHistogramUpdate(unsigned int nBins); void OnCheckBoxIgnoreZeroStateChanged(int state); void OnSliderWidgetHistogramChanged(double value); void CalculateStatistics(mitk::Image::ConstPointer image, mitk::Image::ConstPointer mask=nullptr, mitk::PlanarFigure::ConstPointer maskPlanarFigure = nullptr); // member variables Ui::QmitkImageStatisticsReloadedViewControls m_Controls; private: typedef itk::SimpleMemberCommand< QmitkImageStatisticsReloadedView > ITKCommandType; QmitkImageStatisticsCalculationJob * m_CalculationThread = nullptr; bool m_StatisticsUpdatePending=false; mitk::DataNode::ConstPointer m_selectedImageNode = nullptr, m_selectedMaskNode = nullptr; std::vector m_statisticContainerRules; mitk::PlanarFigure::Pointer m_selectedPlanarFigure=nullptr; long m_PlanarFigureObserverTag; bool m_ForceRecompute = false; }; #endif // QmitkImageStatisticsView_H__INCLUDED 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 b0e5c8b7be..31aabd3b2c 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,1298 +1,1298 @@ /*=================================================================== 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 "QmitkImageStatisticsView.h" // Qt includes #include #include #include // berry includes #include // mitk includes #include #include #include #include #include #include #include #include #include // itk includes #include "itksys/SystemTools.hxx" #include "itkImageRegionConstIteratorWithIndex.h" #include //blueberry includes #include #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; const int QmitkImageStatisticsView::STAT_TABLE_BASE_HEIGHT = 180; QmitkImageStatisticsView::QmitkImageStatisticsView(QObject* /*parent*/, const char* /*name*/) : m_Controls(nullptr), m_SelectedImage(nullptr), m_SelectedImageMask(nullptr), m_SelectedPlanarFigure(nullptr), m_ImageObserverTag(-1), m_ImageMaskObserverTag(-1), m_PlanarFigureObserverTag(-1), m_TimeObserverTag(-1), m_CurrentStatisticsValid(false), m_StatisticsUpdatePending(false), m_DataNodeSelectionChanged(false), m_Visible(false) { this->m_CalculationThread = new QmitkImageStatisticsCalculationJob; } QmitkImageStatisticsView::~QmitkImageStatisticsView() { if (m_SelectedImage != nullptr) m_SelectedImage->RemoveObserver(m_ImageObserverTag); if (m_SelectedImageMask != nullptr) m_SelectedImageMask->RemoveObserver(m_ImageMaskObserverTag); if (m_SelectedPlanarFigure != nullptr) m_SelectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); while (this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } delete this->m_CalculationThread; } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { if (m_Controls == nullptr) { m_Controls = new Ui::QmitkImageStatisticsViewControls; m_Controls->setupUi(parent); CreateConnections(); m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); m_Controls->m_BinSizeFrame->setEnabled(false); } } void QmitkImageStatisticsView::OnPageSuccessfullyLoaded() { berry::IPreferencesService* prefService = berry::WorkbenchPlugin::GetDefault()->GetPreferencesService(); m_StylePref = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); QString styleName = m_StylePref->Get(berry::QtPreferences::QT_STYLE_NAME, ""); if (styleName == ":/org.blueberry.ui.qt/darkstyle.qss") { - this->m_Controls->m_JSHistogram->SetTheme(QmitkChartWidget::ChartStyle::darkstyle); + this->m_Controls->m_JSHistogram->SetTheme(QmitkChartWidget::ColorTheme::darkstyle); } else { - this->m_Controls->m_JSHistogram->SetTheme(QmitkChartWidget::ChartStyle::lightstyle); + this->m_Controls->m_JSHistogram->SetTheme(QmitkChartWidget::ColorTheme::lightstyle); } } void QmitkImageStatisticsView::CreateConnections() { if (m_Controls) { connect((QObject*)(this->m_Controls->m_ButtonCopyHistogramToClipboard), SIGNAL(clicked()), (QObject*)this, SLOT(OnClipboardHistogramButtonClicked())); connect((QObject*)(this->m_Controls->m_ButtonCopyStatisticsToClipboard), SIGNAL(clicked()), (QObject*)this, SLOT(OnClipboardStatisticsButtonClicked())); connect((QObject*)(this->m_Controls->m_IgnoreZerosCheckbox), SIGNAL(clicked()), (QObject*)this, SLOT(OnIgnoreZerosCheckboxClicked())); connect((QObject*)this->m_CalculationThread, SIGNAL(finished()), this, SLOT(OnThreadedStatisticsCalculationEnds()), Qt::QueuedConnection); connect((QObject*)this, SIGNAL(StatisticsUpdate()), this, SLOT(RequestStatisticsUpdate()), Qt::QueuedConnection); connect((QObject*)this->m_Controls->m_StatisticsTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(JumpToCoordinates(int, int))); connect((QObject*)(this->m_Controls->m_barRadioButton), SIGNAL(clicked()), (QObject*)(this), SLOT(OnBarRadioButtonSelected())); connect((QObject*)(this->m_Controls->m_lineRadioButton), SIGNAL(clicked()), (QObject*)(this), SLOT(OnLineRadioButtonSelected())); connect((QObject*)(this->m_Controls->m_HistogramNBinsSpinbox), SIGNAL(editingFinished()), this, SLOT(OnHistogramNBinsCheckBoxValueChanged())); connect((QObject*)(this->m_Controls->m_UseDefaultNBinsCheckBox), SIGNAL(clicked()), (QObject*)this, SLOT(OnDefaultNBinsSpinBoxChanged())); connect((QObject*)(this->m_Controls->m_ShowSubchartCheckBox), SIGNAL(clicked()), (QObject*)this, SLOT(OnShowSubchartBoxChanged())); connect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), (QObject*)this, SLOT(OnPageSuccessfullyLoaded())); } } void QmitkImageStatisticsView::OnDefaultNBinsSpinBoxChanged() { if (this->m_Controls->m_UseDefaultNBinsCheckBox->isChecked()) { m_Controls->m_HistogramNBinsSpinbox->setValue(100); this->m_CalculationThread->SetHistogramNBins(m_Controls->m_HistogramNBinsSpinbox->value()); m_HistogramNBins = m_Controls->m_HistogramNBinsSpinbox->value(); } m_Controls->m_BinSizeFrame->setEnabled(!m_Controls->m_UseDefaultNBinsCheckBox->isChecked()); this->UpdateStatistics(); } void QmitkImageStatisticsView::OnShowSubchartBoxChanged() { bool showSubchart = this->m_Controls->m_ShowSubchartCheckBox->isChecked(); this->m_Controls->m_JSHistogram->SetShowSubchart(showSubchart); this->m_Controls->m_JSHistogram->Reload(); } void QmitkImageStatisticsView::OnBarRadioButtonSelected() { this->m_Controls->m_JSHistogram->SetChartTypeForAllDataAndReload(QmitkChartWidget::ChartType::bar); } void QmitkImageStatisticsView::OnLineRadioButtonSelected() { this->m_Controls->m_JSHistogram->SetChartTypeForAllDataAndReload(QmitkChartWidget::ChartType::line); } void QmitkImageStatisticsView::PartClosed(const berry::IWorkbenchPartReference::Pointer&) { } void QmitkImageStatisticsView::OnTimeChanged(const itk::EventObject& e) { if (this->m_SelectedDataNodes.isEmpty() || this->m_SelectedImage == nullptr) return; const mitk::SliceNavigationController::GeometryTimeEvent* timeEvent = dynamic_cast(&e); assert(timeEvent != nullptr); int timestep = timeEvent->GetPos(); if (this->m_SelectedImage->GetTimeSteps() > 1) { for (int x = 0; x < this->m_Controls->m_StatisticsTable->columnCount(); x++) { for (int y = 0; y < this->m_Controls->m_StatisticsTable->rowCount(); y++) { QTableWidgetItem* item = this->m_Controls->m_StatisticsTable->item(y, x); if (item == nullptr) break; if (x == timestep) { item->setBackgroundColor(Qt::yellow); } else { if (y % 2 == 0) item->setBackground(this->m_Controls->m_StatisticsTable->palette().base()); else item->setBackground(this->m_Controls->m_StatisticsTable->palette().alternateBase()); } } } this->m_Controls->m_StatisticsTable->viewport()->update(); } if ((this->m_SelectedImage->GetTimeSteps() == 1 && timestep == 0) || this->m_SelectedImage->GetTimeSteps() > 1) { m_Controls->m_JSHistogram->Clear(); QmitkImageStatisticsCalculationJob::HistogramType::ConstPointer histogram = (QmitkImageStatisticsCalculationJob::HistogramType::ConstPointer)this->m_CalculationThread->GetTimeStepHistogram(timestep); if (histogram.IsNotNull()) { bool statisticsUpdateSuccessful = this->m_CalculationThread->GetStatisticsUpdateSuccessFlag(); if (statisticsUpdateSuccessful) { auto imageNameLabel = m_Controls->m_SelectedFeatureImageLabel->text().toStdString(); this->m_Controls->m_JSHistogram->AddData2D(ConvertHistogramToMap(histogram), imageNameLabel); if (this->m_Controls->m_lineRadioButton->isChecked()) { this->m_Controls->m_JSHistogram->SetChartType(imageNameLabel, QmitkChartWidget::ChartType::line); } else { this->m_Controls->m_JSHistogram->SetChartType(imageNameLabel, QmitkChartWidget::ChartType::bar); } this->m_Controls->m_JSHistogram->SetXAxisLabel("Grey value"); this->m_Controls->m_JSHistogram->SetYAxisLabel("Frequency"); this->m_Controls->m_JSHistogram->Show(this->m_Controls->m_ShowSubchartCheckBox->isChecked()); } } } } void QmitkImageStatisticsView::JumpToCoordinates(int row, int col) { if (m_SelectedDataNodes.isEmpty()) { MITK_WARN("QmitkImageStatisticsView") << "No data node selected for statistics calculation."; return; } mitk::Point3D world; if (row == 5 && !m_WorldMinList.empty()) world = m_WorldMinList[col]; else if (row == 4 && !m_WorldMaxList.empty()) world = m_WorldMaxList[col]; else return; mitk::IRenderWindowPart* part = this->GetRenderWindowPart(); if (part) { part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()->SelectSliceByPoint(world); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), col); part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SetGeometryTime(timeEvent); } } void QmitkImageStatisticsView::OnIgnoreZerosCheckboxClicked() { emit StatisticsUpdate(); } void QmitkImageStatisticsView::OnClipboardHistogramButtonClicked() { if (!m_CurrentStatisticsValid) { QApplication::clipboard()->clear(); } if (m_SelectedPlanarFigure == nullptr) { const unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); typedef mitk::ImageStatisticsCalculator::HistogramType HistogramType; const HistogramType *histogram = this->m_CalculationThread->GetTimeStepHistogram(t).GetPointer(); QString clipboard("Measurement \t Frequency\n"); for (HistogramType::ConstIterator it = histogram->Begin(); it != histogram->End(); ++it) { clipboard = clipboard.append("%L1 \t %L2\n") .arg(it.GetMeasurementVector()[0], 0, 'f', 2) .arg(it.GetFrequency()); } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard); } //If a (non-closed) PlanarFigure is selected, display a line profile widget else if (m_SelectedPlanarFigure != nullptr) { QString clipboard("Pixel \t Intensity\n"); for (unsigned int i = 0; i < m_IntensityProfileList.size(); i++) { clipboard = clipboard.append("%L1 \t %L2\n").arg(QString::number(i)).arg(QString::number(m_IntensityProfileList.at(i))); } QApplication::clipboard()->setText(clipboard, QClipboard::Clipboard); } } void QmitkImageStatisticsView::OnClipboardStatisticsButtonClicked() { QLocale tempLocal; QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); if (m_CurrentStatisticsValid && !(m_SelectedPlanarFigure != nullptr)) { const auto &statistics = this->m_CalculationThread->GetStatisticsData(); // Set time borders for for loop ;) unsigned int startT, endT; if (this->m_Controls->m_CheckBox4dCompleteTable->checkState() == Qt::CheckState::Unchecked) { startT = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); endT = startT + 1; } else { startT = 0; endT = statistics->GetNumberOfTimeSteps(); } QVector< QVector > statisticsTable; QStringList headline{ "Timestep", "Mean", "Median", "StdDev", "RMS", "Max", "Min", "NumberOfVoxels", "Skewness", "Kurtosis", "Uniformity", "Entropy", "MPP", "UPP", "V [mm³]" }; for (int i = 0; i < headline.size(); i++) { QVector row; row.append(headline.at(i)); statisticsTable.append(row); } // Fill Table for (unsigned int t = startT; t < endT; t++) { // Copy statistics to clipboard ("%Ln" will use the default locale for // number formatting) QStringList value; value << QString::number(t) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::MEAN())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::MEDIAN())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::STANDARDDEVIATION())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::RMS())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::MAXIMUM())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::MINIMUM())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::NUMBEROFVOXELS())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::SKEWNESS())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::KURTOSIS())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::UNIFORMITY())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::ENTROPY())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::MPP())) << QString::number(statistics->GetStatisticsForTimeStep(t).GetValueConverted(mitk::ImageStatisticsConstants::UPP())) << QString::number(m_Controls->m_StatisticsTable->item(7, 0)->data(Qt::DisplayRole).toDouble()); for (int z = 0; z < value.size(); z++) { statisticsTable[z].append(value.at(z)); } } // Create output string QString clipboard; for (int i = 0; i < statisticsTable.size(); i++) { for (int t = 0; t < statisticsTable.at(i).size(); t++) { clipboard.append(statisticsTable.at(i).at(t)); clipboard.append("\t"); } clipboard.append("\n"); } QApplication::clipboard()->setText(clipboard, QClipboard::Clipboard); } else { QApplication::clipboard()->clear(); } QLocale::setDefault(tempLocal); } void QmitkImageStatisticsView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList &nodes) { if (this->m_Visible) { this->SelectionChanged(nodes); } else { this->m_DataNodeSelectionChanged = true; } } void QmitkImageStatisticsView::SelectionChanged(const QList &selectedNodes) { m_Controls->m_JSHistogram->Clear(); if (this->m_StatisticsUpdatePending) { this->m_DataNodeSelectionChanged = true; return; // not ready for new data now! } if (selectedNodes.size() == this->m_SelectedDataNodes.size()) { int i = 0; for (; i < selectedNodes.size(); ++i) { if (selectedNodes.at(i) != this->m_SelectedDataNodes.at(i)) { break; } } // node selection did not change if (i == selectedNodes.size()) return; } //reset the feature image and image mask field m_Controls->m_SelectedFeatureImageLabel->setText("None"); m_Controls->m_SelectedMaskLabel->setText("None"); this->ReinitData(); if (selectedNodes.isEmpty()) { DisableHistogramGUIElements(); } else { EnableHistogramGUIElements(); ResetHistogramGUIElementsToDefault(); } if (selectedNodes.size() == 1 || selectedNodes.size() == 2) { bool isBinary = false; selectedNodes.value(0)->GetBoolProperty("binary", isBinary); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); isBinary |= isLabelSet->CheckNode(selectedNodes.value(0)); if (isBinary) { EnableHistogramGUIElements(); m_Controls->m_InfoLabel->setText(""); } for (int i = 0; i < selectedNodes.size(); ++i) { this->m_SelectedDataNodes.push_back(selectedNodes.at(i)); } this->m_DataNodeSelectionChanged = false; this->m_Controls->m_ErrorMessageLabel->setText(""); this->m_Controls->m_ErrorMessageLabel->hide(); emit StatisticsUpdate(); } else { this->m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::DisableHistogramGUIElements() { m_Controls->m_InfoLabel->setText(""); m_Controls->groupBox_histogram->setEnabled(false); m_Controls->groupBox_statistics->setEnabled(false); } void QmitkImageStatisticsView::ResetHistogramGUIElementsToDefault() { m_Controls->m_barRadioButton->setChecked(true); m_Controls->m_HistogramNBinsSpinbox->setValue(100); m_HistogramNBins = m_Controls->m_HistogramNBinsSpinbox->value(); m_Controls->m_UseDefaultNBinsCheckBox->setChecked(true); m_Controls->m_ShowSubchartCheckBox->setChecked(true); m_Controls->m_BinSizeFrame->setEnabled(false); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_HistogramNBinsSpinbox->setEnabled(true); this->m_CalculationThread->SetHistogramNBins(m_Controls->m_HistogramNBinsSpinbox->value()); } void QmitkImageStatisticsView::EnableHistogramGUIElements() { m_Controls->groupBox_histogram->setEnabled(true); m_Controls->groupBox_plot->setEnabled(true); m_Controls->groupBox_statistics->setEnabled(true); } void QmitkImageStatisticsView::ReinitData() { while (this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if (this->m_SelectedImage != nullptr) { this->m_SelectedImage->RemoveObserver(this->m_ImageObserverTag); this->m_SelectedImage = nullptr; } if (this->m_SelectedImageMask != nullptr) { this->m_SelectedImageMask->RemoveObserver(this->m_ImageMaskObserverTag); this->m_SelectedImageMask = nullptr; } if (this->m_SelectedPlanarFigure != nullptr) { this->m_SelectedPlanarFigure->RemoveObserver(this->m_PlanarFigureObserverTag); this->m_SelectedPlanarFigure = nullptr; } this->m_SelectedDataNodes.clear(); this->m_StatisticsUpdatePending = false; m_Controls->m_ErrorMessageLabel->setText(""); m_Controls->m_ErrorMessageLabel->hide(); this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); } void QmitkImageStatisticsView::OnThreadedStatisticsCalculationEnds() { m_Controls->m_ErrorMessageLabel->setText(""); m_Controls->m_ErrorMessageLabel->hide(); this->WriteStatisticsToGUI(); } void QmitkImageStatisticsView::UpdateStatistics() { mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); if (renderPart == nullptr) { this->m_StatisticsUpdatePending = false; return; } m_WorldMinList.clear(); m_WorldMaxList.clear(); // classify selected nodes mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateOr::Pointer imagePredicate = mitk::NodePredicateOr::New(isImage, isLabelSet); std::string maskName; std::string maskType; std::string featureImageName; unsigned int maskDimension = 0; // reset data from last run ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction(this, &QmitkImageStatisticsView::SelectedDataModified); mitk::DataNode::Pointer planarFigureNode; for (int i = 0; i < this->m_SelectedDataNodes.size(); ++i) { mitk::PlanarFigure::Pointer planarFig = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); if (imagePredicate->CheckNode(this->m_SelectedDataNodes.at(i))) { bool isMask = false; this->m_SelectedDataNodes.at(i)->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(this->m_SelectedDataNodes.at(i)); if (this->m_SelectedImageMask == nullptr && isMask) { this->m_SelectedImageMask = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageMaskObserverTag = this->m_SelectedImageMask->AddObserver(itk::ModifiedEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = m_SelectedImageMask->GetNameOfClass(); maskDimension = 3; } else if (!isMask) { if (this->m_SelectedImage == nullptr) { this->m_SelectedImage = static_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } featureImageName = this->m_SelectedDataNodes.at(i)->GetName(); } } else if (planarFig.IsNotNull()) { if (this->m_SelectedPlanarFigure == nullptr) { this->m_SelectedPlanarFigure = planarFig; this->m_PlanarFigureObserverTag = this->m_SelectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = this->m_SelectedPlanarFigure->GetNameOfClass(); maskDimension = 2; planarFigureNode = m_SelectedDataNodes.at(i); } } else { m_Controls->m_ErrorMessageLabel->setText("Invalid data node type!"); m_Controls->m_ErrorMessageLabel->show(); } } if (maskName == "") { maskName = "None"; maskType = ""; maskDimension = 0; } if (featureImageName == "") { featureImageName = "None"; } if (m_SelectedPlanarFigure != nullptr && m_SelectedImage == nullptr) { mitk::DataStorage::SetOfObjects::ConstPointer parentSet = this->GetDataStorage()->GetSources(planarFigureNode); for (unsigned int i = 0; i < parentSet->Size(); i++) { mitk::DataNode::Pointer node = parentSet->ElementAt(i); if (imagePredicate->CheckNode(node)) { bool isMask = false; node->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(node); if (!isMask) { if (this->m_SelectedImage == nullptr) { this->m_SelectedImage = static_cast(node->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } } } } } unsigned int timeStep = renderPart->GetTimeNavigationController()->GetTime()->GetPos(); if (m_SelectedImage != nullptr && m_SelectedImage->IsInitialized()) { // Check if a the selected image is a multi-channel image. If yes, statistics // cannot be calculated currently. if (m_SelectedImage->GetPixelType().GetNumberOfComponents() > 1) { m_Controls->m_ErrorMessageLabel->setText("Multi-component images not supported."); m_Controls->m_ErrorMessageLabel->show(); this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); m_CurrentStatisticsValid = false; this->m_StatisticsUpdatePending = false; this->DisableHistogramGUIElements(); m_Controls->m_InfoLabel->setText(""); return; } std::stringstream maskLabel; maskLabel << maskName; if (maskDimension > 0) { maskLabel << " [" << maskDimension << "D " << maskType << "]"; } m_Controls->m_SelectedMaskLabel->setText(maskLabel.str().c_str()); m_Controls->m_SelectedFeatureImageLabel->setText(featureImageName.c_str()); // check time step validity if (m_SelectedImage->GetDimension() <= 3 && timeStep > m_SelectedImage->GetDimension(3) - 1) { timeStep = m_SelectedImage->GetDimension(3) - 1; } // Add the used mask time step to the mask label so the user knows which mask time step was used // if the image time step is bigger than the total number of mask time steps (see // ImageStatisticsCalculator::ExtractImageAndMask) if (m_SelectedImageMask != nullptr) { unsigned int maskTimeStep = timeStep; if (maskTimeStep >= m_SelectedImageMask->GetTimeSteps()) { maskTimeStep = m_SelectedImageMask->GetTimeSteps() - 1; } m_Controls->m_SelectedMaskLabel->setText(m_Controls->m_SelectedMaskLabel->text() + QString(" (t=") + QString::number(maskTimeStep) + QString(")")); } // check if the segmentation mask is empty if (m_SelectedImageMask != nullptr) { auto maskStatistics = m_SelectedImageMask->GetStatistics(); mitk::ScalarType maskMaxValue = maskStatistics->GetScalarValueMax(0); if (m_SelectedImageMask->GetDimension() == 4) { for (unsigned int curTimestep = 1; curTimestep < m_SelectedImageMask->GetTimeSteps(); curTimestep++) { maskMaxValue = std::max(maskStatistics->GetScalarValueMax(curTimestep), maskMaxValue); } } bool segmentationIsEmpty = maskMaxValue == 0; if (segmentationIsEmpty) { m_Controls->m_ErrorMessageLabel->setText("Empty segmentation mask selected..."); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; return; } } //// initialize thread and trigger it this->m_CalculationThread->SetIgnoreZeroValueVoxel(m_Controls->m_IgnoreZerosCheckbox->isChecked()); this->m_CalculationThread->Initialize(m_SelectedImage, m_SelectedImageMask, m_SelectedPlanarFigure); this->m_CalculationThread->SetTimeStep(timeStep); m_Controls->m_ErrorMessageLabel->setText("Calculating statistics..."); m_Controls->m_ErrorMessageLabel->show(); try { // Compute statistics this->m_CalculationThread->start(); } catch (const mitk::Exception& e) { m_Controls->m_ErrorMessageLabel->setText("" + QString(e.GetDescription()) + ""); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch (const std::runtime_error &e) { // In case of exception, print error message on GUI m_Controls->m_ErrorMessageLabel->setText("" + QString(e.what()) + ""); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch (const std::exception &e) { MITK_ERROR << "Caught exception: " << e.what(); // In case of exception, print error message on GUI m_Controls->m_ErrorMessageLabel->setText("" + QString(e.what()) + ""); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } } else { this->m_StatisticsUpdatePending = false; } } void QmitkImageStatisticsView::SelectedDataModified() { if (!m_StatisticsUpdatePending) { emit StatisticsUpdate(); } } void QmitkImageStatisticsView::NodeRemoved(const mitk::DataNode *node) { while (this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if (node->GetData() == m_SelectedImage) { m_SelectedImage = nullptr; } } void QmitkImageStatisticsView::RequestStatisticsUpdate() { if (!m_StatisticsUpdatePending) { if (this->m_DataNodeSelectionChanged) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->m_StatisticsUpdatePending = true; this->UpdateStatistics(); } } if (this->GetRenderWindowPart()) this->GetRenderWindowPart()->RequestUpdate(); } void QmitkImageStatisticsView::OnHistogramNBinsCheckBoxValueChanged() { if (static_cast(m_Controls->m_HistogramNBinsSpinbox->value()) != m_HistogramNBins) { m_HistogramNBins = m_Controls->m_HistogramNBinsSpinbox->value(); this->m_CalculationThread->SetHistogramNBins(m_Controls->m_HistogramNBinsSpinbox->value()); this->UpdateStatistics(); } } void QmitkImageStatisticsView::WriteStatisticsToGUI() { m_Controls->m_JSHistogram->Clear(); m_IntensityProfileList.clear(); //Disconnect OnLineRadioButtonSelected() to prevent reloading chart when radiobutton is checked programmatically disconnect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), 0, 0); connect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), (QObject*)this, SLOT(OnPageSuccessfullyLoaded())); m_Controls->m_InfoLabel->setText(""); if (m_DataNodeSelectionChanged) { this->m_StatisticsUpdatePending = false; this->RequestStatisticsUpdate(); return; // stop visualization of results and calculate statistics of new selection } if (this->m_CalculationThread->GetStatisticsUpdateSuccessFlag()) { if (this->m_CalculationThread->GetStatisticsChangedFlag()) { // Do not show any error messages m_Controls->m_ErrorMessageLabel->hide(); m_CurrentStatisticsValid = true; } if (m_SelectedImage != nullptr) { //all statistics are now computed also on planar figures (lines, paths...)! // If a (non-closed) PlanarFigure is selected, display a line profile widget if (m_SelectedPlanarFigure != nullptr && !m_SelectedPlanarFigure->IsClosed()) { // check whether PlanarFigure is initialized const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_SelectedPlanarFigure->GetPlaneGeometry(); if (planarFigurePlaneGeometry != nullptr) { unsigned int timeStep = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); mitk::Image::Pointer image; if (this->m_CalculationThread->GetStatisticsImage()->GetDimension() == 4) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(this->m_CalculationThread->GetStatisticsImage()); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image = timeSelector->GetOutput(); } else { image = this->m_CalculationThread->GetStatisticsImage()->Clone(); } mitk::IntensityProfile::ConstPointer intensityProfile = (mitk::IntensityProfile::ConstPointer)mitk::ComputeIntensityProfile(image, m_SelectedPlanarFigure); m_IntensityProfileList = ConvertIntensityProfileToVector(intensityProfile); auto lineDataLabel = "Intensity profile " + m_Controls->m_SelectedMaskLabel->text().toStdString(); m_Controls->m_JSHistogram->SetChartType(lineDataLabel, QmitkChartWidget::ChartType::line); m_Controls->m_JSHistogram->AddData1D(m_IntensityProfileList, lineDataLabel); m_Controls->m_JSHistogram->SetXAxisLabel("Distance"); m_Controls->m_JSHistogram->SetYAxisLabel("Intensity"); m_Controls->m_JSHistogram->Show(m_Controls->m_ShowSubchartCheckBox->isChecked()); m_Controls->m_lineRadioButton->setChecked(true); m_Controls->m_lineRadioButton->setEnabled(false); m_Controls->m_barRadioButton->setEnabled(false); m_Controls->m_HistogramNBinsSpinbox->setEnabled(false); m_Controls->m_BinSizeFrame->setEnabled(false); m_Controls->m_UseDefaultNBinsCheckBox->setEnabled(false); //Reconnect OnLineRadioButtonSelected() connect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), (QObject*)this, SLOT(OnLineRadioButtonSelected())); auto statisticsContainer = this->m_CalculationThread->GetStatisticsData(); //only one entry (current timestep) this->FillLinearProfileStatisticsTableView(statisticsContainer, this->m_CalculationThread->GetStatisticsImage()); QString message("Only linegraph available for an intensity profile!"); if (this->m_CalculationThread->GetStatisticsImage()->GetDimension() == 4) { message += "Only current timestep displayed!"; } message += ""; m_Controls->m_InfoLabel->setText(message); m_CurrentStatisticsValid = true; } else { // Clear statistics, histogram, and GUI this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); m_CurrentStatisticsValid = false; m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_SelectedMaskLabel->setText("None"); this->m_StatisticsUpdatePending = false; m_Controls->m_InfoLabel->setText(""); return; } } else { m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); auto histogram = this->m_CalculationThread->GetTimeStepHistogram(this->m_CalculationThread->GetTimeStep()).GetPointer(); auto imageLabelName = m_Controls->m_SelectedFeatureImageLabel->text().toStdString(); m_Controls->m_JSHistogram->AddData2D(ConvertHistogramToMap(histogram), imageLabelName); m_Controls->m_JSHistogram->SetChartType(imageLabelName, QmitkChartWidget::ChartType::bar); this->m_Controls->m_JSHistogram->SetXAxisLabel("Gray value"); this->m_Controls->m_JSHistogram->SetYAxisLabel("Frequency"); m_Controls->m_UseDefaultNBinsCheckBox->setEnabled(true); m_Controls->m_JSHistogram->Show(this->m_Controls->m_ShowSubchartCheckBox->isChecked()); this->FillStatisticsTableView(this->m_CalculationThread->GetStatisticsData(), this->m_CalculationThread->GetStatisticsImage()); auto aStatistic = this->m_CalculationThread->GetStatisticsData(); auto statisticsNode = mitk::DataNode::New(); statisticsNode->SetName(m_Controls->m_SelectedFeatureImageLabel->text().toStdString()); statisticsNode->SetData(aStatistic->Clone()); statisticsNode->SetProperty("helper object", mitk::BoolProperty::New(true)); this->GetDataStorage()->Add(statisticsNode); } m_CurrentStatisticsValid = true; } } else { m_Controls->m_SelectedMaskLabel->setText("None"); m_Controls->m_ErrorMessageLabel->setText(m_CalculationThread->GetLastErrorMessage().c_str()); m_Controls->m_ErrorMessageLabel->show(); // Clear statistics and histogram this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); m_CurrentStatisticsValid = false; } berry::IPreferencesService* prefService = berry::WorkbenchPlugin::GetDefault()->GetPreferencesService(); m_StylePref = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); this->m_StatisticsUpdatePending = false; } void QmitkImageStatisticsView::FillStatisticsTableView( mitk::ImageStatisticsContainer::ConstPointer statistics, const mitk::Image *image) { this->m_Controls->m_StatisticsTable->setColumnCount(image->GetTimeSteps()); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(image->GetTimeSteps() > 1); // Set Checkbox for complete copy of statistic table if (image->GetTimeSteps() > 1) { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(true); } else { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(false); this->m_Controls->m_CheckBox4dCompleteTable->setChecked(false); } for (unsigned int t = 0; t < image->GetTimeSteps(); t++) { this->m_Controls->m_StatisticsTable->setHorizontalHeaderItem(t, new QTableWidgetItem(QString::number(t))); auto minIndex = statistics->GetStatisticsForTimeStep(t).GetValueConverted( mitk::ImageStatisticsConstants::MINIMUMPOSITION()); auto maxIndex = statistics->GetStatisticsForTimeStep(t).GetValueConverted( mitk::ImageStatisticsConstants::MAXIMUMPOSITION()); if (maxIndex.size() == 3) { mitk::Point3D index, max, min; index[0] = maxIndex[0]; index[1] = maxIndex[1]; index[2] = maxIndex[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, max); this->m_WorldMaxList.push_back(max); index[0] = minIndex[0]; index[1] = minIndex[1]; index[2] = minIndex[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, min); this->m_WorldMinList.push_back(min); } auto statisticsVector = AssembleStatisticsIntoVector(statistics, image); unsigned int count = 0; for (const auto& entry : statisticsVector) { auto item = new QTableWidgetItem(entry); this->m_Controls->m_StatisticsTable->setItem(count, t, item); count++; } } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); // make sure the current timestep's column is highlighted (and the correct histogram is displayed) unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), t); this->OnTimeChanged(timeEvent); t = std::min(image->GetTimeSteps() - 1, t); // See bug 18340 /*QString hotspotMean; hotspotMean.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMean(), 0, 'f', decimals)); hotspotMean += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( hotspotMean ) ); QString hotspotMax; hotspotMax.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMax(), 0, 'f', decimals)); hotspotMax += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( hotspotMax ) ); QString hotspotMin; hotspotMin.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMin(), 0, 'f', decimals)); hotspotMin += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( hotspotMin ) );*/ } std::vector QmitkImageStatisticsView::AssembleStatisticsIntoVector(mitk::ImageStatisticsContainer::ConstPointer statistics, mitk::Image::ConstPointer image, bool noVolumeDefined) const { std::vector result; unsigned int decimals = 2; //statistics of higher order should have 5 decimal places because they used to be very small unsigned int decimalsHigherOrderStatistics = 5; if (image->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE || image->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT) { decimals = 5; } auto statistic = statistics->GetStatisticsForTimeStep(0); result.push_back(GetFormattedString( statistic.GetValueConverted(mitk::ImageStatisticsConstants::MEAN()), decimals)); result.push_back(GetFormattedString( statistic.GetValueConverted(mitk::ImageStatisticsConstants::MEDIAN()), decimals)); result.push_back(GetFormattedString( statistic.GetValueConverted(mitk::ImageStatisticsConstants::STANDARDDEVIATION()), decimals)); result.push_back(GetFormattedString( statistic.GetValueConverted(mitk::ImageStatisticsConstants::RMS()), decimals)); result.push_back(GetFormattedString( statistic.GetValueConverted(mitk::ImageStatisticsConstants::MAXIMUM()), decimals) + " " + GetFormattedIndex(statistic.GetValueConverted(mitk::ImageStatisticsConstants::MAXIMUMPOSITION()))); result.push_back(GetFormattedString( statistic.GetValueConverted(mitk::ImageStatisticsConstants::MINIMUM()), decimals) + " " + GetFormattedIndex(statistic.GetValueConverted(mitk::ImageStatisticsConstants::MINIMUMPOSITION()))); auto numberVoxels = statistic.GetValueConverted(mitk::ImageStatisticsConstants::NUMBEROFVOXELS()); //to prevent large negative values of empty image statistics if (numberVoxels != std::numeric_limits::min()) { result.push_back(GetFormattedString(numberVoxels, 0)); const mitk::BaseGeometry *geometry = image->GetGeometry(); if (geometry != NULL && !noVolumeDefined) { const mitk::Vector3D &spacing = image->GetGeometry()->GetSpacing(); double volume = spacing[0] * spacing[1] * spacing[2] * static_cast(numberVoxels); result.push_back(GetFormattedString(volume, decimals)); } else { result.push_back("NA"); } } else { result.push_back("NA"); result.push_back("NA"); } result.push_back( GetFormattedString(statistic.GetValueConverted(mitk::ImageStatisticsConstants::SKEWNESS()), decimalsHigherOrderStatistics)); result.push_back( GetFormattedString(statistic.GetValueConverted(mitk::ImageStatisticsConstants::KURTOSIS()), decimalsHigherOrderStatistics)); result.push_back( GetFormattedString(statistic.GetValueConverted(mitk::ImageStatisticsConstants::UNIFORMITY()), decimalsHigherOrderStatistics)); result.push_back( GetFormattedString(statistic.GetValueConverted(mitk::ImageStatisticsConstants::ENTROPY()), decimalsHigherOrderStatistics)); result.push_back( GetFormattedString(statistic.GetValueConverted(mitk::ImageStatisticsConstants::MPP()), decimals)); result.push_back( GetFormattedString(statistic.GetValueConverted(mitk::ImageStatisticsConstants::UPP()), decimalsHigherOrderStatistics)); return result; } void QmitkImageStatisticsView::FillLinearProfileStatisticsTableView(mitk::ImageStatisticsContainer::ConstPointer statistics, const mitk::Image *image) { this->m_Controls->m_StatisticsTable->setColumnCount(1); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); m_PlanarFigureStatistics = this->AssembleStatisticsIntoVector(statistics, image, true); for (unsigned int i = 0; i < m_PlanarFigureStatistics.size(); i++) { this->m_Controls->m_StatisticsTable->setItem(i, 0, new QTableWidgetItem(m_PlanarFigureStatistics[i])); } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); } void QmitkImageStatisticsView::InvalidateStatisticsTableView() { this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); this->m_Controls->m_StatisticsTable->setColumnCount(1); for (int i = 0; i < this->m_Controls->m_StatisticsTable->rowCount(); ++i) { { this->m_Controls->m_StatisticsTable->setItem(i, 0, new QTableWidgetItem("NA")); } } this->m_Controls->m_StatisticsTable->setMinimumHeight(STAT_TABLE_BASE_HEIGHT); } void QmitkImageStatisticsView::Activated() { } void QmitkImageStatisticsView::Deactivated() { } void QmitkImageStatisticsView::Visible() { m_Visible = true; mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { itk::ReceptorMemberCommand::Pointer cmdTimeEvent = itk::ReceptorMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkImageStatisticsView::OnTimeChanged); // It is sufficient to add the observer to the axial render window since the GeometryTimeEvent // is always triggered by all views. m_TimeObserverTag = renderWindow->GetQmitkRenderWindow("axial")-> GetSliceNavigationController()-> AddObserver(mitk::SliceNavigationController::GeometryTimeEvent(nullptr, 0), cmdTimeEvent); } if (m_DataNodeSelectionChanged) { if (this->IsCurrentSelectionValid()) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->SelectionChanged(this->GetDataManagerSelection()); } m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::Hidden() { m_Visible = false; // The slice navigation controller observer is removed here instead of in the destructor. // If it was called in the destructor, the application would freeze because the view's // destructor gets called after the render windows have been destructed. if (m_TimeObserverTag != 0) { mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { renderWindow->GetQmitkRenderWindow("axial")->GetSliceNavigationController()-> RemoveObserver(m_TimeObserverTag); } m_TimeObserverTag = 0; } } void QmitkImageStatisticsView::SetFocus() { } std::map QmitkImageStatisticsView::ConvertHistogramToMap(itk::Statistics::Histogram::ConstPointer histogram) const { std::map histogramMap; 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); } return histogramMap; } std::vector QmitkImageStatisticsView::ConvertIntensityProfileToVector(mitk::IntensityProfile::ConstPointer intensityProfile) const { std::vector intensityProfileList; auto end = intensityProfile->End(); for (auto it = intensityProfile->Begin(); it != end; ++it) { intensityProfileList.push_back(it.GetMeasurementVector()[0]); } return intensityProfileList; } QString QmitkImageStatisticsView::GetFormattedString(double value, unsigned int decimals) const { typedef mitk::ImageStatisticsContainer::RealType RealType; RealType maxVal = std::numeric_limits::max(); if (value == maxVal) { return QString("NA"); } else { return QString("%1").arg(value, 0, 'f', decimals); } } QString QmitkImageStatisticsView::GetFormattedIndex(const vnl_vector& vector) const { if (vector.empty()) { return QString(); } QString formattedIndex("("); for (const auto& entry : vector) { formattedIndex += QString::number(entry); formattedIndex += ","; } formattedIndex.chop(1); formattedIndex += ")"; return formattedIndex; }