diff --git a/Modules/QmitkExt/QmitkHistogramJSWidget.cpp b/Modules/QmitkExt/QmitkHistogramJSWidget.cpp index 7c2d1e7769..b9c93027b2 100644 --- a/Modules/QmitkExt/QmitkHistogramJSWidget.cpp +++ b/Modules/QmitkExt/QmitkHistogramJSWidget.cpp @@ -1,129 +1,233 @@ /*=================================================================== 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())); 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); + + m_ParametricPath = ParametricPathType::New(); } 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 +// reloads WebView, everytime his 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; clearData(); unsigned int i = 0; bool firstValue = false; for (it = m_Histogram->Begin() ; it != m_Histogram->End(); ++it, ++i) { - if (i<1) MITK_INFO << "Measurement: " << it.GetMeasurementVector()[0] << " Frequency: " << it.GetFrequency(); // filter frequencies of 0 to guarantee a better view while using a mask if (it.GetFrequency() != 0 && !firstValue) { firstValue = true; i=0; } if (firstValue) { 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; } bool QmitkHistogramJSWidget::getUseLineGraph() { return m_UseLineGraph; } // slots for radio- and pushbuttons void QmitkHistogramJSWidget::histogramToBarChart() { m_UseLineGraph = false; this->DataChanged(); } void QmitkHistogramJSWidget::histogramToLineGraph() { m_UseLineGraph = true; this->DataChanged(); } void QmitkHistogramJSWidget::resetView() { this->reload(); } + +void QmitkHistogramJSWidget::setImage(mitk::Image* image) +{ + m_Image = image; +} + +void QmitkHistogramJSWidget::setPlanarFigure(const mitk::PlanarFigure* planarFigure) +{ + m_PlanarFigure = planarFigure; +} + +void QmitkHistogramJSWidget::ComputeHistogramOfPlanarFigure() +{ + this->clearData(); + m_ParametricPath->Initialize(); + + if (m_PlanarFigure.IsNull()) + { + throw std::invalid_argument("PlanarFigure not set!"); + } + + if (m_Image.IsNull()) + { + throw std::invalid_argument("Image not set!"); + } + + mitk::Geometry2D* planarFigureGeometry2D = dynamic_cast(m_PlanarFigure->GetGeometry(0)); + if (planarFigureGeometry2D == NULL) + { + throw std::invalid_argument("PlanarFigure has no valid geometry!"); + } + + mitk::Geometry3D* imageGeometry = m_Image->GetGeometry(0); + if (imageGeometry == NULL) + { + throw std::invalid_argument("Image has no valid geometry!"); + } + + typedef mitk::PlanarFigure::PolyLineType VertexContainerType; + const VertexContainerType vertexContainer = m_PlanarFigure->GetPolyLine(0); + + VertexContainerType::const_iterator it; + for (it = vertexContainer.begin(); it != vertexContainer.end(); ++it) + { + mitk::Point3D point3D; + planarFigureGeometry2D->Map(it->Point, point3D); + + mitk::Point3D indexPoint3D; + imageGeometry->WorldToIndex(point3D, indexPoint3D); + + ParametricPathType::OutputType index; + index[0] = indexPoint3D[0]; + index[1] = indexPoint3D[1]; + index[2] = indexPoint3D[2]; + + m_ParametricPath->AddVertex(index); + } + + m_DerivedPath = m_ParametricPath; + + if (m_DerivedPath.IsNull()) + { + throw std::invalid_argument("No path set!"); + } + + double distance = 0.0; + mitk::Point3D currentWorldPoint; + + double t; + unsigned int i = 0; + int k = 0; + for (i = 0, t = m_DerivedPath->StartOfInput(); ;++i) + { + const PathType::OutputType &continousIndex = m_DerivedPath->Evaluate(t); + + mitk::Point3D worldPoint; + imageGeometry->IndexToWorld(continousIndex, worldPoint); + + if (i == 0) + { + currentWorldPoint = worldPoint; + } + + distance += currentWorldPoint.EuclideanDistanceTo(worldPoint); + mitk::Index3D indexPoint; + imageGeometry->WorldToIndex(worldPoint, indexPoint); + double intensity = m_Image->GetPixelValueByIndex(indexPoint); + + m_Measurement.insert(i, distance); + m_Frequency.insert(i, intensity); + + PathType::OffsetType offset = m_DerivedPath->IncrementInput(t); + if (!(offset[0] || offset[1] || offset[2])) + { + break; + } + + currentWorldPoint = worldPoint; + } + + m_UseLineGraph = true; + this->DataChanged(); +} diff --git a/Modules/QmitkExt/QmitkHistogramJSWidget.h b/Modules/QmitkExt/QmitkHistogramJSWidget.h index 73c16b0bab..05e8e33159 100644 --- a/Modules/QmitkExt/QmitkHistogramJSWidget.h +++ b/Modules/QmitkExt/QmitkHistogramJSWidget.h @@ -1,75 +1,86 @@ /*=================================================================== 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" +#include "mitkPlanarFigure.h" +#include 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; + typedef itk::PolyLineParametricPath< 3 > ParametricPathType; + typedef itk::ParametricPath< 3 >::Superclass PathType; explicit QmitkHistogramJSWidget(QWidget *parent = 0); ~QmitkHistogramJSWidget(); void resizeEvent(QResizeEvent* resizeEvent); void ComputeHistogram(HistogramType* histogram); void clearHistogram(); QList getMeasurement(); QList getFrequency(); bool getUseLineGraph(); + void setImage(mitk::Image* image); + void setPlanarFigure(const mitk::PlanarFigure* planarFigure); + void ComputeHistogramOfPlanarFigure(); private: QList m_Frequency; QList m_Measurement; + mitk::Image::Pointer m_Image; + mitk::PlanarFigure::ConstPointer m_PlanarFigure; bool m_UseLineGraph; HistogramType::ConstPointer m_Histogram; + PathType::ConstPointer m_DerivedPath; + ParametricPathType::Pointer m_ParametricPath; void clearData(); private slots: void addJSObject(); public slots: void histogramToBarChart(); void histogramToLineGraph(); void resetView(); signals: void DataChanged(); }; #endif // QMITKHISTOGRAMJSWIDGET_H diff --git a/Modules/QmitkExt/resources/Histogram.js b/Modules/QmitkExt/resources/Histogram.js index fdef47a39c..02df44079f 100644 --- a/Modules/QmitkExt/resources/Histogram.js +++ b/Modules/QmitkExt/resources/Histogram.js @@ -1,345 +1,345 @@ /*=================================================================== 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. ===================================================================*/ var margin = { top : 10, bottom : 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 binSize = 0; // connecting signal from qt side with JavaScript method if (!connected) { connected = true; histogramData.DataChanged.connect(updateHistogram); } var xScale = d3.scale.linear() .domain([d3.min(histogramData.measurement),d3.max(histogramData.measurement)]) .range([0,width]); var yScale = d3.scale.linear() - .domain([0,d3.max(histogramData.frequency)]) + .domain([d3.min(histogramData.frequency),d3.max(histogramData.frequency)]) .range([height,margin.top]); var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom") .tickFormat(d3.format("s")); var yAxis = d3.svg.axis() .scale(yScale) .orient("left") .tickFormat(d3.format("s")); var zoombie = d3.behavior.zoom().x(xScale).scaleExtent([1, 50]).on("zoom", zoom); var svg = d3.select("body") .append("svg") .attr("class", "svg") .attr("width", width + margin.right + margin.left) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate (" + margin.left + "," + margin.top + ")") .call(zoombie) .on("mousemove", myMouseMove); svg.append("rect") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .attr("opacity", 0); var vis = svg.append("svg") .attr("width", width) .attr("height", height); updateHistogram(); // method to update and choose histogram function updateHistogram() { calcBinSize(); if (!histogramData.useLineGraph) { barChart(); } else if (histogramData.useLineGraph) { linePlot() } } function calcBinSize() { var min = d3.min(histogramData.measurement); var max = d3.max(histogramData.measurement); binSize = Math.round((max - min) / (histogramData.measurement.length)); } // method to display histogram as barchart function barChart() { definition(); zoombie = d3.behavior.zoom().x(xScale).scaleExtent([1, 50]).on("zoom", zoom); svg.call(zoombie); var linenull = d3.svg.line() .interpolate("linear") .x(function(d,i) { return xScale(histogramData.measurement[i]); }) .y(function(d) { return yScale(0); }); // element to animate transition from linegraph to barchart vis.selectAll("path.line").transition().duration(dur).attr("d", linenull(histogramData.frequency)).remove(); var bar = vis.selectAll("rect.bar").data(histogramData.frequency); bar.enter().append("rect") .attr("class", "bar") .on("mouseover", myMouseOver) .on("mouseout", myMouseOut) .attr("x", function(d,i) { return xScale(histogramData.measurement[i]-binSize/2); }) .attr("y", height) .attr("height", 0) .attr("width", barWidth) .transition().delay(dur).duration(dur*1.5) .attr("height", barHeight) .attr("width", barWidth) .attr("y", myYPostion); bar.transition().delay(dur).duration(dur*1.5) .attr("x", function(d,i) { return xScale(histogramData.measurement[i]-binSize/2); }) .attr("y", myYPostion) .attr("height", barHeight) .attr("width", barWidth); 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", "x axis") .attr("transform", "translate(0," + height + ")") .transition().duration(dur) .attr("opacity", 100) .call(xAxis); svg.append("g") .attr("class", "y axis") .transition().duration(dur) .attr("opacity", 100) .call(yAxis); } // method to display histogram as linegraph function linePlot() { definition(); zoombie = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 50]).on("zoom", zoom); svg.call(zoombie); // 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("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("cardinal") .x(function(d,i) { return xScale(histogramData.measurement[i]); }) .y(function(d) { return yScale(0); }) .tension(0.8); 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", "x axis") .attr("transform", "translate(0," + height + ")") .transition().duration(dur) .attr("opacity", 100) .call(xAxis); svg.append("g") .attr("class", "y axis") .transition().duration(dur) .attr("opacity", 100) .call(yAxis); } function definition() { // match scale to current data xScale = d3.scale.linear() .domain([d3.min(histogramData.measurement)-binSize/2,d3.max(histogramData.measurement)+binSize/2]) .range([0,width]); yScale = d3.scale.linear() - .domain([0,d3.max(histogramData.frequency)]) + .domain([d3.min(histogramData.frequency),d3.max(histogramData.frequency)]) .range([height,margin.top]); xAxis = d3.svg.axis() .scale(xScale) .orient("bottom") .tickFormat(d3.format("s")); yAxis = d3.svg.axis() .scale(yScale) .orient("left") .tickFormat(d3.format("s")); } // method to ensure barwidth is not smaller than 1px function barWidth(d, i) { var bw; bw =(xScale(histogramData.measurement[i + 1]) - xScale(histogramData.measurement[i])) * (histogramData.frequency.length / (histogramData.frequency.length + 1)) - 1; bw = bw > 1 ? bw : 1; return bw; } function barHeight(d) { var bh; bh = height - yScale(d); bh = bh >=2 ? bh : 2; return bh; } function myYPostion(d) { var myy = yScale(d); myy = (height-myy) > 2 ? myy : (height-2); if (d == 0) { return height; } return myy; } // zoom function, with plot focus by scale 1 and different zooming mode function zoom() { if (zoombie.scale() == 1) { zoombie.translate([0,0]); xScale.domain([d3.min(histogramData.measurement),d3.max(histogramData.measurement)]); - yScale.domain([0,d3.max(histogramData.frequency)]); + yScale.domain([d3.min(histogramData.frequency),d3.max(histogramData.frequency)]); } if (!histogramData.useLineGraph) { svg.select(".x.axis").call(xAxis); vis.selectAll(".bar") .attr("width", barWidth) .attr("x", function(d, i) { return xScale(histogramData.measurement[i]-binSize/2)}); } else { svg.select(".x.axis").call(xAxis); svg.select(".y.axis").call(yAxis); vis.selectAll("path.line").attr("transform", "translate(" + zoombie.translate() + ")scale(" + zoombie.scale() + ")").style("stroke-width", 1 / zoombie.scale()); } } // method to show infobox, while mouse is over a bin function myMouseOver() { var myBar = d3.select(this); var reScale = d3.scale.linear() .domain(xScale.range()) .range(xScale.domain()); var y = myBar.data(); var x = reScale(myBar.attr("x")); myBar.style("fill", "red"); d3.select(".infobox").style("display", "block"); d3.select(".measurement").text("Greyvalue: " + (Math.round(x)) + " ... " + (Math.round(x+binSize))); //d3.select(".measurement").text("Greyvalue: " + (Math.round(x*100))/100 + " ... " + (Math.round((x+binSize)*100))/100); d3.select(".frequency").text("Frequency: " + y); } // hide infobox, when mouse not over a bin function myMouseOut() { var myBar = d3.select(this); myBar.style("fill", "steelblue"); d3.select(".infobox").style("display", "none"); } // update mousecoordinates by mousemove function myMouseMove() { var infobox = d3.select(".infobox"); var coords = d3.mouse(this); infobox.style("left", coords[0] + 75 + "px"); infobox.style("top", coords[1] + "px"); } 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 9ab04d6b9b..1b1cac5d61 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,704 +1,695 @@ /*=================================================================== 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())); } } 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_Controls->m_JSHistogram->clearHistogram(); 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() { + m_Controls->m_lineRadioButton->setEnabled(true); + m_Controls->m_barRadioButton->setEnabled(true); + if (m_Controls->m_barRadioButton->isChecked()) + { + m_Controls->m_JSHistogram->histogramToBarChart(); + } 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_Controls->m_JSHistogram->clearHistogram(); 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_Controls->m_JSHistogram->clearHistogram(); 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(); - //m_Controls->m_JSHistogram + m_Controls->m_JSHistogram->setImage(this->m_CalculationThread->GetStatisticsImage()); + m_Controls->m_JSHistogram->setPlanarFigure(m_SelectedPlanarFigure); + m_Controls->m_JSHistogram->ComputeHistogramOfPlanarFigure(); + m_Controls->m_lineRadioButton->setEnabled(false); + m_Controls->m_barRadioButton->setEnabled(false); } } 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 791b17fd4b..a0b59ffecf 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui @@ -1,429 +1,470 @@ 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 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 - - - - - 230 - 10 - 85 - 17 - - - - - 0 - 0 - - - - - 0 - 0 - - - - Use linegraph - - - - - - 10 - 10 - 82 - 17 - - - - - 0 - 0 - - - - Use barchart - - - true - - - - 0 0 0 0 0 0 0 Copy to Clipboard Qt::Horizontal 40 20 + + + + + 0 + 0 + + + + + 0 + 50 + + + + + 16777215 + 16777215 + + + + Plot + + + + + 0 + 20 + 411 + 31 + + + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 10 + 0 + + + + + + + + + 0 + 0 + + + + + + + Use barchart + + + true + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Use linegraph + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + - + Qt::Vertical 20 40 QmitkVtkHistogramWidget QWidget
QmitkVtkHistogramWidget.h
1
QmitkVtkLineProfileWidget QWidget
QmitkVtkLineProfileWidget.h
1
QmitkHistogramJSWidget QWidget
QmitkHistogramJSWidget.h
1