diff --git a/Modules/QmitkExt/QmitkHistogramJSWidget.cpp b/Modules/QmitkExt/QmitkHistogramJSWidget.cpp index b9c93027b2..e2620857a7 100644 --- a/Modules/QmitkExt/QmitkHistogramJSWidget.cpp +++ b/Modules/QmitkExt/QmitkHistogramJSWidget.cpp @@ -1,233 +1,239 @@ /*=================================================================== 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 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) { // 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); } } - + m_IntensityProfile = false; 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 +// slots for radiobuttons void QmitkHistogramJSWidget::histogramToBarChart() { m_UseLineGraph = false; - this->DataChanged(); + this->GraphChanged(); } void QmitkHistogramJSWidget::histogramToLineGraph() { m_UseLineGraph = true; - this->DataChanged(); + this->GraphChanged(); } 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_IntensityProfile = true; m_UseLineGraph = true; this->DataChanged(); } + +bool QmitkHistogramJSWidget::getIntensityProfile() +{ + return m_IntensityProfile; +} diff --git a/Modules/QmitkExt/QmitkHistogramJSWidget.h b/Modules/QmitkExt/QmitkHistogramJSWidget.h index 05e8e33159..0f1c286a13 100644 --- a/Modules/QmitkExt/QmitkHistogramJSWidget.h +++ b/Modules/QmitkExt/QmitkHistogramJSWidget.h @@ -1,86 +1,91 @@ /*=================================================================== 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) + Q_PROPERTY(bool intensityProfile + READ getIntensityProfile) 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(); + bool getIntensityProfile(); 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; + bool m_IntensityProfile; void clearData(); private slots: void addJSObject(); public slots: void histogramToBarChart(); void histogramToLineGraph(); void resetView(); signals: void DataChanged(); + void GraphChanged(); }; #endif // QMITKHISTOGRAMJSWIDGET_H diff --git a/Modules/QmitkExt/resources/HighChartsHistogram.js b/Modules/QmitkExt/resources/HighChartsHistogram.js index afff8c2361..8b23f75969 100644 --- a/Modules/QmitkExt/resources/HighChartsHistogram.js +++ b/Modules/QmitkExt/resources/HighChartsHistogram.js @@ -1,117 +1,150 @@ /*=================================================================== 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 connected = false; var chart; var histData; if (!connected) { connected = true; histogramData.DataChanged.connect(updateHistogram); + histogramData.GraphChanged.connect(toggleGraph); } $(function () { chart = new Highcharts.Chart({ chart: { renderTo: 'container', resetZoomButton: { position: { x:-10, y:10 }, relativeTo: 'chart' }, animation: { duration: 1500, }, zoomType: 'x' }, title: { - text: 'Histogram' + text: null }, plotOptions: { column: { borderWidth: 0.0, shadow: false, turboThreshold: 500.0, enableMouseTracking: false }, - spline: { + line: { shadow: false, lineWidth: 1.0, marker: { enabled: false }, + stickyTracking: false, visible: false } }, xAxis: { title: { - text: 'Greyvalue' + text: null }, allowDecimals: false, endOnTick: true }, yAxis: { title: { - text: 'Frequency (px)' + text: null }, allowDecimals: false, endOnTick: true }, tooltip: { shadow: false, formatter: function() { - return 'Greyvalue: '+this.x+'
'+'Frequency: '+this.y; + if (!histogramData.intensityProfile) + { + return 'Greyvalue: '+this.x+'
'+'Frequency: '+this.y; + } + else + { + return 'Distance: '+Math.round(this.x*100)/100+' mm
'+'Intensity: '+this.y; + } } }, series: [{ type: 'column', data: histData, name: 'Barchart' }, { - type: 'spline', + type: 'line', data: histData, name: 'Lineplot' }], credits: { enabled: false }, navigation: { buttonOptions: { enabled: false } }, legend: { - enabled: true + enabled: false } }); }); updateHistogram(); function updateHistogram() { + if (histogramData.useLineGraph) + { + chart.series[0].hide(); + chart.series[1].show(); + } + else + { + chart.series[0].show(); + chart.series[1].hide(); + } histData = []; for (var i = 0; i < histogramData.frequency.length; i++) { var tempArray = [histogramData.measurement[i], histogramData.frequency[i]] histData[i] = tempArray; } chart.series[0].setData(histData); chart.series[1].setData(histData); chart.redraw(); } + +function toggleGraph() +{ + if (histogramData.useLineGraph) + { + chart.series[0].hide(); + chart.series[1].show(); + } + else + { + chart.series[0].show(); + chart.series[1].hide(); + } +} diff --git a/Modules/QmitkExt/resources/Histogram.js b/Modules/QmitkExt/resources/Histogram.js index 02df44079f..949711ee62 100644 --- a/Modules/QmitkExt/resources/Histogram.js +++ b/Modules/QmitkExt/resources/Histogram.js @@ -1,345 +1,356 @@ /*=================================================================== 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); + histogramData.GraphChanged.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([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) + .style("stroke", getRandomColor) .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([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([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"); } + +function getRandomColor() { + var letters = '0123456789ABCDEF'.split(''); + var color = '#'; + for (var i = 0; i < 6; i++ ) { + color += letters[Math.round(Math.random() * 15)]; + } + return color; +}