diff --git a/Modules/C3js/include/QmitkC3Data.h b/Modules/C3js/include/QmitkC3Data.h index f2eba4c31c..98400b6d1a 100644 --- a/Modules/C3js/include/QmitkC3Data.h +++ b/Modules/C3js/include/QmitkC3Data.h @@ -1,87 +1,99 @@ /*=================================================================== 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 QmitkC3Data_h #define QmitkC3Data_h #include /** /brief This class holds the relevant data for the chart generation with C3. * Documentation: This class holds the relevant data for the chart generation with C3. It forwards the frequencies and the measurements to JavaScript. * Those are then displayed in the histogram chart. * It is derived from QObject, because we need Q_PROPERTIES to send Data via QWebChannel to JavaScript. */ class QmitkC3Data : public QObject { Q_OBJECT typedef mitk::Image::HistogramType HistogramType; - Q_PROPERTY(QList m_Frequency READ GetFrequency WRITE SetFrequency NOTIFY OnFrequencyChaned); - Q_PROPERTY(QList m_Measurement READ GetMeasurement WRITE SetMeasurement NOTIFY OnMeasurementChaned); + Q_PROPERTY(QList m_YData READ GetYData WRITE SetYData NOTIFY SignalYDataChaned); + Q_PROPERTY(QList m_XData READ GetXData WRITE SetXData NOTIFY SignalXDataChaned); + Q_PROPERTY(QVariant m_UseLineChart READ GetUseLineChart WRITE SetUseLineChart NOTIFY SignalUseLineChartChanged); + Q_PROPERTY(QVariant m_ShowSubchart READ GetShowSubchart WRITE SetShowSubchart NOTIFY SignalShowSubchartChanged); public: QmitkC3Data(); QmitkC3Data(HistogramType::ConstPointer histogram); - Q_INVOKABLE QList GetFrequency() { return m_Frequency; }; - Q_INVOKABLE void SetFrequency(QList frequency) { m_Frequency = frequency; }; + void SetAppearance(bool UseLineChart, bool ShowSubChart); + Q_INVOKABLE QList GetYData() { return m_YData; }; + Q_INVOKABLE void SetYData(QList YData) { m_YData = YData; }; - Q_INVOKABLE QList GetMeasurement() { return m_Measurement; }; - Q_INVOKABLE void SetMeasurement(QList measurement) { m_Measurement = measurement; }; + Q_INVOKABLE QList GetXData() { return m_XData; }; + Q_INVOKABLE void SetXData(QList XData) { m_XData = XData; }; + Q_INVOKABLE QVariant GetUseLineChart() { return m_UseLineChart; }; + Q_INVOKABLE void SetUseLineChart(QVariant useLineChart) { m_UseLineChart = useLineChart; }; + + Q_INVOKABLE QVariant GetShowSubchart() { return m_ShowSubchart; }; + Q_INVOKABLE void SetShowSubchart(QVariant showSubchart) { m_ShowSubchart = showSubchart; }; /** * \brief Clears the Data. * * This function clears the data. */ void ClearData(); HistogramType::ConstPointer GetHistogram() { return m_Histogram; }; void SetHistogram(HistogramType::ConstPointer histogram) { m_Histogram = histogram; }; - QList* GetFrequencyPointer() { return &m_Frequency; }; + QList* GetYDataPointer() { return &m_YData; }; - QList* GetMeasurementPointer() { return &m_Measurement; }; + QList* GetXDataPointer() { return &m_XData; }; signals: - void OnFrequencyChaned(const QList frequency); - void OnMeasurementChaned(const QList measurement); - void OnUseLineGraphChanged(const bool useLineGraphChanged); + void SignalYDataChaned(const QList YData); + void SignalXDataChaned(const QList XData); + void SignalUseLineChartChanged(const QVariant useLineChart); + void SignalShowSubchartChanged(const QVariant showSubchart); private: /** * Holds the current histogram */ HistogramType::ConstPointer m_Histogram; /** * \brief List of frequencies. * * A QList which holds the frequencies of the current histogram * or holds the intensities of current intensity profile. */ - QList m_Frequency; + QList m_YData; /** * \brief List of measurements. * * A QList which holds the measurements of the current histogram * or holds the distances of current intensity profile. */ - QList m_Measurement; + QList m_XData; + + QVariant m_UseLineChart; + QVariant m_ShowSubchart; }; #endif //QmitkC3Data_h \ No newline at end of file diff --git a/Modules/C3js/include/QmitkC3jsWidget.h b/Modules/C3js/include/QmitkC3jsWidget.h index f194d7f7b1..f07cf0bcec 100644 --- a/Modules/C3js/include/QmitkC3jsWidget.h +++ b/Modules/C3js/include/QmitkC3jsWidget.h @@ -1,74 +1,75 @@ /*=================================================================== 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 QmitkD3jsWidget_h #define QmitkD3jsWidget_h #include #include #include "mitkImage.h" #include "mitkPlanarFigure.h" #include class MITKC3JS_EXPORT QmitkC3jsWidget : public QWidget { Q_OBJECT public: explicit QmitkC3jsWidget(QWidget* parent = nullptr); QmitkC3jsWidget(const QString& id, QObject* object, QWidget* parent = nullptr); virtual ~QmitkC3jsWidget(); typedef mitk::Image::HistogramType HistogramType; typedef mitk::Image::HistogramType::ConstIterator HistogramConstIteratorType; /** * \brief Calculates the histogram. * * This function removes all frequencies of 0 until the first bin and behind the last bin. * It writes the measurement and frequency, which are given from the HistogramType, into * m_Measurement and m_Frequency. * initializeJavaScriptChart is then called, to update the information, which is displayed in the webframe. */ - void ComputeHistogram(HistogramType* histogram); + void ComputeHistogram(HistogramType* histogram, bool UseLineChart, bool ShowSubChart); void ComputeIntensityProfile(unsigned int timeStep, bool computeStatistics); void ClearHistogram(); mitk::Image::Pointer GetImage() const; void SetImage(const mitk::Image::Pointer image); mitk::PlanarFigure::ConstPointer GetPlanarFigure() const; void SetPlanarFigure(const mitk::PlanarFigure::ConstPointer planarFigure); - void TransformView(QString transformTo); + void SetAppearance(bool UseLineChart, bool ShowSubChart); void SendCommand(QString command); + + void TransformView(QString transformTo); private: class Impl; Impl* m_Impl; public slots: void OnLoadFinished(bool IsLoadSuccessfull); signals: void PageSuccessfullyLoaded(); }; #endif diff --git a/Modules/C3js/resource/Histogram.js b/Modules/C3js/resource/Histogram.js index f6c69b8c01..743690a9c2 100644 --- a/Modules/C3js/resource/Histogram.js +++ b/Modules/C3js/resource/Histogram.js @@ -1,329 +1,332 @@ document.body.style.backgroundColor = 'rgb(240, 240, 240)'; var greyvalue; //needed to display the tooltip in the right format var binSize = 10; var min; var max; var minHeight = 255; var chart = c3.generate({ data: { x : 'x', //use first "column" as x axis values columns: [], //initialize empty. Data will be loaded in function setupChart(initValues) type: 'bar', selection: { enabled: false, multiple: false, } }, legend: { position: 'inset' }, grid: { y: { lines: [{value:0}] //Draws a horizontal line at y=0 } }, bar: { width: { ratio: 0.95 // this makes bar width 95% of length between ticks } }, zoom: { enabled: true, }, subchart: { show: true //Shows a subchart that shows the region the primary chart is zoomed in to by overlay. }, axis: { x: { type: 'category', //only for type 'category' the bars will be rescaled in width on zoom tick: { multiline: false, fit: false, //to make more x labels appear on zoom centered: true, }, }, y: { tick: { format: d3.format("s"), }, //for some reason, there is an offset for our linegraph. This is prevented by the following lines min: 0, padding: { top: 0, bottom: 0 } } }, //Style data points in linegraph point: { r: 0.2, focus: { expand: { r: 4 } } }, tooltip: { format: { title: function (d) { var endValue = (parseFloat(greyvalue[d]) + binSize); endValue = endValue.toFixed(3); return 'Greyvalue: ' + greyvalue[d] + '...' + endValue; }, } } }); var initValues; + window.onload = function() { new QWebChannel(qt.webChannelTransport, function(channel) { initValues = channel.objects.initValues; setupChart(initValues) }); } //This is necessary to resize the chart, after the size of the parent changed window.onresize = function () { var size = window.innerHeight-(window.innerHeight/100*10); //subtract 5% of height to hide vertical scrool bar if (size < minHeight) { size = minHeight; } chart.resize({ height: size, }); } +function ReloadChart(useLineChart, showSubchart) +{ + initValues.m_UseLineChart = useLineChart; + initValues.m_ShowSubchart = showSubchart; + + var chartType = 'bar'; + if (initValues.m_UseLineChart) { + chartType = 'line'; + } + + if (initValues.m_ShowSubchart) { + ShowSubchart(chartType) + } + else { + HideSubchart(chartType) + } + + setupChart(initValues); +} + function setupChart(initValues) { window.onresize(); calcBinSize(initValues); //copy measurements to xValues for x-axis-labels and to greyvalues for tooltips - var xValues = initValues.m_Measurement.slice(0); - greyvalue = initValues.m_Measurement.slice(0); + var xValues = initValues.m_XData.slice(0); + greyvalue = initValues.m_XData.slice(0); for (var i = 0; i < xValues.length; i++) { greyvalue[i] = greyvalue[i] - (binSize / 2); greyvalue[i] = greyvalue[i].toFixed(3); //change number format for x axis. Need to do it here, because it is not working on chart generation. xValues[i] = xValues[i]; xValues[i] = xValues[i].toFixed(); xValues[i] = d3.format("s")(xValues[i]); } xValues.unshift('x'); //add label to x array xValues.push(null); //append null value, to make sure the last tick on x-axis is displayed correctly - var yValues = initValues.m_Frequency; + var yValues = initValues.m_YData; yValues.unshift('Frequency'); //add label to y array xValues.push(null); //append null value, to make sure the last tick on x-axis is displayed correctly - + + chart.unload(); //unload data before loading new data chart.load({ columns:[ xValues, yValues ] }); - - if (initValues.useLineGraph) { - chart.transform('line'); - } } /* * Calculation of the bin size. */ function calcBinSize(initValues) { - if (1 < initValues.m_Measurement.length) { - min = d3.min(initValues.m_Measurement); - max = d3.max(initValues.m_Measurement); - binSize = ((max - min) / (initValues.m_Measurement.length - 1)); + if (1 < initValues.m_XData.length) { + min = d3.min(initValues.m_XData); + max = d3.max(initValues.m_XData); + binSize = ((max - min) / (initValues.m_XData.length - 1)); } else { binSize = 10; } } //Transforamtion between bar and line chart //takes the name of the chart type as a parameter //calles by QmitkC3jsWidget function transformView(TransformTo) { chart.transform(TransformTo); }; function changeTheme(color) { if (color == 'dark') { link = document.getElementsByTagName("link")[0]; link.href = "Histogram_dark.css"; } else { link = document.getElementsByTagName("link")[0]; link.href = "Histogram.css"; } }; -//Hacky way to show/hide the subchart -var IsSubchartShown = true; -function ToggleShowSubchart(chartType) -{ - if (IsSubchartShown == true){ - HideSubchart(chartType) - IsSubchartShown = false; - } - else{ - ShowSubchart(chartType) - IsSubchartShown = true; - } -} function ShowSubchart(chartType) { chart = c3.generate({ data: { x : 'x', //use first "column" as x axis values columns: [], //initialize empty. Data will be loaded in function setupChart(initValues) type: chartType, selection: { enabled: false, multiple: false, } }, legend: { position: 'inset' }, grid: { y: { lines: [{value:0}] //Draws a horizontal line at y=0 } }, bar: { width: { ratio: 0.95 // this makes bar width 95% of length between ticks } }, zoom: { enabled: true, }, subchart: { show: true //Shows a subchart that shows the region the primary chart is zoomed in to by overlay. }, axis: { x: { type: 'category', //only for type 'category' the bars will be rescaled in width on zoom tick: { multiline: false, fit: false, //to make more x labels appear on zoom centered: true, }, }, y: { tick: { format: d3.format("s"), }, //for some reason, there is an offset for our linegraph. This is prevented by the following lines min: 0, padding: { top: 0, bottom: 0 } } }, //Style data points in linegraph point: { r: 0.2, focus: { expand: { r: 4 } } }, tooltip: { format: { title: function (d) { var endValue = (parseFloat(greyvalue[d]) + binSize); endValue = endValue.toFixed(3); return 'Greyvalue: ' + greyvalue[d] + '...' + endValue; }, } } }); - setupChart(initValues); } function HideSubchart(chartType) { chart = c3.generate({ data: { x : 'x', //use first "column" as x axis values columns: [], //initialize empty. Data will be loaded in function setupChart(initValues) type: chartType, selection: { enabled: false, multiple: false, } }, legend: { position: 'inset' }, grid: { y: { lines: [{value:0}] //Draws a horizontal line at y=0 } }, bar: { width: { ratio: 0.95 // this makes bar width 95% of length between ticks } }, zoom: { enabled: true, }, subchart: { show: false //Shows a subchart that shows the region the primary chart is zoomed in to by overlay. }, axis: { x: { type: 'category', //only for type 'category' the bars will be rescaled in width on zoom tick: { multiline: false, fit: false, //to make more x labels appear on zoom centered: true, }, }, y: { tick: { format: d3.format("s"), }, //for some reason, there is an offset for our linegraph. This is prevented by the following lines min: 0, padding: { top: 0, bottom: 0 } } }, //Style data points in linegraph point: { r: 0.2, focus: { expand: { r: 4 } } }, tooltip: { format: { title: function (d) { var endValue = (parseFloat(greyvalue[d]) + binSize); endValue = endValue.toFixed(3); return 'Greyvalue: ' + greyvalue[d] + '...' + endValue; }, } } }); - setupChart(initValues); } \ No newline at end of file diff --git a/Modules/C3js/src/QmitkC3Data.cpp b/Modules/C3js/src/QmitkC3Data.cpp index 9e49759305..86438248ad 100644 --- a/Modules/C3js/src/QmitkC3Data.cpp +++ b/Modules/C3js/src/QmitkC3Data.cpp @@ -1,32 +1,40 @@ /*=================================================================== 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 QmitkC3Data::QmitkC3Data() { } QmitkC3Data::QmitkC3Data(HistogramType::ConstPointer histogram) - : m_Histogram(histogram) + : m_Histogram(histogram), + m_UseLineChart(false), + m_ShowSubchart(true) { } -void QmitkC3Data::ClearData() +void QmitkC3Data::SetAppearance(bool UseLineChart = false, bool ShowSubChart = true) { - this->m_Frequency.clear(); - this->m_Measurement.clear(); + m_UseLineChart = UseLineChart; + m_ShowSubchart = ShowSubChart; } + +void QmitkC3Data::ClearData() +{ + this->m_YData.clear(); + this->m_XData.clear(); +} \ No newline at end of file diff --git a/Modules/C3js/src/QmitkC3jsWidget.cpp b/Modules/C3js/src/QmitkC3jsWidget.cpp index 2463f2fbbe..6d717b767c 100644 --- a/Modules/C3js/src/QmitkC3jsWidget.cpp +++ b/Modules/C3js/src/QmitkC3jsWidget.cpp @@ -1,274 +1,284 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include #include #include "mitkImageTimeSelector.h" class QmitkC3jsWidget::Impl final { public: explicit Impl(QWidget* parent); ~Impl(); Impl(const Impl&) = delete; Impl& operator=(const Impl&) = delete; QWebChannel* GetWebChannel(); void ClearJavaScriptChart(); void initializeJavaScriptChart(); void callJavaScriptFuntion(QString command); - QmitkC3Data& GetC3Data() { return m_c3Data; }; + QmitkC3Data* GetC3Data() { return &m_c3Data; }; mitk::Image::Pointer GetImage() { return m_Image; }; const void SetImage(const mitk::Image::Pointer image) { m_Image = image; }; mitk::PlanarFigure::ConstPointer GetPlanarFigure() { return m_PlanarFigure; }; const void SetPlanarFigure(const mitk::PlanarFigure::ConstPointer planarFigure) { m_PlanarFigure = planarFigure; }; private: + QWidget* m_Parent; QWebChannel* m_WebChannel; QWebEngineView* m_WebEngineView; QmitkC3Data m_c3Data; /** * \brief Reference image. * * Holds the image to calculate an intensity profile. */ mitk::Image::Pointer m_Image; /** * \brief Pathelement. * * Holds a not closed planar figure to calculate an intensity profile. */ mitk::PlanarFigure::ConstPointer m_PlanarFigure; }; QmitkC3jsWidget::Impl::Impl(QWidget* parent) : m_WebChannel(new QWebChannel(parent)), - m_WebEngineView(new QWebEngineView(parent)) + m_WebEngineView(new QWebEngineView(parent)), + m_Parent(parent) { //disable context menu for QWebEngineView m_WebEngineView->setContextMenuPolicy(Qt::NoContextMenu); //Set the webengineview to an initial empty page. The actual chart will be loaded once the data is calculated. m_WebEngineView->setUrl(QUrl(QStringLiteral("qrc:///C3js/empty.html"))); m_WebEngineView->page()->setWebChannel(m_WebChannel); connect( m_WebEngineView, SIGNAL( loadFinished(bool) ), parent, SLOT( OnLoadFinished(bool) ) ); auto layout = new QGridLayout(parent); layout->setMargin(0); layout->addWidget(m_WebEngineView); parent->setLayout(layout); } QmitkC3jsWidget::Impl::~Impl() { } QWebChannel* QmitkC3jsWidget::Impl::GetWebChannel() { return m_WebChannel; } QmitkC3jsWidget::QmitkC3jsWidget(QWidget* parent) : QWidget(parent), m_Impl(new Impl(this)) { } void QmitkC3jsWidget::Impl::callJavaScriptFuntion(QString command) { m_WebEngineView->page()->runJavaScript(command); } void QmitkC3jsWidget::Impl::ClearJavaScriptChart() { m_WebEngineView->setUrl(QUrl(QStringLiteral("qrc:///C3js/empty.html"))); } void QmitkC3jsWidget::Impl::initializeJavaScriptChart() { m_WebChannel->registerObject(QStringLiteral("initValues"), &m_c3Data); m_WebEngineView->setUrl(QUrl(QStringLiteral("qrc:///C3js/QmitkC3jsWidget.html"))); } QmitkC3jsWidget::QmitkC3jsWidget(const QString& id, QObject* object, QWidget* parent) : QWidget(parent), m_Impl(new Impl(this)) { if (!id.isEmpty() && object != nullptr) m_Impl->GetWebChannel()->registerObject(id, object); } QmitkC3jsWidget::~QmitkC3jsWidget() { delete m_Impl; } void QmitkC3jsWidget::OnLoadFinished(bool IsLoadSuccessfull) { emit PageSuccessfullyLoaded(); } void QmitkC3jsWidget::TransformView(QString transformTo) { QString command = QString("transformView('" + transformTo + "')"); m_Impl->callJavaScriptFuntion(command); } void QmitkC3jsWidget::SendCommand(QString command) { m_Impl->callJavaScriptFuntion(command); } +void QmitkC3jsWidget::SetAppearance(bool UseLineChart, bool ShowSubChart) +{ + this->m_Impl->GetC3Data()->SetAppearance(UseLineChart, ShowSubChart); +} + // method to expose data to JavaScript by using properties -void QmitkC3jsWidget::ComputeHistogram(HistogramType* histogram) +void QmitkC3jsWidget::ComputeHistogram(HistogramType* histogram, bool UseLineChart, bool ShowSubChart) { - this->m_Impl->GetC3Data().SetHistogram(histogram); + this->m_Impl->GetC3Data()->SetHistogram(histogram); + + SetAppearance(UseLineChart, ShowSubChart); + HistogramConstIteratorType startIt = this->m_Impl->GetC3Data()->GetHistogram()->End(); + HistogramConstIteratorType endIt = this->m_Impl->GetC3Data()->GetHistogram()->End(); + HistogramConstIteratorType it = this->m_Impl->GetC3Data()->GetHistogram()->Begin(); + - HistogramConstIteratorType startIt = this->m_Impl->GetC3Data().GetHistogram()->End(); - HistogramConstIteratorType endIt = this->m_Impl->GetC3Data().GetHistogram()->End(); - HistogramConstIteratorType it = this->m_Impl->GetC3Data().GetHistogram()->Begin(); //Clear old data befor loading new data. - this->m_Impl->GetC3Data().ClearData(); + this->m_Impl->GetC3Data()->ClearData(); unsigned int i = 0; bool firstValue = false; // removes frequencies of 0, which are outside the first and last bin - for (; it != this->m_Impl->GetC3Data().GetHistogram()->End(); ++it) + for (; it != this->m_Impl->GetC3Data()->GetHistogram()->End(); ++it) { if (it.GetFrequency() > 0.0) { endIt = it; if (!firstValue) { firstValue = true; startIt = it; } } } ++endIt; // generating Lists of measurement and frequencies for (it = startIt; it != endIt; ++it, ++i) { QVariant frequency = QVariant::fromValue(it.GetFrequency()); QVariant measurement = it.GetMeasurementVector()[0]; - this->m_Impl->GetC3Data().GetFrequencyPointer()->insert(i, frequency); - this->m_Impl->GetC3Data().GetMeasurementPointer()->insert(i, measurement); + this->m_Impl->GetC3Data()->GetYDataPointer()->insert(i, frequency); + this->m_Impl->GetC3Data()->GetXDataPointer()->insert(i, measurement); } m_Impl->initializeJavaScriptChart(); } void QmitkC3jsWidget::ComputeIntensityProfile(unsigned int timeStep, bool computeStatistics) { this->ClearHistogram(); - //m_Impl->GetC3Data().ClearData(); + //m_Impl->GetC3Data()->ClearData(); //m_ParametricPath->Initialize(); if (m_Impl->GetPlanarFigure().IsNull()) { mitkThrow() << "PlanarFigure not set!"; } if (m_Impl->GetImage().IsNull()) { mitkThrow() << "Image not set!"; } mitk::Image::Pointer image; if (m_Impl->GetImage()->GetDimension() == 4) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Impl->GetImage()); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image = timeSelector->GetOutput(); } else { image = m_Impl->GetImage(); } mitk::IntensityProfile::Pointer intensityProfile = mitk::ComputeIntensityProfile(image, const_cast(m_Impl->GetPlanarFigure().GetPointer())); //m_Frequency.clear(); //m_Measurement.clear(); int i = -1; mitk::IntensityProfile::ConstIterator end = intensityProfile->End(); for (mitk::IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) { - m_Impl->GetC3Data().GetFrequencyPointer()->push_back(it.GetMeasurementVector()[0]); - //m_Impl->GetC3Data().GetFrequencyPointer()->push_back(50000); - m_Impl->GetC3Data().GetMeasurementPointer()->push_back(++i); + m_Impl->GetC3Data()->GetYDataPointer()->push_back(it.GetMeasurementVector()[0]); + //m_Impl->GetC3Data()->GetFrequencyPointer()->push_back(50000); + m_Impl->GetC3Data()->GetXDataPointer()->push_back(++i); } if (computeStatistics) { //mitk::ComputeIntensityProfileStatistics(intensityProfile, m_Statistics); } //m_IntensityProfile = true; //m_UseLineGraph = true; //this->SignalDataChanged(); m_Impl->initializeJavaScriptChart(); } void QmitkC3jsWidget::ClearHistogram() { - m_Impl->GetC3Data().ClearData(); + m_Impl->GetC3Data()->ClearData(); m_Impl->ClearJavaScriptChart(); } mitk::Image::Pointer QmitkC3jsWidget::GetImage() const { return m_Impl->GetImage(); } void QmitkC3jsWidget::SetImage(const mitk::Image::Pointer image) { m_Impl->SetImage(image); } mitk::PlanarFigure::ConstPointer QmitkC3jsWidget::GetPlanarFigure() const { return m_Impl->GetPlanarFigure(); } void QmitkC3jsWidget::SetPlanarFigure(const mitk::PlanarFigure::ConstPointer planarFigure) { m_Impl->SetPlanarFigure(planarFigure); } \ No newline at end of file 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 1544136478..b6f04fb70a 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,1350 +1,1361 @@ /*=================================================================== 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 "mitkNodePredicateDataType.h" #include "mitkNodePredicateOr.h" #include "mitkPlanarFigureInteractor.h" // itk includes #include "itksys/SystemTools.hxx" #include #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( NULL ), m_TimeStepperAdapter( NULL ), m_SelectedImage( NULL ), m_SelectedImageMask( NULL ), m_SelectedPlanarFigure( NULL ), 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 QmitkImageStatisticsCalculationThread; } QmitkImageStatisticsView::~QmitkImageStatisticsView() { if ( m_SelectedImage != NULL ) m_SelectedImage->RemoveObserver( m_ImageObserverTag ); if ( m_SelectedImageMask != NULL ) m_SelectedImageMask->RemoveObserver( m_ImageMaskObserverTag ); if ( m_SelectedPlanarFigure != NULL ) 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 == NULL) { 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->setVisible(false); } } 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_HistogramBinSizeSpinbox), SIGNAL(editingFinished()), this, SLOT(OnHistogramBinSizeBoxValueChanged())); connect((QObject*)(this->m_Controls->m_UseDefaultBinSizeBox), SIGNAL(clicked()), (QObject*) this, SLOT(OnDefaultBinSizeBoxChanged())); connect((QObject*)(this->m_Controls->m_ShowSubchartCheckBox), SIGNAL(clicked()), (QObject*) this, SLOT(OnShowSubchartBoxChanged())); } } void QmitkImageStatisticsView::OnDefaultBinSizeBoxChanged() { m_Controls->m_BinSizeFrame->setVisible(!m_Controls->m_UseDefaultBinSizeBox->isChecked()); if (m_CalculationThread != NULL){ m_Controls->m_HistogramBinSizeSpinbox->setValue(m_CalculationThread->GetHistogramBinSize()); m_CalculationThread->SetUseDefaultNBins(m_Controls->m_UseDefaultBinSizeBox->isChecked()); } this->UpdateStatistics(); } void QmitkImageStatisticsView::OnShowSubchartBoxChanged() { - QString chartType = "line"; - if (this->m_Controls->m_barRadioButton->isChecked()) - chartType = "bar"; - this->m_Controls->m_JSHistogram->SendCommand("ToggleShowSubchart('" + chartType + "')"); + this->m_Controls->m_JSHistogram->SetAppearance( + this->m_Controls->m_lineRadioButton->isChecked(), this->m_Controls->m_ShowSubchartCheckBox->isChecked()); + + QString useLineChart = "false"; + if (this->m_Controls->m_lineRadioButton->isChecked()) + useLineChart = "true"; + + QString showSubchart = "false"; + if (this->m_Controls->m_ShowSubchartCheckBox->isChecked()) + showSubchart = "true"; + + this->m_Controls->m_JSHistogram->SendCommand( + "ReloadChart(" + useLineChart + "," + showSubchart + ")"); } void QmitkImageStatisticsView::OnBarRadioButtonSelected() { this->m_Controls->m_JSHistogram->TransformView("bar"); } void QmitkImageStatisticsView::OnLineRadioButtonSelected() { this->m_Controls->m_JSHistogram->TransformView("line"); } void QmitkImageStatisticsView::PartClosed(const berry::IWorkbenchPartReference::Pointer& ) { } void QmitkImageStatisticsView::OnTimeChanged(const itk::EventObject& e) { if (this->m_SelectedDataNodes.isEmpty() || this->m_SelectedImage == NULL) return; const mitk::SliceNavigationController::GeometryTimeEvent* timeEvent = dynamic_cast(&e); assert(timeEvent != NULL); unsigned 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 == NULL) 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) { // display histogram for selected timestep this->m_Controls->m_JSHistogram->ClearHistogram(); QmitkImageStatisticsCalculationThread::HistogramType::Pointer histogram = this->m_CalculationThread->GetTimeStepHistogram(timestep); if (histogram.IsNotNull()) { bool closedFigure = this->m_CalculationThread->GetStatisticsUpdateSuccessFlag(); if ( closedFigure ) { - this->m_Controls->m_JSHistogram->ComputeHistogram(histogram.GetPointer()); + this->m_Controls->m_JSHistogram->ComputeHistogram( + histogram.GetPointer(), this->m_Controls->m_lineRadioButton->isChecked(), this->m_Controls->m_ShowSubchartCheckBox->isChecked() ); } //this->m_Controls->m_JSHistogram->ComputeHistogram(histogram.GetPointer()); /*else { m_Controls->m_JSHistogram->ComputeIntensityProfile(timestep, true); }*/ // this->m_Controls->m_JSHistogram->SignalGraphChanged(); // hacky way to make sure the protected SignalGraphChanged() is called //if (this->m_Controls->m_JSHistogram->GetUseLineGraph()) //{ //this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); //this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); //} //else //{ //this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); //this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); //} } } } 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==4 && !m_WorldMinList.empty()) world = m_WorldMinList[col]; else if (row==3 && !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 && !( m_SelectedPlanarFigure != NULL)) { 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 ) { if( m_Controls->m_HistogramBinSizeSpinbox->value() == 1.0) { clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( it.GetMeasurementVector()[0], 0, 'f', 0 ) .arg( it.GetFrequency() ); } else { 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_CurrentStatisticsValid && (m_SelectedPlanarFigure != NULL )) { /*auto intensity = m_Controls->m_JSHistogram->GetFrequency(); auto pixel = m_Controls->m_JSHistogram->GetMeasurement(); QString clipboard( "Pixel \t Intensity\n" ); auto j = pixel.begin(); for (auto i = intensity.begin(); i < intensity.end(); i++) { assert(j != pixel.end()); clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( (*j).toString()) .arg( (*i).toString()); j++; } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard ); */ } else { QApplication::clipboard()->clear(); } } void QmitkImageStatisticsView::OnClipboardStatisticsButtonClicked() { QLocale tempLocal; QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); if ( m_CurrentStatisticsValid && !( m_SelectedPlanarFigure != NULL)) { const std::vector &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.size(); } QVector< QVector > statisticsTable; QStringList headline; // Create Headline headline << " " << "Mean" << "Median" << "StdDev" << "RMS" << "Max" << "Min" << "NumberOfVoxels" << "Skewness" << "Kurtosis" << "Uniformity" << "Entropy" << "MPP" << "UPP" << "V [mm³]"; for(int i=0;i row; row.append(headline.at(i)); statisticsTable.append(row); } // Fill Table for(unsigned int t=startT;tGetMean()) << QString::number(statistics[t]->GetMedian()) << QString::number(statistics[t]->GetStd()) << QString::number(statistics[t]->GetRMS()) << QString::number(statistics[t]->GetMax()) << QString::number(statistics[t]->GetMin()) << QString::number(statistics[t]->GetN()) << QString::number(statistics[t]->GetSkewness()) << QString::number(statistics[t]->GetKurtosis()) << QString::number(statistics[t]->GetUniformity()) << QString::number(statistics[t]->GetEntropy()) << QString::number(statistics[t]->GetMPP()) << QString::number(statistics[t]->GetUPP()) << QString::number(m_Controls->m_StatisticsTable->item(7, 0)->data(Qt::DisplayRole).toDouble()); for(int z=0;zsetText(clipboard, QClipboard::Clipboard); } else { QApplication::clipboard()->clear(); } QLocale::setDefault(tempLocal); } void QmitkImageStatisticsView::OnSelectionChanged( berry::IWorkbenchPart::Pointer /*part*/, const QList &selectedNodes ) { if (this->m_Visible) { this->SelectionChanged( selectedNodes ); } else { this->m_DataNodeSelectionChanged = true; } } void QmitkImageStatisticsView::SelectionChanged(const QList &selectedNodes) { 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()) { m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); // m_Controls->horizontalLayout_3->setEnabled(false); m_Controls->groupBox->setEnabled(false); m_Controls->groupBox_3->setEnabled(false); } else { // m_Controls->horizontalLayout_3->setEnabled(true); m_Controls->groupBox->setEnabled(true); m_Controls->groupBox_3->setEnabled(true); } 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) { m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); } 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::ReinitData() { while( this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if(this->m_SelectedImage != NULL) { this->m_SelectedImage->RemoveObserver( this->m_ImageObserverTag); this->m_SelectedImage = NULL; } if(this->m_SelectedImageMask != NULL) { this->m_SelectedImageMask->RemoveObserver( this->m_ImageMaskObserverTag); this->m_SelectedImageMask = NULL; } if(this->m_SelectedPlanarFigure != NULL) { this->m_SelectedPlanarFigure->RemoveObserver( this->m_PlanarFigureObserverTag); this->m_SelectedPlanarFigure = NULL; } this->m_SelectedDataNodes.clear(); this->m_StatisticsUpdatePending = false; m_Controls->m_ErrorMessageLabel->setText( "" ); m_Controls->m_ErrorMessageLabel->hide(); this->InvalidateStatisticsTableView(); m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); } void QmitkImageStatisticsView::OnThreadedStatisticsCalculationEnds() { std::stringstream message; message << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->hide(); this->WriteStatisticsToGUI(); } void QmitkImageStatisticsView::UpdateStatistics() { mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); if ( renderPart == NULL ) { 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(); std::string maskType = std::string(); std::string featureImageName = std::string(); 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 == NULL && 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 == NULL) { 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 == NULL) { 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 { std::stringstream message; message << "" << "Invalid data node type!" << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); } } if(maskName == "") { maskName = "None"; maskType = ""; maskDimension = 0; } if(featureImageName == "") { featureImageName = "None"; } if (m_SelectedPlanarFigure != NULL && m_SelectedImage == NULL) { mitk::DataStorage::SetOfObjects::ConstPointer parentSet = this->GetDataStorage()->GetSources(planarFigureNode); for (int i=0; iSize(); 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 == NULL) { 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 != NULL && 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 ) { std::stringstream message; message << "Multi-component images not supported."; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_JSHistogram->ClearHistogram(); m_CurrentStatisticsValid = false; this->m_StatisticsUpdatePending = false; m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); 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 != NULL) { 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(")")); } //// 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 ); std::stringstream message; message << "Calculating statistics..."; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); try { // Compute statistics // this->m_CalculationThread->SetUseDefaultBinSize(m_Controls->m_UseDefaultBinSizeBox->isChecked()); this->m_CalculationThread->start(); } catch ( const mitk::Exception& e) { std::stringstream message; message << "" << e.GetDescription() << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::runtime_error &e ) { // In case of exception, print error message on GUI std::stringstream message; message << "" << e.what() << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); 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 std::stringstream message; message << "Error! Unequal Dimensions of Image and Segmentation. No recompute possible "; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); 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 = NULL; } } 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::OnHistogramBinSizeBoxValueChanged() { if (m_Controls->m_HistogramBinSizeSpinbox->value() != m_HistogramBinSize) { m_HistogramBinSize = m_Controls->m_HistogramBinSizeSpinbox->value(); this->m_CalculationThread->SetHistogramBinSize(m_Controls->m_HistogramBinSizeSpinbox->value()); this->UpdateStatistics(); } } void QmitkImageStatisticsView::WriteStatisticsToGUI() { disconnect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), 0, 0); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); 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_Controls->m_barRadioButton->isChecked()) { //m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); } m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_HistogramBinSizeSpinbox->setValue( this->m_CalculationThread->GetHistogramBinSize() ); //m_Controls->m_JSHistogram->ComputeHistogram( this->m_CalculationThread->GetTimeStepHistogram(this->m_CalculationThread->GetTimeStep()).GetPointer() ); this->FillStatisticsTableView( this->m_CalculationThread->GetStatisticsData(), this->m_CalculationThread->GetStatisticsImage()); } 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_Controls->m_JSHistogram->clearHistogram(); m_CurrentStatisticsValid = false; // If a (non-closed) PlanarFigure is selected, display a line profile widget if ( m_SelectedPlanarFigure != NULL ) { // Check if the (closed) planar figure is out of bounds and so no image mask could be calculated--> Intensity Profile can not be calculated bool outOfBounds = false; if ( m_SelectedPlanarFigure->IsClosed() && m_SelectedImageMask == NULL) { outOfBounds = true; std::stringstream message; message << "Planar figure is on a rotated image plane or outside the image bounds."; m_Controls->m_InfoLabel->setText(message.str().c_str()); } // check whether PlanarFigure is initialized const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_SelectedPlanarFigure->GetPlaneGeometry(); if ( !(planarFigurePlaneGeometry == NULL || outOfBounds)) { unsigned int timeStep = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); m_Controls->m_JSHistogram->SetImage(this->m_CalculationThread->GetStatisticsImage()); m_Controls->m_JSHistogram->SetPlanarFigure(m_SelectedPlanarFigure); connect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), (QObject*) this, SLOT(OnLineRadioButtonSelected())); m_Controls->m_JSHistogram->ComputeIntensityProfile(timeStep, true); //m_Controls->m_JSHistogram->ComputeIntensityProfile(timeStep); //this->ComputeIntensityProfile(m_SelectedPlanarFigure, this->m_CalculationThread->GetStatisticsImage(), timeStep, true); + m_Controls->m_lineRadioButton->setChecked(true); m_Controls->m_lineRadioButton->setEnabled(false); m_Controls->m_barRadioButton->setEnabled(false); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(false); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(false); // m_Controls->m_HistogramBinSizeLabel->setEnabled(false); this->FillLinearProfileStatisticsTableView( this->m_CalculationThread->GetStatisticsImage() ); std::stringstream message; message << "Only linegraph available for an intensity profile!"; m_Controls->m_InfoLabel->setText(message.str().c_str()); m_CurrentStatisticsValid = true; } else { // Clear statistics, histogram, and GUI this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_JSHistogram->ClearHistogram(); m_CurrentStatisticsValid = false; m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_SelectedMaskLabel->setText( "None" ); this->m_StatisticsUpdatePending = false; m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); if (!outOfBounds) m_Controls->m_InfoLabel->setText(QString("")); return; // Sebastian Wirkert: would suggest to remove this return, since it is an artifact of previous // code architecture. However, removing it will cause m_StatisticsUpdatePending to be set to false // in case of invalid statistics which it previously was not. } } } this->m_StatisticsUpdatePending = false; } void QmitkImageStatisticsView::FillStatisticsTableView( const std::vector &s, 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); } int decimals = 2; mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) { decimals = 5; } for (unsigned int t = 0; t < image->GetTimeSteps(); t++) { this->m_Controls->m_StatisticsTable->setHorizontalHeaderItem(t, new QTableWidgetItem(QString::number(t))); if (s[t]->GetMaxIndex().size()==3) { mitk::Point3D index, max, min; index[0] = s[t]->GetMaxIndex()[0]; index[1] = s[t]->GetMaxIndex()[1]; index[2] = s[t]->GetMaxIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, max); this->m_WorldMaxList.push_back(max); index[0] = s[t]->GetMinIndex()[0]; index[1] = s[t]->GetMinIndex()[1]; index[2] = s[t]->GetMinIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, min); this->m_WorldMinList.push_back(min); } typedef mitk::ImageStatisticsCalculator::StatisticsContainer::RealType RealType; RealType maxVal = std::numeric_limits::max(); this->m_Controls->m_StatisticsTable->setItem( 0, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetMean(), 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 1, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetMedian(), 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 2, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetStd(), 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 3, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetRMS(), 0, 'f', decimals) ) ); QString max; max.append(QString("%1").arg(s[t]->GetMax(), 0, 'f', decimals)); max += " ("; for (int i=0; iGetMaxIndex().size(); i++) { max += QString::number(s[t]->GetMaxIndex()[i]); if (iGetMaxIndex().size()-1) max += ","; } max += ")"; this->m_Controls->m_StatisticsTable->setItem( 4, t, new QTableWidgetItem( max ) ); QString min; min.append(QString("%1").arg(s[t]->GetMin(), 0, 'f', decimals)); min += " ("; for (int i=0; iGetMinIndex().size(); i++) { min += QString::number(s[t]->GetMinIndex()[i]); if (iGetMinIndex().size()-1) min += ","; } min += ")"; this->m_Controls->m_StatisticsTable->setItem( 5, t, new QTableWidgetItem( min ) ); this->m_Controls->m_StatisticsTable->setItem( 6, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetN()) ) ); const mitk::BaseGeometry *geometry = image->GetGeometry(); if ( geometry != NULL ) { const mitk::Vector3D &spacing = image->GetGeometry()->GetSpacing(); double volume = spacing[0] * spacing[1] * spacing[2] * (double) s[t]->GetN(); this->m_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( QString("%1").arg(volume, 0, 'f', decimals) ) ); } else { this->m_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( "NA" ) ); } //statistics of higher order should have 5 decimal places because they used to be very small this->m_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetSkewness(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetKurtosis(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 10, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetUniformity(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 11, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetEntropy(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 12, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetMPP(), 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 13, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetUPP(), 0, 'f', 5) ) ); } 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::CalculateStatisticsForPlanarFigure( const mitk::Image *image) { std::vector result; int decimals = 2; /*mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) { decimals = 5; } mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats = m_Controls->m_JSHistogram->GetStatistics(); typedef mitk::ImageStatisticsCalculator::StatisticsContainer::RealType RealType; RealType maxVal = std::numeric_limits::max(); if (stats->GetMean() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetMean(), 0, 'f', decimals)); } if (stats->GetMedian() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetMedian(), 0, 'f', decimals)); } if (stats->GetStd() == maxVal) { result.push_back(QString("NA")); } else { result.push_back( QString("%1").arg( stats->GetStd(), 0, 'f', decimals)); } if (stats->GetRMS() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg( stats->GetRMS(), 0, 'f', decimals)); } if (stats->GetMax() == maxVal) { result.push_back(QString("NA")); } else { QString max; max.append(QString("%1").arg(stats->GetMax(), 0, 'f', decimals)); result.push_back(max); } if (stats->GetMin() == maxVal) { result.push_back(QString("NA")); } else { QString min; min.append(QString("%1").arg(stats->GetMin(), 0, 'f', decimals)); result.push_back(min); } if (stats->GetN() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetN())); } result.push_back(QString("NA")); //statistics of higher order should have 5 decimal places because they used to be very small if (stats->GetSkewness() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetSkewness(), 0, 'f', 5 )); } if (stats->GetKurtosis() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetKurtosis(), 0, 'f', 5) ); } if (stats->GetUniformity() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetUniformity(), 0, 'f', 5) ); } if (stats->GetEntropy() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetEntropy(), 0, 'f', 5) ); } if (stats->GetMPP() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetMPP(), 0, 'f', decimals) ); } if (stats->GetUPP() == maxVal) { result.push_back(QString("NA")); } else { result.push_back(QString("%1").arg(stats->GetUPP(), 0, 'f', 5) ); } */ return result; } void QmitkImageStatisticsView::FillLinearProfileStatisticsTableView( const mitk::Image *image ) { this->m_Controls->m_StatisticsTable->setColumnCount(1); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); m_PlanarFigureStatistics = this->CalculateStatisticsForPlanarFigure(image); for (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 ( unsigned 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(NULL, 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 != NULL ) { mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { renderWindow->GetQmitkRenderWindow("axial")->GetSliceNavigationController()-> RemoveObserver( m_TimeObserverTag ); } m_TimeObserverTag = NULL; } } void QmitkImageStatisticsView::SetFocus() { }