diff --git a/Modules/QmitkExt/QmitkHistogramJSWidget.cpp b/Modules/QmitkExt/QmitkHistogramJSWidget.cpp index 2bfe5db82e..734070d1e6 100644 --- a/Modules/QmitkExt/QmitkHistogramJSWidget.cpp +++ b/Modules/QmitkExt/QmitkHistogramJSWidget.cpp @@ -1,107 +1,122 @@ /*=================================================================== 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 "QmitkHistogramJSWidget.h" QmitkHistogramJSWidget::QmitkHistogramJSWidget(QWidget *parent) : QWebView(parent) { + // set histogram to barchart in first instance + m_UseLineGraph = false; + + // prepare html for use connect(page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addJSObject())); - connect(this, SIGNAL(loadFinished()), this, SLOT(resetData())); QUrl myUrl = QUrl("qrc:/qmitk/Histogram.html"); setUrl(myUrl); // set Scrollbars to always disabled page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); } QmitkHistogramJSWidget::~QmitkHistogramJSWidget() { } // adds an Object of Type QmitkHistogramJSWidget to the JavaScript void QmitkHistogramJSWidget::addJSObject() { page()->mainFrame()->addToJavaScriptWindowObject(QString("histogramData"), this); } // reloads WebView, everytime its size has been changed, so the size of the Histogram fits to the size of the widget void QmitkHistogramJSWidget::resizeEvent(QResizeEvent* resizeEvent) { QWebView::resizeEvent(resizeEvent); this->reload(); } +// method to expose data to JavaScript by using properties void QmitkHistogramJSWidget::ComputeHistogram(HistogramType* histogram) { m_Histogram = histogram; HistogramConstIteratorType startIt = m_Histogram->End(); HistogramConstIteratorType endIt = m_Histogram->End(); HistogramConstIteratorType it; - m_Frequency.clear(); - m_Measurement.clear(); + clearData(); unsigned int i = 0; for (it = m_Histogram->Begin() ; it != m_Histogram->End(); ++it, ++i) { - QVariant frequency = it.GetFrequency(); - QVariant measurement = it.GetMeasurementVector()[0]; - m_Frequency.insert(i, frequency); - m_Measurement.insert(i, measurement); + // filter frequencies of 0 to guarantee a better view while using a mask + if (it.GetFrequency() != 0) + { + QVariant frequency = it.GetFrequency(); + QVariant measurement = it.GetMeasurementVector()[0]; + m_Frequency.insert(i, frequency); + m_Measurement.insert(i, measurement); + } } this->DataChanged(); } void QmitkHistogramJSWidget::clearData() { m_Frequency.clear(); m_Measurement.clear(); } void QmitkHistogramJSWidget::clearHistogram() { this->clearData(); this->DataChanged(); } QList QmitkHistogramJSWidget::getFrequency() { return m_Frequency; } QList QmitkHistogramJSWidget::getMeasurement() { return m_Measurement; } -void QmitkHistogramJSWidget::resetData(bool ok) +bool QmitkHistogramJSWidget::getUseLineGraph() { + return m_UseLineGraph; +} + +// slots for radio- and pushbuttons +void QmitkHistogramJSWidget::histogramToBarChart() +{ + m_UseLineGraph = false; this->DataChanged(); } -void QmitkHistogramJSWidget::testData() +void QmitkHistogramJSWidget::histogramToLineGraph() { - this->clearData(); - for (unsigned int i = 0; i<10; i++) - { - m_Frequency.insert(i,10); - m_Measurement.insert(i,i); - } + m_UseLineGraph = true; + this->DataChanged(); +} + +void QmitkHistogramJSWidget::resetView() +{ + this->reload(); } diff --git a/Modules/QmitkExt/QmitkHistogramJSWidget.h b/Modules/QmitkExt/QmitkHistogramJSWidget.h index 2bce1c1412..73c16b0bab 100644 --- a/Modules/QmitkExt/QmitkHistogramJSWidget.h +++ b/Modules/QmitkExt/QmitkHistogramJSWidget.h @@ -1,67 +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 QMITKHISTOGRAMJSWIDGET_H #define QMITKHISTOGRAMJSWIDGET_H #include #include #include #include "QmitkExtExports.h" #include #include "mitkImage.h" class QmitkExt_EXPORT QmitkHistogramJSWidget : public QWebView { Q_OBJECT + +// Properties which can be used in JavaScript Q_PROPERTY(QList measurement READ getMeasurement) Q_PROPERTY(QList frequency READ getFrequency) + Q_PROPERTY(bool useLineGraph + READ getUseLineGraph) public: typedef mitk::Image::HistogramType HistogramType; typedef mitk::Image::HistogramType::ConstIterator HistogramConstIteratorType; explicit QmitkHistogramJSWidget(QWidget *parent = 0); ~QmitkHistogramJSWidget(); void resizeEvent(QResizeEvent* resizeEvent); void ComputeHistogram(HistogramType* histogram); void clearHistogram(); QList getMeasurement(); QList getFrequency(); + bool getUseLineGraph(); private: QList m_Frequency; QList m_Measurement; + bool m_UseLineGraph; HistogramType::ConstPointer m_Histogram; void clearData(); - void testData(); private slots: void addJSObject(); - void resetData(bool ok); + +public slots: + void histogramToBarChart(); + void histogramToLineGraph(); + void resetView(); signals: void DataChanged(); - void sizeChanged(); }; #endif // QMITKHISTOGRAMJSWIDGET_H diff --git a/Modules/QmitkExt/resources/Histogram.html b/Modules/QmitkExt/resources/Histogram.html index e4fc5c5496..c296e5c856 100644 --- a/Modules/QmitkExt/resources/Histogram.html +++ b/Modules/QmitkExt/resources/Histogram.html @@ -1,13 +1,12 @@ Histogram - diff --git a/Modules/QmitkExt/resources/Histogram.js b/Modules/QmitkExt/resources/Histogram.js index dd64b39aee..ee8498466b 100644 --- a/Modules/QmitkExt/resources/Histogram.js +++ b/Modules/QmitkExt/resources/Histogram.js @@ -1,379 +1,291 @@ /** * @author Moritz Petry */ //var dataset = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 7, 18, 19, 22]; /*var indexNumber = Math.round(Math.random()*10+30); for (var i = 0; i< indexNumber; i++) { var newNumber = Math.random() * 100; dataset.push(newNumber); }*/ var dataset = new Array(); var frequency = new Array(); var measurement = new Array(); var dataset; -var data = [8,6,4,2,0,9,7,5,3,1]; - var margin = { - top : 30, + top : 0, bottom : 50, - left : 50, + left : 45, right : 20, }; var height = histogramData.height - margin.top - margin.bottom; var width = histogramData.width - margin.left - margin.right; var tension = 0.8; var connected = false; var dur = 1000; var useLinePlot = false; - +// connecting signal from qt side with JavaScript method if (!connected) { connected = true; histogramData.DataChanged.connect(updateHistogram); - histogramData.sizeChanged.connect(changeSize); } - - var xScale = d3.scale.linear() .domain([d3.min(histogramData.measurement),d3.max(histogramData.measurement)]) - .range([margin.left,width]); + .range([0,width]); var yScale = d3.scale.linear() .domain([0,d3.max(histogramData.frequency)]) .range([height,margin.top]); var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom"); var yAxis = d3.svg.axis() .scale(yScale) .orient("left"); var svg = d3.select("body") .append("svg") .attr("class", "svg") .attr("width", width + margin.right + margin.left) - .attr("height", height + margin.top + margin.bottom); + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate (" + margin.left + "," + margin.top + ")") + .call(d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 10]).on("zoom", zoom)); -svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + 0 + "," + height + ")") - .call(xAxis); +var vis = svg.append("svg") + .attr("width", width) + .attr("height", height); -svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + margin.left + "," + 0 + ")") - .call(yAxis); +svg.append("rect") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .attr("opacity", 0); -function changeSize () -{ - height = histogramData.height - margin.top - margin.bottom; - width = histogramData.width - margin.left - margin.right; - svg = d3.select("body") - .append("svg") - .attr("class", "svg") - .attr("width", width + margin.right + margin.left) - .attr("height", height + margin.top + margin.bottom); - changeHistogram(); -} +updateHistogram(); +// method to update and choose histogram function updateHistogram() { - if (!useLinePlot) + if (!histogramData.useLineGraph) { barChart(); } - else if (useLinePlot) + else if (histogramData.useLineGraph) { linePlot() } } -function changeHistogram() -{ - useLinePlot = !useLinePlot; - updateHistogram(); -} - +// method to display histogram as barchart function barChart() { +// match scale to current data xScale = d3.scale.linear() .domain([d3.min(histogramData.measurement),d3.max(histogramData.measurement)]) - .range([margin.left,width]); + .range([0,width]); yScale = d3.scale.linear() .domain([0,d3.max(histogramData.frequency)]) .range([height,margin.top]); xAxis = d3.svg.axis() .scale(xScale) .ticks(5) .orient("bottom"); yAxis = d3.svg.axis() .scale(yScale) .orient("left"); + svg.call(d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 10]).on("zoom", zoom)); + var linenull = d3.svg.line() .interpolate("linear") .x(function(d,i) { return xScale(histogramData.measurement[i]); }) .y(function(d) { return yScale(0); }); - svg.selectAll("path.line").transition().duration(dur).attr("d", linenull(histogramData.frequency)).remove(); +// element to animate transition from linegraph to barchart + vis.selectAll("path.line").transition().duration(dur).attr("d", linenull(histogramData.frequency)).remove(); - var bar = svg.selectAll("rect").data(histogramData.frequency); + var bar = vis.selectAll("rect.bar").data(histogramData.frequency); bar.enter().append("rect") .attr("class", "bar") .on("mouseover", function (d) {d3.select(this).style('fill', "red");}) .on("mouseout", function (d) {d3.select(this).style("fill", "steelblue");}) .attr("x", function(d,i) { return xScale(histogramData.measurement[i]); }) .attr("y", height) .attr("height", 0) .attr("width", (width/(histogramData.frequency.length + 1))) - .transition().duration(dur) + .transition().delay(dur).duration(dur*1.5) .attr("height", function(d) { return (height - yScale(d)); }) .attr("width", (width/(histogramData.frequency.length + 1))) .attr("y", function(d) { return yScale(d); }); - bar.transition().duration(dur) + bar.transition().delay(dur).duration(dur*1.5) .attr("x", function(d,i) { return xScale(histogramData.measurement[i]); }) .attr("y", function(d) { return yScale(d); }) .attr("height", function(d) { return (height - yScale(d)); }) .attr("width", (width/(histogramData.frequency.length + 1))) - bar.exit().transition().duration(dur) + bar.exit().transition().delay(dur).duration(dur*1.5) .attr("y", height) .attr("height", 0) .remove(); svg.selectAll("g") .transition() .duration(dur) .attr("opacity", 0) .remove(); svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + 0 + "," + height + ")") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") .transition().duration(dur) .attr("opacity", 100) .call(xAxis); svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + margin.left + "," + 0 + ")") + .attr("class", "y axis") .transition().duration(dur) .attr("opacity", 100) .call(yAxis); } +// method to display histogram as linegraph function linePlot() { +// match scale to current data xScale = d3.scale.linear() .domain([d3.min(histogramData.measurement),d3.max(histogramData.measurement)]) - .range([margin.left,width]); + .range([0,width]); yScale = d3.scale.linear() .domain([0,d3.max(histogramData.frequency)]) .range([height,margin.top]); xAxis = d3.svg.axis() .scale(xScale) .ticks(5) .orient("bottom"); yAxis = d3.svg.axis() .scale(yScale) .orient("left"); - svg.selectAll("rect") + svg.call(d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 10]).on("zoom", zoom)); + +// element to animate transition from barchart to linegraph + vis.selectAll("rect.bar") .transition() .duration(dur) .attr("height", 0) .attr("fill", "black") .remove(); var line = d3.svg.line() - .interpolate("linear") + .interpolate("cardinal") .x(function(d,i) { return xScale(histogramData.measurement[i]); }) .y(function(d) { return yScale(d); - }); + }) + .tension(0.8); var linenull = d3.svg.line() - .interpolate("linear") + .interpolate("cardinal") .x(function(d,i) { return xScale(histogramData.measurement[i]); }) .y(function(d) { return yScale(0); - }); + }) + .tension(0.8); - var graph = svg.selectAll("path.line").data([histogramData.frequency]); + var graph = vis.selectAll("path.line").data([histogramData.frequency]); graph.enter() .append("path") .attr("class", "line") .attr("d", linenull) .transition() .duration(dur) .attr("d", line); graph.transition() .duration(dur) .attr("d", line); graph.exit().transition().duration(dur).attr("d", linenull); svg.selectAll("g") .transition() .duration(dur) .attr("opacity", 0) .remove(); svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + 0 + "," + height + ")") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") .transition().duration(dur) .attr("opacity", 100) .call(xAxis); svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + margin.left + "," + 0 + ")") + .attr("class", "y axis") .transition().duration(dur) .attr("opacity", 100) .call(yAxis); + } -function Test() +// method to ensure barwidth is not smaller than 1px +function barWidth() { - xScale = d3.scale.linear() - .domain([0,data.length]) - .range([margin.left,width]); - - yScale = d3.scale.linear() - .domain([0,d3.max(data)]) - .range([height,margin.bottom]); - - xAxis = d3.svg.axis() - .scale(xScale) - .orient("bottom"); - - yAxis = d3.svg.axis() - .scale(yScale) - .orient("left"); - - var bar = svg.selectAll("rect").data(data); - - bar.enter().append("rect") - .attr("class", "bar") - .attr("x", function(d,i) { - return xScale(i); - }) - .attr("y", height) - .attr("height", 0) - .attr("width", width/(data.length + 1)) - .transition() - .attr("height", function(d) { - return (height-yScale(d)); - }) - .attr("width", width/(data.length + 1)) - .attr("y", function(d) { - return yScale(d); - }); - - bar.transition() - .attr("x", function(d,i) { - return xScale(i); - }) - .attr("y", function(d) { - return yScale(d); - }) - .attr("height", function(d) { - return (height-yScale(d)); - }) - .attr("width", width/(data.length + 1)); - - bar.exit().transition() - .attr("y", height) - .attr("height", 0) - .remove(); - - var line = d3.svg.line() - .interpolate("linear") - .x(function(d,i) { - return xScale(i); - }) - .y(function(d) { - return yScale(d); - }); - - var linenull = d3.svg.line() - .interpolate("linear") - .x(function(d,i) { - return xScale(i); - }) - .y(function(d) { - return yScale(0); - }); - - var graph = svg.selectAll("path.line").data([data]); - - graph.enter() - .append("path") - .attr("class", "line") - .attr("d", linenull) - .transition() - .duration(dur) - .attr("d", line); - - - graph.transition() - .duration(dur) - .attr("d", line); - - graph.exit().transition().duration(dur).attr("d", linenull); - - svg.selectAll("g").transition().attr("opacity", 0).remove(); - - svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + 0 + "," + height + ")") - .transition() - .call(xAxis); - - svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + margin.left + "," + 0 + ")") - .transition() - .call(yAxis); + var barWidth = width/data.length - 1; + if (barWidth < 1) + { + barWidth = 1; + } + return barWidth; +} +function zoom() +{ + svg.select(".x.axis").call(xAxis); + svg.select(".y.axis").call(yAxis); + vis.selectAll(".bar").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); + vis.selectAll("path.line").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } + +svg.append("rect") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .attr("opacity", 0); 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 5f47588942..ed8461e566 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,699 +1,701 @@ /*=================================================================== 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 // berry includes #include // mitk includes #include "mitkNodePredicateDataType.h" #include "mitkPlanarFigureInteractor.h" // itk includes #include "itksys/SystemTools.hxx" #include #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; 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_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); this->CreateConnections(); m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_LineProfileWidget->SetPathModeToPlanarFigure(); } } 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->m_Controls->m_JSHistogram), SLOT(histogramToBarChart())); + connect( (QObject*) (this->m_Controls->m_lineRadioButton), SIGNAL(clicked()), (QObject*) (this->m_Controls->m_JSHistogram), SLOT(histogramToLineGraph())); + connect( (QObject*) (this->m_Controls->m_resetButton), SIGNAL(clicked()), (QObject*) (this->m_Controls->m_JSHistogram), SLOT(resetView())); } } void QmitkImageStatisticsView::JumpToCoordinates(int row ,int col) { mitk::Point3D world; if (row==4) world = m_WorldMin; else if (row==3) world = m_WorldMax; else return; mitk::IRenderWindowPart* part = this->GetRenderWindowPart(); if (part) { part->GetRenderWindow("axial")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetRenderWindow("sagittal")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetRenderWindow("coronal")->GetSliceNavigationController()->SelectSliceByPoint(world); } } void QmitkImageStatisticsView::OnIgnoreZerosCheckboxClicked() { emit StatisticsUpdate(); } void QmitkImageStatisticsView::OnClipboardHistogramButtonClicked() { if ( m_CurrentStatisticsValid ) { typedef mitk::ImageStatisticsCalculator::HistogramType HistogramType; const HistogramType *histogram = this->m_CalculationThread->GetTimeStepHistogram().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 ); } else { QApplication::clipboard()->clear(); } } void QmitkImageStatisticsView::OnClipboardStatisticsButtonClicked() { if ( this->m_CurrentStatisticsValid ) { const mitk::ImageStatisticsCalculator::Statistics &statistics = this->m_CalculationThread->GetStatisticsData(); // Copy statistics to clipboard ("%Ln" will use the default locale for // number formatting) QString clipboard( "Mean \t StdDev \t RMS \t Max \t Min \t N \t V (mm³)\n" ); clipboard = clipboard.append( "%L1 \t %L2 \t %L3 \t %L4 \t %L5 \t %L6 \t %L7" ) .arg( statistics.Mean, 0, 'f', 10 ) .arg( statistics.Sigma, 0, 'f', 10 ) .arg( statistics.RMS, 0, 'f', 10 ) .arg( statistics.Max, 0, 'f', 10 ) .arg( statistics.Min, 0, 'f', 10 ) .arg( statistics.N ) .arg( m_Controls->m_StatisticsTable->item( 0, 6 )->text() ); QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard ); } else { QApplication::clipboard()->clear(); } } 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; } this->ReinitData(); if(selectedNodes.size() == 1 || selectedNodes.size() == 2) { 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_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_HistogramWidget->ClearItemModel(); m_Controls->m_LineProfileWidget->ClearItemModel(); m_Controls->m_JSHistogram->clearHistogram(); } 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_WorldMin.Fill(-1); m_WorldMax.Fill(-1); // classify selected nodes mitk::NodePredicateDataType::Pointer imagePredicate = mitk::NodePredicateDataType::New("Image"); std::string maskName = std::string(); std::string maskType = 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); 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); } } } 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 (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); 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_HistogramWidget->ClearItemModel(); m_Controls->m_LineProfileWidget->ClearItemModel(); m_CurrentStatisticsValid = false; this->m_StatisticsUpdatePending = false; return; } std::stringstream maskLabel; maskLabel << maskName; if ( maskDimension > 0 ) { maskLabel << " [" << maskDimension << "D " << maskType << "]"; } m_Controls->m_SelectedMaskLabel->setText( maskLabel.str().c_str() ); // check time step validity if(m_SelectedImage->GetDimension() <= 3 && timeStep > m_SelectedImage->GetDimension(3)-1) { timeStep = m_SelectedImage->GetDimension(3)-1; } //// 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->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::WriteStatisticsToGUI() { 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; } m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_HistogramWidget->SetHistogramModeToDirectHistogram(); m_Controls->m_HistogramWidget->SetHistogram( this->m_CalculationThread->GetTimeStepHistogram().GetPointer() ); m_Controls->m_JSHistogram->ComputeHistogram( this->m_CalculationThread->GetTimeStepHistogram().GetPointer() ); m_Controls->m_HistogramWidget->UpdateItemModelFromHistogram(); //int timeStep = this->m_CalculationThread->GetTimeStep(); 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_HistogramWidget->ClearItemModel(); m_Controls->m_LineProfileWidget->ClearItemModel(); m_CurrentStatisticsValid = false; // If a (non-closed) PlanarFigure is selected, display a line profile widget if ( m_SelectedPlanarFigure != NULL ) { // check whether PlanarFigure is initialized const mitk::Geometry2D *planarFigureGeometry2D = m_SelectedPlanarFigure->GetGeometry2D(); if ( planarFigureGeometry2D == NULL ) { // Clear statistics, histogram, and GUI this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_HistogramWidget->ClearItemModel(); m_Controls->m_LineProfileWidget->ClearItemModel(); m_CurrentStatisticsValid = false; m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_SelectedMaskLabel->setText( "None" ); this->m_StatisticsUpdatePending = false; return; } // TODO: enable line profile widget m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 1 ); m_Controls->m_LineProfileWidget->SetImage( this->m_CalculationThread->GetStatisticsImage() ); m_Controls->m_LineProfileWidget->SetPlanarFigure( m_SelectedPlanarFigure ); m_Controls->m_LineProfileWidget->UpdateItemModelFromPath(); } } this->m_StatisticsUpdatePending = false; } void QmitkImageStatisticsView::ComputeIntensityProfile( mitk::PlanarLine* line ) { double sampling = 300; QmitkVtkHistogramWidget::HistogramType::Pointer histogram = QmitkVtkHistogramWidget::HistogramType::New(); itk::Size<1> siz; siz[0] = sampling; itk::FixedArray lower, higher; lower.Fill(0); mitk::Point3D begin = line->GetWorldControlPoint(0); mitk::Point3D end = line->GetWorldControlPoint(1); itk::Vector direction = (end - begin); higher.Fill(direction.GetNorm()); histogram->Initialize(siz, lower, higher); for(int i = 0; i < sampling; i++) { double d = m_SelectedImage->GetPixelValueByWorldCoordinate(begin + double(i)/sampling * direction); histogram->SetFrequency(i,d); } m_Controls->m_HistogramWidget->SetHistogramModeToDirectHistogram(); m_Controls->m_HistogramWidget->SetHistogram( histogram ); m_Controls->m_JSHistogram->ComputeHistogram( histogram ); m_Controls->m_HistogramWidget->UpdateItemModelFromHistogram(); } void QmitkImageStatisticsView::FillStatisticsTableView( const mitk::ImageStatisticsCalculator::Statistics &s, const mitk::Image *image ) { if (s.MaxIndex.size()==3) { mitk::Point3D index; index[0] = s.MaxIndex[0]; index[1] = s.MaxIndex[1]; index[2] = s.MaxIndex[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, m_WorldMax); index[0] = s.MinIndex[0]; index[1] = s.MinIndex[1]; index[2] = s.MinIndex[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, m_WorldMin); } int decimals = 2; mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) decimals = 5; this->m_Controls->m_StatisticsTable->setItem( 0, 0, new QTableWidgetItem( QString("%1").arg(s.Mean, 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 0, 1, new QTableWidgetItem( QString("%1").arg(s.Sigma, 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 0, 2, new QTableWidgetItem( QString("%1").arg(s.RMS, 0, 'f', decimals) ) ); QString max; max.append(QString("%1").arg(s.Max, 0, 'f', decimals)); max += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 0, 3, new QTableWidgetItem( max ) ); QString min; min.append(QString("%1").arg(s.Min, 0, 'f', decimals)); min += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 0, 4, new QTableWidgetItem( min ) ); this->m_Controls->m_StatisticsTable->setItem( 0, 5, new QTableWidgetItem( QString("%1").arg(s.N) ) ); const mitk::Geometry3D *geometry = image->GetGeometry(); if ( geometry != NULL ) { const mitk::Vector3D &spacing = image->GetGeometry()->GetSpacing(); double volume = spacing[0] * spacing[1] * spacing[2] * (double) s.N; this->m_Controls->m_StatisticsTable->setItem( 0, 6, new QTableWidgetItem( QString("%1").arg(volume, 0, 'f', decimals) ) ); } else { this->m_Controls->m_StatisticsTable->setItem( 0, 6, new QTableWidgetItem( "NA" ) ); } } void QmitkImageStatisticsView::InvalidateStatisticsTableView() { for ( unsigned int i = 0; i < 7; ++i ) { this->m_Controls->m_StatisticsTable->setItem( 0, i, new QTableWidgetItem( "NA" ) ); } } void QmitkImageStatisticsView::Activated() { } void QmitkImageStatisticsView::Deactivated() { } void QmitkImageStatisticsView::Visible() { m_Visible = true; if (m_DataNodeSelectionChanged) { if (this->IsCurrentSelectionValid()) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->SelectionChanged(this->GetDataManagerSelection()); } m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::Hidden() { m_Visible = false; } void QmitkImageStatisticsView::SetFocus() { } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui index fc80cad62e..cd0d5b7fa9 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui @@ -1,365 +1,436 @@ QmitkImageStatisticsViewControls true 0 0 465 800 Form - + 0 0 Mask: None 2 Qt::Horizontal 40 20 color: rgb(255, 0, 0); Error Message Qt::AutoText Ignore zero-valued voxels - - - - - 150 - 160 - - - - Histogram - - - false - - - - - - - 0 - - - 0 - - - - - Copy to Clipboard - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 1 - - - - - 0 - 0 - - - - - - - 0 - 0 - - - groupBox_3 - m_StatisticsTable - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - Statistics 9 9 9 0 0 100 175 16777215 170 Qt::ScrollBarAlwaysOff Qt::ScrollBarAsNeeded true true true Qt::DotLine true false false 80 true 80 false true true false 25 25 false false Mean StdDev RMS Max Min N V (mm³) Component 1 0 0 Copy to Clipboard Qt::Horizontal 40 20 + + + + + 150 + 160 + + + + Histogram + + + false + + + + + + + 0 + 0 + + + + + 0 + 35 + + + + Plot + + + + + 150 + 10 + 85 + 17 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Use linegraph + + + + + + 10 + 10 + 82 + 17 + + + + Use barchart + + + true + + + + + + 280 + 10 + 75 + 23 + + + + Reset view + + + + + + + + 0 + + + + + 0 + 0 + + + + + + + 0 + 0 + + + + + + + + + + + 0 + + + 0 + + + + + Copy to Clipboard + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + QmitkVtkHistogramWidget QWidget
QmitkVtkHistogramWidget.h
1
QmitkVtkLineProfileWidget QWidget
QmitkVtkLineProfileWidget.h
1
QmitkHistogramJSWidget QWidget
QmitkHistogramJSWidget.h
1