diff --git a/Modules/C3js/CMakeLists.txt b/Modules/C3js/CMakeLists.txt new file mode 100644 index 0000000000..a1be2d6383 --- /dev/null +++ b/Modules/C3js/CMakeLists.txt @@ -0,0 +1,4 @@ +MITK_CREATE_MODULE( + DEPENDS MitkCore MitkPlanarFigure MitkImageStatistics + PACKAGE_DEPENDS PRIVATE Qt5|WebEngineWidgets +) diff --git a/Modules/C3js/files.cmake b/Modules/C3js/files.cmake new file mode 100644 index 0000000000..7acf92a459 --- /dev/null +++ b/Modules/C3js/files.cmake @@ -0,0 +1,13 @@ +set(CPP_FILES + QmitkC3jsWidget.cpp + QmitkC3Data.cpp +) + +set(MOC_H_FILES + include/QmitkC3jsWidget.h + include/QmitkC3Data.h +) + +set(QRC_FILES + resource/C3js.qrc +) diff --git a/Modules/C3js/include/C3Data.h b/Modules/C3js/include/C3Data.h new file mode 100644 index 0000000000..63325ae437 --- /dev/null +++ b/Modules/C3js/include/C3Data.h @@ -0,0 +1,87 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef QmitkC3Data_h +#define QmitkC3Data_h + +#include + +/** /brief This class holds the relevant data for the chart generation with C3. +* Documentation: This class holds the relevant data for the chart generation with C3. It forwards the frequencies and the measurements to JavaScript. +* Those are then displayed in the histogram chart. +* It is derived from QObject, because we need Q_PROPERTIES to send Data via QWebChannel to JavaScript. +*/ +class QmitkC3Data : public QObject +{ + Q_OBJECT + + typedef mitk::Image::HistogramType HistogramType; + Q_PROPERTY(QList m_Frequency READ GetFrequency WRITE SetFrequency NOTIFY OnFrequencyChaned); + Q_PROPERTY(QList m_Measurement READ GetMeasurement WRITE SetMeasurement NOTIFY OnMeasurementChaned); + +public: + Q_INVOKABLE QList GetFrequency() { return m_Frequency; }; + Q_INVOKABLE void SetFrequency(QList frequency) { m_Frequency = frequency; }; + + Q_INVOKABLE QList GetMeasurement() { return m_Measurement; }; + Q_INVOKABLE void SetMeasurement(QList measurement) { m_Measurement = measurement; }; + + /** + * \brief Clears the Data. + * + * This function clears the data. + */ + void ClearData(); + + HistogramType::ConstPointer GetHistogram() { return m_Histogram; }; + void SetHistogram(HistogramType::ConstPointer histogram) { m_Histogram = histogram; }; + + //QList GetFrequency() { return m_Frequency; }; + QList* GetFrequencyPointer() { return &m_Frequency; }; + //void SetFrequency(QList frequency) { m_Frequency = frequency; }; + + //QList GetMeasurement() { return m_Frequency; }; + QList* GetMeasurementPointer() { return &m_Measurement; }; + //void SetMeasurement(QList measurement) { m_Measurement = measurement; }; + +signals: + void OnFrequencyChaned(const QList frequency); + void OnMeasurementChaned(const QList measurement); + +private: + /** + * Holds the current histogram + */ + HistogramType::ConstPointer m_Histogram; + + /** + * \brief List of frequencies. + * + * A QList which holds the frequencies of the current histogram + * or holds the intensities of current intensity profile. + */ + QList m_Frequency; + + /** + * \brief List of measurements. + * + * A QList which holds the measurements of the current histogram + * or holds the distances of current intensity profile. + */ + QList m_Measurement; +}; + +#endif //QmitkC3Data_h diff --git a/Modules/C3js/include/QmitkC3Data.h b/Modules/C3js/include/QmitkC3Data.h new file mode 100644 index 0000000000..7fc2d8ed8d --- /dev/null +++ b/Modules/C3js/include/QmitkC3Data.h @@ -0,0 +1,99 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef QmitkC3Data_h +#define QmitkC3Data_h + +#include + +/** /brief This class holds the relevant data for the chart generation with C3. +* Documentation: This class holds the relevant data for the chart generation with C3. It forwards the frequencies and the measurements to JavaScript. +* Those are then displayed in the histogram chart. +* It is derived from QObject, because we need Q_PROPERTIES to send Data via QWebChannel to JavaScript. +*/ +class QmitkC3Data : public QObject +{ + Q_OBJECT + + typedef mitk::Image::HistogramType HistogramType; + Q_PROPERTY(QList m_YData READ GetYData WRITE SetYData NOTIFY SignalYDataChanged); + Q_PROPERTY(QList m_XData READ GetXData WRITE SetXData NOTIFY SignalXDataChanged); + Q_PROPERTY(QVariant m_UseLineChart READ GetUseLineChart WRITE SetUseLineChart NOTIFY SignalUseLineChartChanged); + Q_PROPERTY(QVariant m_ShowSubchart READ GetShowSubchart WRITE SetShowSubchart NOTIFY SignalShowSubchartChanged); + +public: + QmitkC3Data(); + QmitkC3Data(HistogramType::ConstPointer histogram); + + void SetAppearance(bool useLineChart, bool showSubChart); + Q_INVOKABLE QList GetYData() { return m_YData; }; + Q_INVOKABLE void SetYData(QList yData) { m_YData = yData; }; + + Q_INVOKABLE QList GetXData() { return m_XData; }; + Q_INVOKABLE void SetXData(QList xData) { m_XData = xData; }; + + Q_INVOKABLE QVariant GetUseLineChart() { return m_UseLineChart; }; + Q_INVOKABLE void SetUseLineChart(QVariant useLineChart) { m_UseLineChart = useLineChart; }; + + Q_INVOKABLE QVariant GetShowSubchart() { return m_ShowSubchart; }; + Q_INVOKABLE void SetShowSubchart(QVariant showSubchart) { m_ShowSubchart = showSubchart; }; + /** + * \brief Clears the Data. + * + * This function clears the data. + */ + void ClearData(); + + HistogramType::ConstPointer GetHistogram() { return m_Histogram; }; + void SetHistogram(HistogramType::ConstPointer histogram) { m_Histogram = histogram; }; + + QList* GetYDataPointer() { return &m_YData; }; + + QList* GetXDataPointer() { return &m_XData; }; + +signals: + void SignalYDataChanged(const QList yData); + void SignalXDataChanged(const QList xData); + void SignalUseLineChartChanged(const QVariant useLineChart); + void SignalShowSubchartChanged(const QVariant showSubchart); + +private: + /** + * Holds the current histogram + */ + HistogramType::ConstPointer m_Histogram; + + /** + * \brief List of frequencies. + * + * A QList which holds the frequencies of the current histogram + * or holds the intensities of current intensity profile. + */ + QList m_YData; + + /** + * \brief List of measurements. + * + * A QList which holds the measurements of the current histogram + * or holds the distances of current intensity profile. + */ + QList m_XData; + + QVariant m_UseLineChart; + QVariant m_ShowSubchart; +}; + +#endif //QmitkC3Data_h \ No newline at end of file diff --git a/Modules/C3js/include/QmitkC3jsWidget.h b/Modules/C3js/include/QmitkC3jsWidget.h new file mode 100644 index 0000000000..641d41eb92 --- /dev/null +++ b/Modules/C3js/include/QmitkC3jsWidget.h @@ -0,0 +1,84 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ +#ifndef QmitkD3jsWidget_h +#define QmitkD3jsWidget_h + +#include +#include + +#include "mitkImage.h" +#include "mitkPlanarFigure.h" +#include +#include + +class MITKC3JS_EXPORT QmitkC3jsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit QmitkC3jsWidget(QWidget* parent = nullptr); + QmitkC3jsWidget(const QString& id, QObject* object, QWidget* parent = nullptr); + virtual ~QmitkC3jsWidget(); + + typedef mitk::Image::HistogramType HistogramType; + typedef mitk::Image::HistogramType::ConstIterator HistogramConstIteratorType; + + /** + * \brief Calculates the histogram. + * + * This function removes all frequencies of 0 until the first bin and behind the last bin. + * It writes the measurement and frequency, which are given from the HistogramType, into + * m_Measurement and m_Frequency. + * initializeJavaScriptChart is then called, to update the information, which is displayed in the webframe. + */ + void ComputeHistogram(HistogramType* histogram, bool useLineChart, bool showSubChart); + + void ComputeIntensityProfile(unsigned int timeStep, bool computeStatistics); + + void ClearHistogram(); + + mitk::Image::Pointer GetImage() const; + void SetImage(const mitk::Image::Pointer image); + + mitk::PlanarFigure::ConstPointer GetPlanarFigure() const; + void SetPlanarFigure(const mitk::PlanarFigure::ConstPointer planarFigure); + + void SetAppearance(bool useLineChart, bool showSubChart); + + void SendCommand(QString command); + + void TransformView(QString transformTo); + + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer GetStatistics() + { + return m_Statistics; + }; + + +private: + class Impl; + Impl* m_Impl; + + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer m_Statistics; + +public slots: + void OnLoadFinished(bool isLoadSuccessfull); + +signals: + void PageSuccessfullyLoaded(); +}; + +#endif diff --git a/Modules/C3js/resource/C3js.qrc b/Modules/C3js/resource/C3js.qrc new file mode 100644 index 0000000000..ef0618eb41 --- /dev/null +++ b/Modules/C3js/resource/C3js.qrc @@ -0,0 +1,13 @@ + + + + c3.min.css + Histogram.css + Histogram_dark.css + c3.min.js + d3.min.js + empty.html + QmitkC3jsWidget.html + Histogram.js + + diff --git a/Modules/C3js/resource/Histogram.css b/Modules/C3js/resource/Histogram.css new file mode 100644 index 0000000000..9cd7975c64 --- /dev/null +++ b/Modules/C3js/resource/Histogram.css @@ -0,0 +1,29 @@ +/*--body { + background-color: darkgrey !important; +}--*/ + +/*-- Bar --*/ +.c3-bar { + fill-opacity: 1; + } + +.c3-bar._expanded_ { + fill: red !important; /*-- The !important tag prevnts the color from being overwritten at rendering--*/ + fill-opacity: 1; +} + +/*-- Line --*/ +.c3-line { + stroke-width: 1px; + /*stroke-dasharray: 5,5; /*to make it dashed*/ +} + +/*-- Point --*/ +.c3-circle._expanded_ { + stroke-width: 1px; + stroke: red !important; + fill: red !important; /*-- The !important tag prevnts the color from being overwritten at rendering--*/} + +/*path.domain { stroke: white; } +.tick text { stroke: white; } +.c3-legend-item text { stroke: grey; }*/ \ No newline at end of file diff --git a/Modules/C3js/resource/Histogram.js b/Modules/C3js/resource/Histogram.js new file mode 100644 index 0000000000..c34e9730c8 --- /dev/null +++ b/Modules/C3js/resource/Histogram.js @@ -0,0 +1,343 @@ +document.body.style.backgroundColor = 'rgb(240, 240, 240)'; + +var greyvalue; //needed to display the tooltip in the right format +var binSize = 10; +var min; +var max; + +var minHeight = 255; + +var chart = c3.generate({ + data: { + x : 'x', //use first "column" as x axis values + columns: [], //initialize empty. Data will be loaded in function setupChart(initValues) + type: 'bar', + selection: { + enabled: false, + multiple: false, + } + }, + legend: { + position: 'inset' + }, + grid: { + y: { + lines: [{value:0}] //Draws a horizontal line at y=0 + } + }, + bar: { + width: { + ratio: 0.95 // this makes bar width 95% of length between ticks + } + }, + zoom: { + enabled: true, + }, + subchart: { + show: true //Shows a subchart that shows the region the primary chart is zoomed in to by overlay. + }, + axis: { + x: { + type: 'category', //only for type 'category' the bars will be rescaled in width on zoom + tick: { + multiline: false, + fit: false, //to make more x labels appear on zoom + centered: true, + }, + }, + y: { + tick: { + format: d3.format("s"), + }, + //for some reason, there is an offset for our linegraph. This is prevented by the following lines + min: 0, + padding: { top: 0, bottom: 0 } + } + }, + //Style data points in linegraph + point: { + r: 0.2, + focus: + { + expand: { + r: 4 + } + } + }, + tooltip: { + format: { + title: function (d) { + var endValue = (parseFloat(greyvalue[d]) + binSize); + endValue = endValue.toFixed(3); + return 'Greyvalue: ' + greyvalue[d] + '...' + endValue; + }, + } + } +}); + +var initValues; + +window.onload = function() +{ + new QWebChannel(qt.webChannelTransport, function(channel) { + initValues = channel.objects.initValues; + + setupChart(initValues) + }); +} + +//This is necessary to resize the chart, after the size of the parent changed +window.onresize = function () { + var size = window.innerHeight-(window.innerHeight/100*10); //subtract 5% of height to hide vertical scrool bar + + if (size < minHeight) + { + size = minHeight; + } + + chart.resize({ + height: size, + }); +} + +function ReloadChart(useLineChart, showSubchart) +{ + initValues.m_UseLineChart = useLineChart; + initValues.m_ShowSubchart = showSubchart; + + var chartType = 'bar'; + if (initValues.m_UseLineChart) { + chartType = 'line'; + } + + if (initValues.m_ShowSubchart) { + ShowSubchart(chartType) + } + else { + HideSubchart(chartType) + } + + setupChart(initValues); +} + +function setupChart(initValues) +{ + window.onresize(); + + calcBinSize(initValues); + + //copy measurements to xValues for x-axis-labels and to greyvalues for tooltips + var xValues = initValues.m_XData.slice(0); + greyvalue = initValues.m_XData.slice(0); + + for (var i = 0; i < xValues.length; i++) + { + greyvalue[i] = greyvalue[i] - (binSize / 2); + greyvalue[i] = greyvalue[i].toFixed(3); + //change number format for x axis. Need to do it here, because it is not working on chart generation. + xValues[i] = xValues[i]; + xValues[i] = xValues[i].toFixed(); + xValues[i] = d3.format("s")(xValues[i]); + } + + xValues.unshift('x'); //add label to x array + xValues.push(null); //append null value, to make sure the last tick on x-axis is displayed correctly + var yValues = initValues.m_YData; + yValues.unshift('Frequency'); //add label to y array + xValues.push(null); //append null value, to make sure the last tick on x-axis is displayed correctly + + var chartType = 'bar'; + if (initValues.m_UseLineChart) { + chartType = 'line'; + } + + if (initValues.m_ShowSubchart) { + ShowSubchart(chartType) + } + else { + HideSubchart(chartType) + } + + chart.unload(); //unload data before loading new data + chart.load({ + columns:[ + xValues, + yValues + ] + }); +} + +/* + * Calculation of the bin size. + */ +function calcBinSize(initValues) { + if (1 < initValues.m_XData.length) { + min = d3.min(initValues.m_XData); + max = d3.max(initValues.m_XData); + binSize = ((max - min) / (initValues.m_XData.length - 1)); + } + else { + binSize = 10; + } +} + +//Transforamtion between bar and line chart +//takes the name of the chart type as a parameter +//calles by QmitkC3jsWidget +function transformView(TransformTo) { + chart.transform(TransformTo); +}; + +function changeTheme(color) { + if (color == 'dark') { + link = document.getElementsByTagName("link")[0]; + link.href = "Histogram_dark.css"; + } + else + { + link = document.getElementsByTagName("link")[0]; + link.href = "Histogram.css"; + } +}; + +function ShowSubchart(chartType) +{ + chart = c3.generate({ + data: { + x : 'x', //use first "column" as x axis values + columns: [], //initialize empty. Data will be loaded in function setupChart(initValues) + type: chartType, + selection: { + enabled: false, + multiple: false, + } + }, + legend: { + position: 'inset' + }, + grid: { + y: { + lines: [{value:0}] //Draws a horizontal line at y=0 + } + }, + bar: { + width: { + ratio: 0.95 // this makes bar width 95% of length between ticks + } + }, + zoom: { + enabled: true, + }, + subchart: { + show: true //Shows a subchart that shows the region the primary chart is zoomed in to by overlay. + }, + axis: { + x: { + type: 'category', //only for type 'category' the bars will be rescaled in width on zoom + tick: { + multiline: false, + fit: false, //to make more x labels appear on zoom + centered: true, + }, + }, + y: { + tick: { + format: d3.format("s"), + }, + //for some reason, there is an offset for our linegraph. This is prevented by the following lines + //min: 0, + //padding: { top: 0, bottom: 0 } + } + }, + //Style data points in linegraph + point: { + r: 0.2, + focus: + { + expand: { + r: 4 + } + } + }, + tooltip: { + format: { + title: function (d) { + var endValue = (parseFloat(greyvalue[d]) + binSize); + endValue = endValue.toFixed(3); + return 'Greyvalue: ' + greyvalue[d] + '...' + endValue; + }, + } + } + }); +} + +function HideSubchart(chartType) +{ + chart = c3.generate({ + data: { + x : 'x', //use first "column" as x axis values + columns: [], //initialize empty. Data will be loaded in function setupChart(initValues) + type: chartType, + selection: { + enabled: false, + multiple: false, + } + }, + legend: { + position: 'inset' + }, + grid: { + y: { + lines: [{value:0}] //Draws a horizontal line at y=0 + } + }, + bar: { + width: { + ratio: 0.95 // this makes bar width 95% of length between ticks + } + }, + zoom: { + enabled: true, + }, + subchart: { + show: false //Shows a subchart that shows the region the primary chart is zoomed in to by overlay. + }, + axis: { + x: { + type: 'category', //only for type 'category' the bars will be rescaled in width on zoom + tick: { + multiline: false, + fit: false, //to make more x labels appear on zoom + centered: true, + }, + }, + y: { + tick: { + format: d3.format("s"), + }, + //for some reason, there is an offset for our linegraph. This is prevented by the following lines + //min: 0, + //padding: { top: 0, bottom: 0 } + } + }, + //Style data points in linegraph + point: { + r: 0.2, + focus: + { + expand: { + r: 4 + } + } + }, + tooltip: { + format: { + title: function (d) { + var endValue = (parseFloat(greyvalue[d]) + binSize); + endValue = endValue.toFixed(3); + return 'Greyvalue: ' + greyvalue[d] + '...' + endValue; + }, + } + } + }); +} \ No newline at end of file diff --git a/Modules/C3js/resource/Histogram_dark.css b/Modules/C3js/resource/Histogram_dark.css new file mode 100644 index 0000000000..a69610d1b1 --- /dev/null +++ b/Modules/C3js/resource/Histogram_dark.css @@ -0,0 +1,36 @@ +body { + background-color: darkgrey !important; +} + +/*-- Bar --*/ +.c3-bar { + fill-opacity: 1; + } + +.c3-bar._expanded_ { + fill: red !important; /*-- The !important tag prevents the color from being overwritten at rendering--*/ + fill-opacity: 1; +} + +/*-- Line --*/ +.c3-line { + stroke-width: 1px; + stroke: white; + /*stroke-dasharray: 5,5; /*to make it dashed*/ +} + +/*-- Point --*/ +.c3-circle._expanded_ { + stroke-width: 1px; + stroke: red !important; + fill: red !important; /*-- The !important tag prevents the color from being overwritten at rendering--*/} + +path.domain { stroke: white !important; } +.c3 .c3-axis-x path, .c3 .c3-axis-x line { + stroke: white !important; +} +.c3 .c3-axis-y path, .c3 .c3-axis-y line { + stroke: white !important; +} +.tick text { stroke: white !important; } +.c3-legend-item text { stroke: white !important; } \ No newline at end of file diff --git a/Modules/C3js/resource/QmitkC3jsWidget.html b/Modules/C3js/resource/QmitkC3jsWidget.html new file mode 100644 index 0000000000..81e4f0c89a --- /dev/null +++ b/Modules/C3js/resource/QmitkC3jsWidget.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + +
+ + + diff --git a/Modules/C3js/resource/c3.min.css b/Modules/C3js/resource/c3.min.css new file mode 100644 index 0000000000..c1131b1346 --- /dev/null +++ b/Modules/C3js/resource/c3.min.css @@ -0,0 +1,76 @@ +.c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent} + +.c3 line, +.c3 path{fill:none;stroke:#000} + +.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none} + +.c3-bars path, +.c3-event-rect, +.c3-legend-item-tile, +.c3-xgrid-focus, +.c3-ygrid{shape-rendering:crispEdges} + +.c3-chart-arc path{stroke:#fff} + +.c3-chart-arc text{fill:#fff;font-size:13px} + +.c3-grid line{stroke:#aaa} + +.c3-grid text{fill:#aaa} + +.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3} + +.c3-text.c3-empty{fill:gray;font-size:2em} + +.c3-line{stroke-width:1px} + +.c3-circle._expanded_{stroke-width:1px;stroke:#fff} + +.c3-selected-circle{fill:#fff;stroke-width:2px} + +.c3-bar{stroke-width:0} + +.c3-bar._expanded_{fill-opacity:.75} + +.c3-target.c3-focused{opacity:1} + +.c3-target.c3-focused path.c3-line, +.c3-target.c3-focused path.c3-step{stroke-width:2px} + +.c3-target.c3-defocused{opacity:.3!important} + +.c3-region{fill:#4682b4;fill-opacity:.1} + +.c3-brush .extent{fill-opacity:.1} + +.c3-legend-item{font-size:12px} + +.c3-legend-item-hidden{opacity:.15} + +.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1} + +.c3-title{font:14px sans-serif} + +.c3-tooltip-container{z-index:10} + +.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC} + +.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF} + +.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999} + +.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px} + +.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2} + +.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em} + +.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none} + +.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px} + +.c3-chart-arcs .c3-chart-arcs-gauge-max, +.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777} + +.c3-chart-arc .c3-gauge-value{fill:#000} \ No newline at end of file diff --git a/Modules/C3js/resource/c3.min.js b/Modules/C3js/resource/c3.min.js new file mode 100644 index 0000000000..3b8fbd975e --- /dev/null +++ b/Modules/C3js/resource/c3.min.js @@ -0,0 +1,6 @@ +!function(a){"use strict";function b(a){this.owner=a}function c(a,b){if(Object.create)b.prototype=Object.create(a.prototype);else{var c=function(){};c.prototype=a.prototype,b.prototype=new c}return b.prototype.constructor=b,b}function d(a){var b=this.internal=new e(this);b.loadConfig(a),b.beforeInit(a),b.init(),b.afterInit(a),function c(a,b,d){Object.keys(a).forEach(function(e){b[e]=a[e].bind(d),Object.keys(a[e]).length>0&&c(a[e],b[e],d)})}(h,this,this)}function e(b){var c=this;c.d3=a.d3?a.d3:"undefined"!=typeof require?require("d3"):void 0,c.api=b,c.config=c.getDefaultConfig(),c.data={},c.cache={},c.axes={}}function f(a){b.call(this,a)}function g(a,b){function c(a,b){a.attr("transform",function(a){return"translate("+Math.ceil(b(a)+u)+", 0)"})}function d(a,b){a.attr("transform",function(a){return"translate(0,"+Math.ceil(b(a))+")"})}function e(a){var b=a[0],c=a[a.length-1];return c>b?[b,c]:[c,b]}function f(a){var b,c,d=[];if(a.ticks)return a.ticks.apply(a,n);for(c=a.domain(),b=Math.ceil(c[0]);b0&&d[0]>0&&d.unshift(d[0]-(d[1]-d[0])),d}function g(){var a,c=p.copy();return b.isCategory&&(a=p.domain(),c.domain([a[0],a[1]-1])),c}function h(a){var b=m?m(a):a;return"undefined"!=typeof b?b:""}function i(a){if(A)return A;var b={h:11.5,w:5.5};return a.select("text").text(h).each(function(a){var c=this.getBoundingClientRect(),d=h(a),e=c.height,f=d?c.width/d.length:void 0;e&&f&&(b.h=e,b.w=f)}).text(""),A=b,b}function j(c){return b.withoutTransition?c:a.transition(c)}function k(m){m.each(function(){function m(a,c){function d(a,b){f=void 0;for(var h=1;hc)return d(a.concat(b.substr(0,f?f:h)),b.slice(f?f+1:h));return a.concat(b)}var e,f,g,i=h(a),j=[];return"[object Array]"===Object.prototype.toString.call(i)?i:((!c||0>=c)&&(c=X?95:b.isCategory?Math.ceil(F(G[1])-F(G[0]))-12:110),d(j,i+""))}function n(a,b){var c=U.h;return 0===b&&(c="left"===q||"right"===q?-((V[a.index]-1)*(U.h/2)-3):".71em"),c}function v(a){var b=p(a)+(o?0:u);return L[0]0?"start":"end":"middle"}function x(a){return a?"rotate("+a+")":""}function y(a){return a?8*Math.sin(Math.PI*(a/180)):0}function z(a){return a?11.5-2.5*(a/15)*(a>0?1:-1):W}var A,B,C,D=k.g=a.select(this),E=this.__chart__||p,F=this.__chart__=g(),G=t?t:f(F),H=D.selectAll(".tick").data(G,F),I=H.enter().insert("g",".domain").attr("class","tick").style("opacity",1e-6),J=H.exit().remove(),K=j(H).style("opacity",1),L=p.rangeExtent?p.rangeExtent():e(p.range()),M=D.selectAll(".domain").data([0]),N=(M.enter().append("path").attr("class","domain"),j(M));I.append("line"),I.append("text");var O=I.select("line"),P=K.select("line"),Q=I.select("text"),R=K.select("text");b.isCategory?(u=Math.ceil((F(1)-F(0))/2),B=o?0:u,C=o?u:0):u=B=0;var S,T,U=i(D.select(".tick")),V=[],W=Math.max(r,0)+s,X="left"===q||"right"===q;S=H.select("text"),T=S.selectAll("tspan").data(function(a,c){var d=b.tickMultiline?m(a,b.tickWidth):[].concat(h(a));return V[c]=d.length,d.map(function(a){return{index:c,splitted:a}})}),T.enter().append("tspan"),T.exit().remove(),T.text(function(a){return a.splitted});var Y=b.tickTextRotate;switch(q){case"bottom":A=c,O.attr("y2",r),Q.attr("y",W),P.attr("x1",B).attr("x2",B).attr("y2",v),R.attr("x",0).attr("y",z(Y)).style("text-anchor",w(Y)).attr("transform",x(Y)),T.attr("x",0).attr("dy",n).attr("dx",y(Y)),N.attr("d","M"+L[0]+","+l+"V0H"+L[1]+"V"+l);break;case"top":A=c,O.attr("y2",-r),Q.attr("y",-W),P.attr("x2",0).attr("y2",-r),R.attr("x",0).attr("y",-W),S.style("text-anchor","middle"),T.attr("x",0).attr("dy","0em"),N.attr("d","M"+L[0]+","+-l+"V0H"+L[1]+"V"+-l);break;case"left":A=d,O.attr("x2",-r),Q.attr("x",-W),P.attr("x2",-r).attr("y1",C).attr("y2",C),R.attr("x",-W).attr("y",u),S.style("text-anchor","end"),T.attr("x",-W).attr("dy",n),N.attr("d","M"+-l+","+L[0]+"H0V"+L[1]+"H"+-l);break;case"right":A=d,O.attr("x2",r),Q.attr("x",W),P.attr("x2",r).attr("y2",0),R.attr("x",W).attr("y",0),S.style("text-anchor","start"),T.attr("x",W).attr("dy",n),N.attr("d","M"+l+","+L[0]+"H0V"+L[1]+"H"+l)}if(F.rangeBand){var Z=F,$=Z.rangeBand()/2;E=F=function(a){return Z(a)+$}}else E.rangeBand?E=F:J.call(A,F);I.call(A,E),K.call(A,F)})}var l,m,n,o,p=a.scale.linear(),q="bottom",r=6,s=3,t=null,u=0,v=!0;return b=b||{},l=b.withOuterTick?6:0,k.scale=function(a){return arguments.length?(p=a,k):p},k.orient=function(a){return arguments.length?(q=a in{top:1,right:1,bottom:1,left:1}?a+"":"bottom",k):q},k.tickFormat=function(a){return arguments.length?(m=a,k):m},k.tickCentered=function(a){return arguments.length?(o=a,k):o},k.tickOffset=function(){return u},k.tickInterval=function(){var a,c;return b.isCategory?a=2*u:(c=k.g.select("path.domain").node().getTotalLength()-2*l,a=c/k.g.selectAll("line").size()),a===1/0?0:a},k.ticks=function(){return arguments.length?(n=arguments,k):n},k.tickCulling=function(a){return arguments.length?(v=a,k):v},k.tickValues=function(a){if("function"==typeof a)t=function(){return a(p.domain())};else{if(!arguments.length)return t;t=a}return k},k}var h,i,j,k={version:"0.4.11"};k.generate=function(a){return new d(a)},k.chart={fn:d.prototype,internal:{fn:e.prototype,axis:{fn:f.prototype}}},h=k.chart.fn,i=k.chart.internal.fn,j=k.chart.internal.axis.fn,i.beforeInit=function(){},i.afterInit=function(){},i.init=function(){var a=this,b=a.config;if(a.initParams(),b.data_url)a.convertUrlToData(b.data_url,b.data_mimeType,b.data_headers,b.data_keys,a.initWithData);else if(b.data_json)a.initWithData(a.convertJsonToData(b.data_json,b.data_keys));else if(b.data_rows)a.initWithData(a.convertRowsToData(b.data_rows));else{if(!b.data_columns)throw Error("url or json or rows or columns is required.");a.initWithData(a.convertColumnsToData(b.data_columns))}},i.initParams=function(){var a=this,b=a.d3,c=a.config;a.clipId="c3-"+ +new Date+"-clip",a.clipIdForXAxis=a.clipId+"-xaxis",a.clipIdForYAxis=a.clipId+"-yaxis",a.clipIdForGrid=a.clipId+"-grid",a.clipIdForSubchart=a.clipId+"-subchart",a.clipPath=a.getClipPath(a.clipId),a.clipPathForXAxis=a.getClipPath(a.clipIdForXAxis),a.clipPathForYAxis=a.getClipPath(a.clipIdForYAxis),a.clipPathForGrid=a.getClipPath(a.clipIdForGrid),a.clipPathForSubchart=a.getClipPath(a.clipIdForSubchart),a.dragStart=null,a.dragging=!1,a.flowing=!1,a.cancelClick=!1,a.mouseover=!1,a.transiting=!1,a.color=a.generateColor(),a.levelColor=a.generateLevelColor(),a.dataTimeFormat=c.data_xLocaltime?b.time.format:b.time.format.utc,a.axisTimeFormat=c.axis_x_localtime?b.time.format:b.time.format.utc,a.defaultAxisTimeFormat=a.axisTimeFormat.multi([[".%L",function(a){return a.getMilliseconds()}],[":%S",function(a){return a.getSeconds()}],["%I:%M",function(a){return a.getMinutes()}],["%I %p",function(a){return a.getHours()}],["%-m/%-d",function(a){return a.getDay()&&1!==a.getDate()}],["%-m/%-d",function(a){return 1!==a.getDate()}],["%-m/%-d",function(a){return a.getMonth()}],["%Y/%-m/%-d",function(){return!0}]]),a.hiddenTargetIds=[],a.hiddenLegendIds=[],a.focusedTargetIds=[],a.defocusedTargetIds=[],a.xOrient=c.axis_rotated?"left":"bottom",a.yOrient=c.axis_rotated?c.axis_y_inner?"top":"bottom":c.axis_y_inner?"right":"left",a.y2Orient=c.axis_rotated?c.axis_y2_inner?"bottom":"top":c.axis_y2_inner?"left":"right",a.subXOrient=c.axis_rotated?"left":"bottom",a.isLegendRight="right"===c.legend_position,a.isLegendInset="inset"===c.legend_position,a.isLegendTop="top-left"===c.legend_inset_anchor||"top-right"===c.legend_inset_anchor,a.isLegendLeft="top-left"===c.legend_inset_anchor||"bottom-left"===c.legend_inset_anchor,a.legendStep=0,a.legendItemWidth=0,a.legendItemHeight=0,a.currentMaxTickWidths={x:0,y:0,y2:0},a.rotated_padding_left=30,a.rotated_padding_right=c.axis_rotated&&!c.axis_x_show?0:30,a.rotated_padding_top=5,a.withoutFadeIn={},a.intervalForObserveInserted=void 0,a.axes.subx=b.selectAll([])},i.initChartElements=function(){this.initBar&&this.initBar(),this.initLine&&this.initLine(),this.initArc&&this.initArc(),this.initGauge&&this.initGauge(),this.initText&&this.initText()},i.initWithData=function(a){var b,c,d=this,e=d.d3,g=d.config,h=!0;d.axis=new f(d),d.initPie&&d.initPie(),d.initBrush&&d.initBrush(),d.initZoom&&d.initZoom(),g.bindto?"function"==typeof g.bindto.node?d.selectChart=g.bindto:d.selectChart=e.select(g.bindto):d.selectChart=e.selectAll([]),d.selectChart.empty()&&(d.selectChart=e.select(document.createElement("div")).style("opacity",0),d.observeInserted(d.selectChart),h=!1),d.selectChart.html("").classed("c3",!0),d.data.xs={},d.data.targets=d.convertDataToTargets(a),g.data_filter&&(d.data.targets=d.data.targets.filter(g.data_filter)),g.data_hide&&d.addHiddenTargetIds(g.data_hide===!0?d.mapToIds(d.data.targets):g.data_hide),g.legend_hide&&d.addHiddenLegendIds(g.legend_hide===!0?d.mapToIds(d.data.targets):g.legend_hide),d.hasType("gauge")&&(g.legend_show=!1),d.updateSizes(),d.updateScales(),d.x.domain(e.extent(d.getXDomain(d.data.targets))),d.y.domain(d.getYDomain(d.data.targets,"y")),d.y2.domain(d.getYDomain(d.data.targets,"y2")),d.subX.domain(d.x.domain()),d.subY.domain(d.y.domain()),d.subY2.domain(d.y2.domain()),d.orgXDomain=d.x.domain(),d.brush&&d.brush.scale(d.subX),g.zoom_enabled&&d.zoom.scale(d.x),d.svg=d.selectChart.append("svg").style("overflow","hidden").on("mouseenter",function(){return g.onmouseover.call(d)}).on("mouseleave",function(){return g.onmouseout.call(d)}),d.config.svg_classname&&d.svg.attr("class",d.config.svg_classname),b=d.svg.append("defs"),d.clipChart=d.appendClip(b,d.clipId),d.clipXAxis=d.appendClip(b,d.clipIdForXAxis),d.clipYAxis=d.appendClip(b,d.clipIdForYAxis),d.clipGrid=d.appendClip(b,d.clipIdForGrid),d.clipSubchart=d.appendClip(b,d.clipIdForSubchart),d.updateSvgSize(),c=d.main=d.svg.append("g").attr("transform",d.getTranslate("main")),d.initSubchart&&d.initSubchart(),d.initTooltip&&d.initTooltip(),d.initLegend&&d.initLegend(),d.initTitle&&d.initTitle(),c.append("text").attr("class",l.text+" "+l.empty).attr("text-anchor","middle").attr("dominant-baseline","middle"),d.initRegion(),d.initGrid(),c.append("g").attr("clip-path",d.clipPath).attr("class",l.chart),g.grid_lines_front&&d.initGridLines(),d.initEventRect(),d.initChartElements(),c.insert("rect",g.zoom_privileged?null:"g."+l.regions).attr("class",l.zoomRect).attr("width",d.width).attr("height",d.height).style("opacity",0).on("dblclick.zoom",null),g.axis_x_extent&&d.brush.extent(d.getDefaultExtent()),d.axis.init(),d.updateTargets(d.data.targets),h&&(d.updateDimension(),d.config.oninit.call(d),d.redraw({withTransition:!1,withTransform:!0,withUpdateXDomain:!0,withUpdateOrgXDomain:!0,withTransitionForAxis:!1})),d.bindResize(),d.api.element=d.selectChart.node()},i.smoothLines=function(a,b){var c=this;"grid"===b&&a.each(function(){var a=c.d3.select(this),b=a.attr("x1"),d=a.attr("x2"),e=a.attr("y1"),f=a.attr("y2");a.attr({x1:Math.ceil(b),x2:Math.ceil(d),y1:Math.ceil(e),y2:Math.ceil(f)})})},i.updateSizes=function(){var a=this,b=a.config,c=a.legend?a.getLegendHeight():0,d=a.legend?a.getLegendWidth():0,e=a.isLegendRight||a.isLegendInset?0:c,f=a.hasArcType(),g=b.axis_rotated||f?0:a.getHorizontalAxisHeight("x"),h=b.subchart_show&&!f?b.subchart_size_height+g:0;a.currentWidth=a.getCurrentWidth(),a.currentHeight=a.getCurrentHeight(),a.margin=b.axis_rotated?{top:a.getHorizontalAxisHeight("y2")+a.getCurrentPaddingTop(),right:f?0:a.getCurrentPaddingRight(),bottom:a.getHorizontalAxisHeight("y")+e+a.getCurrentPaddingBottom(),left:h+(f?0:a.getCurrentPaddingLeft())}:{top:4+a.getCurrentPaddingTop(),right:f?0:a.getCurrentPaddingRight(),bottom:g+h+e+a.getCurrentPaddingBottom(),left:f?0:a.getCurrentPaddingLeft()},a.margin2=b.axis_rotated?{top:a.margin.top,right:NaN,bottom:20+e,left:a.rotated_padding_left}:{top:a.currentHeight-h-e,right:NaN,bottom:g+e,left:a.margin.left},a.margin3={top:0,right:NaN,bottom:0,left:0},a.updateSizeForLegend&&a.updateSizeForLegend(c,d),a.width=a.currentWidth-a.margin.left-a.margin.right,a.height=a.currentHeight-a.margin.top-a.margin.bottom,a.width<0&&(a.width=0),a.height<0&&(a.height=0),a.width2=b.axis_rotated?a.margin.left-a.rotated_padding_left-a.rotated_padding_right:a.width,a.height2=b.axis_rotated?a.height:a.currentHeight-a.margin2.top-a.margin2.bottom,a.width2<0&&(a.width2=0),a.height2<0&&(a.height2=0),a.arcWidth=a.width-(a.isLegendRight?d+10:0),a.arcHeight=a.height-(a.isLegendRight?0:10),a.hasType("gauge")&&!b.gauge_fullCircle&&(a.arcHeight+=a.height-a.getGaugeLabelHeight()),a.updateRadius&&a.updateRadius(),a.isLegendRight&&f&&(a.margin3.left=a.arcWidth/2+1.1*a.radiusExpanded)},i.updateTargets=function(a){var b=this;b.updateTargetsForText(a),b.updateTargetsForBar(a),b.updateTargetsForLine(a),b.hasArcType()&&b.updateTargetsForArc&&b.updateTargetsForArc(a),b.updateTargetsForSubchart&&b.updateTargetsForSubchart(a),b.showTargets()},i.showTargets=function(){var a=this;a.svg.selectAll("."+l.target).filter(function(b){return a.isTargetToShow(b.id)}).transition().duration(a.config.transition_duration).style("opacity",1)},i.redraw=function(a,b){var c,d,e,f,g,h,i,j,k,m,n,o,p,q,r,s,t,u,v,x,y,z,A,B,C,D,E,F,G,H=this,I=H.main,J=H.d3,K=H.config,L=H.getShapeIndices(H.isAreaType),M=H.getShapeIndices(H.isBarType),N=H.getShapeIndices(H.isLineType),O=H.hasArcType(),P=H.filterTargetsToShow(H.data.targets),Q=H.xv.bind(H);if(a=a||{},c=w(a,"withY",!0),d=w(a,"withSubchart",!0),e=w(a,"withTransition",!0),h=w(a,"withTransform",!1),i=w(a,"withUpdateXDomain",!1),j=w(a,"withUpdateOrgXDomain",!1),k=w(a,"withTrimXDomain",!0),p=w(a,"withUpdateXAxis",i),m=w(a,"withLegend",!1),n=w(a,"withEventRect",!0),o=w(a,"withDimension",!0),f=w(a,"withTransitionForExit",e),g=w(a,"withTransitionForAxis",e),v=e?K.transition_duration:0,x=f?v:0,y=g?v:0,b=b||H.axis.generateTransitions(y),m&&K.legend_show?H.updateLegend(H.mapToIds(H.data.targets),a,b):o&&H.updateDimension(!0),H.isCategorized()&&0===P.length&&H.x.domain([0,H.axes.x.selectAll(".tick").size()]),P.length?(H.updateXDomain(P,i,j,k),K.axis_x_tick_values||(B=H.axis.updateXAxisTickValues(P))):(H.xAxis.tickValues([]),H.subXAxis.tickValues([])),K.zoom_rescale&&!a.flow&&(E=H.x.orgDomain()),H.y.domain(H.getYDomain(P,"y",E)),H.y2.domain(H.getYDomain(P,"y2",E)),!K.axis_y_tick_values&&K.axis_y_tick_count&&H.yAxis.tickValues(H.axis.generateTickValues(H.y.domain(),K.axis_y_tick_count)),!K.axis_y2_tick_values&&K.axis_y2_tick_count&&H.y2Axis.tickValues(H.axis.generateTickValues(H.y2.domain(),K.axis_y2_tick_count)),H.axis.redraw(b,O),H.axis.updateLabels(e),(i||p)&&P.length)if(K.axis_x_tick_culling&&B){for(C=1;C=0&&J.select(this).style("display",b%D?"none":"block")})}else H.svg.selectAll("."+l.axisX+" .tick text").style("display","block");q=H.generateDrawArea?H.generateDrawArea(L,!1):void 0,r=H.generateDrawBar?H.generateDrawBar(M):void 0,s=H.generateDrawLine?H.generateDrawLine(N,!1):void 0,t=H.generateXYForText(L,M,N,!0),u=H.generateXYForText(L,M,N,!1),c&&(H.subY.domain(H.getYDomain(P,"y")),H.subY2.domain(H.getYDomain(P,"y2"))),H.updateXgridFocus(),I.select("text."+l.text+"."+l.empty).attr("x",H.width/2).attr("y",H.height/2).text(K.data_empty_label_text).transition().style("opacity",P.length?0:1),H.updateGrid(v),H.updateRegion(v),H.updateBar(x),H.updateLine(x),H.updateArea(x),H.updateCircle(),H.hasDataLabel()&&H.updateText(x),H.redrawTitle&&H.redrawTitle(),H.redrawArc&&H.redrawArc(v,x,h),H.redrawSubchart&&H.redrawSubchart(d,b,v,x,L,M,N),I.selectAll("."+l.selectedCircles).filter(H.isBarType.bind(H)).selectAll("circle").remove(),K.interaction_enabled&&!a.flow&&n&&(H.redrawEventRect(),H.updateZoom&&H.updateZoom()),H.updateCircleY(),F=(H.config.axis_rotated?H.circleY:H.circleX).bind(H),G=(H.config.axis_rotated?H.circleX:H.circleY).bind(H),a.flow&&(A=H.generateFlow({targets:P,flow:a.flow,duration:a.flow.duration,drawBar:r,drawLine:s,drawArea:q,cx:F,cy:G,xv:Q,xForText:t,yForText:u})),(v||A)&&H.isTabVisible()?J.transition().duration(v).each(function(){var b=[];[H.redrawBar(r,!0),H.redrawLine(s,!0),H.redrawArea(q,!0),H.redrawCircle(F,G,!0),H.redrawText(t,u,a.flow,!0),H.redrawRegion(!0),H.redrawGrid(!0)].forEach(function(a){a.forEach(function(a){b.push(a)})}),z=H.generateWait(),b.forEach(function(a){z.add(a)})}).call(z,function(){A&&A(),K.onrendered&&K.onrendered.call(H)}):(H.redrawBar(r),H.redrawLine(s),H.redrawArea(q),H.redrawCircle(F,G),H.redrawText(t,u,a.flow),H.redrawRegion(),H.redrawGrid(),K.onrendered&&K.onrendered.call(H)),H.mapToIds(H.data.targets).forEach(function(a){H.withoutFadeIn[a]=!0})},i.updateAndRedraw=function(a){var b,c=this,d=c.config;a=a||{},a.withTransition=w(a,"withTransition",!0),a.withTransform=w(a,"withTransform",!1),a.withLegend=w(a,"withLegend",!1),a.withUpdateXDomain=!0,a.withUpdateOrgXDomain=!0,a.withTransitionForExit=!1,a.withTransitionForTransform=w(a,"withTransitionForTransform",a.withTransition),c.updateSizes(),a.withLegend&&d.legend_show||(b=c.axis.generateTransitions(a.withTransitionForAxis?d.transition_duration:0),c.updateScales(),c.updateSvgSize(),c.transformAll(a.withTransitionForTransform,b)),c.redraw(a,b)},i.redrawWithoutRescale=function(){this.redraw({withY:!1,withSubchart:!1,withEventRect:!1,withTransitionForAxis:!1})},i.isTimeSeries=function(){return"timeseries"===this.config.axis_x_type},i.isCategorized=function(){return this.config.axis_x_type.indexOf("categor")>=0},i.isCustomX=function(){var a=this,b=a.config;return!a.isTimeSeries()&&(b.data_x||v(b.data_xs))},i.isTimeSeriesY=function(){return"timeseries"===this.config.axis_y_type},i.getTranslate=function(a){var b,c,d=this,e=d.config;return"main"===a?(b=s(d.margin.left),c=s(d.margin.top)):"context"===a?(b=s(d.margin2.left),c=s(d.margin2.top)):"legend"===a?(b=d.margin3.left,c=d.margin3.top):"x"===a?(b=0,c=e.axis_rotated?0:d.height):"y"===a?(b=0,c=e.axis_rotated?d.height:0):"y2"===a?(b=e.axis_rotated?0:d.width,c=e.axis_rotated?1:0):"subx"===a?(b=0,c=e.axis_rotated?0:d.height2):"arc"===a&&(b=d.arcWidth/2,c=d.arcHeight/2),"translate("+b+","+c+")"},i.initialOpacity=function(a){return null!==a.value&&this.withoutFadeIn[a.id]?1:0},i.initialOpacityForCircle=function(a){return null!==a.value&&this.withoutFadeIn[a.id]?this.opacityForCircle(a):0},i.opacityForCircle=function(a){var b=this.config.point_show?1:0;return m(a.value)?this.isScatterType(a)?.5:b:0},i.opacityForText=function(){return this.hasDataLabel()?1:0},i.xx=function(a){return a?this.x(a.x):null},i.xv=function(a){var b=this,c=a.value;return b.isTimeSeries()?c=b.parseDate(a.value):b.isCategorized()&&"string"==typeof a.value&&(c=b.config.axis_x_categories.indexOf(a.value)),Math.ceil(b.x(c))},i.yv=function(a){var b=this,c=a.axis&&"y2"===a.axis?b.y2:b.y;return Math.ceil(c(a.value))},i.subxx=function(a){return a?this.subX(a.x):null},i.transformMain=function(a,b){var c,d,e,f=this;b&&b.axisX?c=b.axisX:(c=f.main.select("."+l.axisX),a&&(c=c.transition())),b&&b.axisY?d=b.axisY:(d=f.main.select("."+l.axisY),a&&(d=d.transition())),b&&b.axisY2?e=b.axisY2:(e=f.main.select("."+l.axisY2),a&&(e=e.transition())),(a?f.main.transition():f.main).attr("transform",f.getTranslate("main")),c.attr("transform",f.getTranslate("x")),d.attr("transform",f.getTranslate("y")),e.attr("transform",f.getTranslate("y2")),f.main.select("."+l.chartArcs).attr("transform",f.getTranslate("arc"))},i.transformAll=function(a,b){var c=this;c.transformMain(a,b),c.config.subchart_show&&c.transformContext(a,b),c.legend&&c.transformLegend(a)},i.updateSvgSize=function(){var a=this,b=a.svg.select(".c3-brush .background");a.svg.attr("width",a.currentWidth).attr("height",a.currentHeight),a.svg.selectAll(["#"+a.clipId,"#"+a.clipIdForGrid]).select("rect").attr("width",a.width).attr("height",a.height),a.svg.select("#"+a.clipIdForXAxis).select("rect").attr("x",a.getXAxisClipX.bind(a)).attr("y",a.getXAxisClipY.bind(a)).attr("width",a.getXAxisClipWidth.bind(a)).attr("height",a.getXAxisClipHeight.bind(a)),a.svg.select("#"+a.clipIdForYAxis).select("rect").attr("x",a.getYAxisClipX.bind(a)).attr("y",a.getYAxisClipY.bind(a)).attr("width",a.getYAxisClipWidth.bind(a)).attr("height",a.getYAxisClipHeight.bind(a)),a.svg.select("#"+a.clipIdForSubchart).select("rect").attr("width",a.width).attr("height",b.size()?b.attr("height"):0),a.svg.select("."+l.zoomRect).attr("width",a.width).attr("height",a.height),a.selectChart.style("max-height",a.currentHeight+"px")},i.updateDimension=function(a){var b=this;a||(b.config.axis_rotated?(b.axes.x.call(b.xAxis),b.axes.subx.call(b.subXAxis)):(b.axes.y.call(b.yAxis),b.axes.y2.call(b.y2Axis))),b.updateSizes(),b.updateScales(),b.updateSvgSize(),b.transformAll(!1)},i.observeInserted=function(b){var c,d=this;return"undefined"==typeof MutationObserver?void a.console.error("MutationObserver not defined."):(c=new MutationObserver(function(e){e.forEach(function(e){"childList"===e.type&&e.previousSibling&&(c.disconnect(),d.intervalForObserveInserted=a.setInterval(function(){b.node().parentNode&&(a.clearInterval(d.intervalForObserveInserted),d.updateDimension(),d.brush&&d.brush.update(),d.config.oninit.call(d),d.redraw({withTransform:!0,withUpdateXDomain:!0,withUpdateOrgXDomain:!0,withTransition:!1,withTransitionForTransform:!1,withLegend:!0}),b.transition().style("opacity",1))},10))})}),void c.observe(b.node(),{attributes:!0,childList:!0,characterData:!0}))},i.bindResize=function(){var b=this,c=b.config;if(b.resizeFunction=b.generateResize(),b.resizeFunction.add(function(){c.onresize.call(b)}),c.resize_auto&&b.resizeFunction.add(function(){void 0!==b.resizeTimeout&&a.clearTimeout(b.resizeTimeout),b.resizeTimeout=a.setTimeout(function(){delete b.resizeTimeout,b.api.flush()},100)}),b.resizeFunction.add(function(){c.onresized.call(b)}),a.attachEvent)a.attachEvent("onresize",b.resizeFunction);else if(a.addEventListener)a.addEventListener("resize",b.resizeFunction,!1);else{var d=a.onresize;d?d.add&&d.remove||(d=b.generateResize(),d.add(a.onresize)):d=b.generateResize(),d.add(b.resizeFunction),a.onresize=d}},i.generateResize=function(){function a(){b.forEach(function(a){a()})}var b=[];return a.add=function(a){b.push(a)},a.remove=function(a){for(var c=0;c0)for(g=h.hasNegativeValueInTargets(a),b=0;b=0}),0!==e.length)for(d=e[0],g&&k[d]&&k[d].forEach(function(a,b){k[d][b]=0>a?a:0}),c=1;c0||(k[d][b]+=+a)});return h.d3.min(Object.keys(k).map(function(a){return h.d3.min(k[a])}))},i.getYDomainMax=function(a){var b,c,d,e,f,g,h=this,i=h.config,j=h.mapToIds(a),k=h.getValuesAsIdKeyed(a);if(i.data_groups.length>0)for(g=h.hasPositiveValueInTargets(a),b=0;b=0}),0!==e.length)for(d=e[0],g&&k[d]&&k[d].forEach(function(a,b){k[d][b]=a>0?a:0}),c=1;c+a||(k[d][b]+=+a)});return h.d3.max(Object.keys(k).map(function(a){return h.d3.max(k[a])}))},i.getYDomain=function(a,b,c){var d,e,f,g,h,i,j,k,l,n,o,p=this,q=p.config,r=a.filter(function(a){return p.axis.getId(a.id)===b}),s=c?p.filterByXDomain(r,c):r,u="y2"===b?q.axis_y2_min:q.axis_y_min,w="y2"===b?q.axis_y2_max:q.axis_y_max,x=p.getYDomainMin(s),y=p.getYDomainMax(s),z="y2"===b?q.axis_y2_center:q.axis_y_center,A=p.hasType("bar",s)&&q.bar_zerobased||p.hasType("area",s)&&q.area_zerobased,B="y2"===b?q.axis_y2_inverted:q.axis_y_inverted,C=p.hasDataLabel()&&q.axis_rotated,D=p.hasDataLabel()&&!q.axis_rotated;return x=m(u)?u:m(w)?w>x?x:w-10:x,y=m(w)?w:m(u)?y>u?y:u+10:y,0===s.length?"y2"===b?p.y2.domain():p.y.domain():(isNaN(x)&&(x=0),isNaN(y)&&(y=x),x===y&&(0>x?y=0:x=0),n=x>=0&&y>=0,o=0>=x&&0>=y,(m(u)&&n||m(w)&&o)&&(A=!1),A&&(n&&(x=0),o&&(y=0)),e=Math.abs(y-x),f=g=h=.1*e,"undefined"!=typeof z&&(i=Math.max(Math.abs(x),Math.abs(y)),y=z+i,x=z-i),C?(j=p.getDataLabelLength(x,y,"width"),k=t(p.y.range()),l=[j[0]/k,j[1]/k], +g+=e*(l[1]/(1-l[0]-l[1])),h+=e*(l[0]/(1-l[0]-l[1]))):D&&(j=p.getDataLabelLength(x,y,"height"),g+=p.axis.convertPixelsToAxisPadding(j[1],e),h+=p.axis.convertPixelsToAxisPadding(j[0],e)),"y"===b&&v(q.axis_y_padding)&&(g=p.axis.getPadding(q.axis_y_padding,"top",g,e),h=p.axis.getPadding(q.axis_y_padding,"bottom",h,e)),"y2"===b&&v(q.axis_y2_padding)&&(g=p.axis.getPadding(q.axis_y2_padding,"top",g,e),h=p.axis.getPadding(q.axis_y2_padding,"bottom",h,e)),A&&(n&&(h=x),o&&(g=-y)),d=[x-h,y+g],B?d.reverse():d)},i.getXDomainMin=function(a){var b=this,c=b.config;return q(c.axis_x_min)?b.isTimeSeries()?this.parseDate(c.axis_x_min):c.axis_x_min:b.d3.min(a,function(a){return b.d3.min(a.values,function(a){return a.x})})},i.getXDomainMax=function(a){var b=this,c=b.config;return q(c.axis_x_max)?b.isTimeSeries()?this.parseDate(c.axis_x_max):c.axis_x_max:b.d3.max(a,function(a){return b.d3.max(a.values,function(a){return a.x})})},i.getXDomainPadding=function(a){var b,c,d,e,f=this,g=f.config,h=a[1]-a[0];return f.isCategorized()?c=0:f.hasType("bar")?(b=f.getMaxDataCount(),c=b>1?h/(b-1)/2:.5):c=.01*h,"object"==typeof g.axis_x_padding&&v(g.axis_x_padding)?(d=m(g.axis_x_padding.left)?g.axis_x_padding.left:c,e=m(g.axis_x_padding.right)?g.axis_x_padding.right:c):d=e="number"==typeof g.axis_x_padding?g.axis_x_padding:c,{left:d,right:e}},i.getXDomain=function(a){var b=this,c=[b.getXDomainMin(a),b.getXDomainMax(a)],d=c[0],e=c[1],f=b.getXDomainPadding(c),g=0,h=0;return d-e!==0||b.isCategorized()||(b.isTimeSeries()?(d=new Date(.5*d.getTime()),e=new Date(1.5*e.getTime())):(d=0===d?1:.5*d,e=0===e?-1:1.5*e)),(d||0===d)&&(g=b.isTimeSeries()?new Date(d.getTime()-f.left):d-f.left),(e||0===e)&&(h=b.isTimeSeries()?new Date(e.getTime()+f.right):e+f.right),[g,h]},i.updateXDomain=function(a,b,c,d,e){var f=this,g=f.config;return c&&(f.x.domain(e?e:f.d3.extent(f.getXDomain(a))),f.orgXDomain=f.x.domain(),g.zoom_enabled&&f.zoom.scale(f.x).updateScaleExtent(),f.subX.domain(f.x.domain()),f.brush&&f.brush.scale(f.subX)),b&&(f.x.domain(e?e:!f.brush||f.brush.empty()?f.orgXDomain:f.brush.extent()),g.zoom_enabled&&f.zoom.scale(f.x).updateScaleExtent()),d&&f.x.domain(f.trimXDomain(f.x.orgDomain())),f.x.domain()},i.trimXDomain=function(a){var b=this.getZoomDomain(),c=b[0],d=b[1];return a[0]<=c&&(a[1]=+a[1]+(c-a[0]),a[0]=c),d<=a[1]&&(a[0]=+a[0]-(a[1]-d),a[1]=d),a},i.isX=function(a){var b=this,c=b.config;return c.data_x&&a===c.data_x||v(c.data_xs)&&x(c.data_xs,a)},i.isNotX=function(a){return!this.isX(a)},i.getXKey=function(a){var b=this,c=b.config;return c.data_x?c.data_x:v(c.data_xs)?c.data_xs[a]:null},i.getXValuesOfXKey=function(a,b){var c,d=this,e=b&&v(b)?d.mapToIds(b):[];return e.forEach(function(b){d.getXKey(b)===a&&(c=d.data.xs[b])}),c},i.getIndexByX=function(a){var b=this,c=b.filterByX(b.data.targets,a);return c.length?c[0].index:null},i.getXValue=function(a,b){var c=this;return a in c.data.xs&&c.data.xs[a]&&m(c.data.xs[a][b])?c.data.xs[a][b]:b},i.getOtherTargetXs=function(){var a=this,b=Object.keys(a.data.xs);return b.length?a.data.xs[b[0]]:null},i.getOtherTargetX=function(a){var b=this.getOtherTargetXs();return b&&a1},i.isMultipleX=function(){return v(this.config.data_xs)||!this.config.data_xSort||this.hasType("scatter")},i.addName=function(a){var b,c=this;return a&&(b=c.config.data_names[a.id],a.name=void 0!==b?b:a.id),a},i.getValueOnIndex=function(a,b){var c=a.filter(function(a){return a.index===b});return c.length?c[0]:null},i.updateTargetX=function(a,b){var c=this;a.forEach(function(a){a.values.forEach(function(d,e){d.x=c.generateTargetX(b[e],a.id,e)}),c.data.xs[a.id]=b})},i.updateTargetXs=function(a,b){var c=this;a.forEach(function(a){b[a.id]&&c.updateTargetX([a],b[a.id])})},i.generateTargetX=function(a,b,c){var d,e=this;return d=e.isTimeSeries()?a?e.parseDate(a):e.parseDate(e.getXValue(b,c)):e.isCustomX()&&!e.isCategorized()?m(a)?+a:e.getXValue(b,c):c},i.cloneTarget=function(a){return{id:a.id,id_org:a.id_org,values:a.values.map(function(a){return{x:a.x,value:a.value,id:a.id}})}},i.updateXs=function(){var a=this;a.data.targets.length&&(a.xs=[],a.data.targets[0].values.forEach(function(b){a.xs[b.index]=b.x}))},i.getPrevX=function(a){var b=this.xs[a-1];return"undefined"!=typeof b?b:null},i.getNextX=function(a){var b=this.xs[a+1];return"undefined"!=typeof b?b:null},i.getMaxDataCount=function(){var a=this;return a.d3.max(a.data.targets,function(a){return a.values.length})},i.getMaxDataCountTarget=function(a){var b,c=a.length,d=0;return c>1?a.forEach(function(a){a.values.length>d&&(b=a,d=a.values.length)}):b=c?a[0]:null,b},i.getEdgeX=function(a){var b=this;return a.length?[b.d3.min(a,function(a){return a.values[0].x}),b.d3.max(a,function(a){return a.values[a.values.length-1].x})]:[0,0]},i.mapToIds=function(a){return a.map(function(a){return a.id})},i.mapToTargetIds=function(a){var b=this;return a?[].concat(a):b.mapToIds(b.data.targets)},i.hasTarget=function(a,b){var c,d=this.mapToIds(a);for(c=0;ca?-1:a>b?1:a>=b?0:NaN})},i.addHiddenTargetIds=function(a){this.hiddenTargetIds=this.hiddenTargetIds.concat(a)},i.removeHiddenTargetIds=function(a){this.hiddenTargetIds=this.hiddenTargetIds.filter(function(b){return a.indexOf(b)<0})},i.addHiddenLegendIds=function(a){this.hiddenLegendIds=this.hiddenLegendIds.concat(a)},i.removeHiddenLegendIds=function(a){this.hiddenLegendIds=this.hiddenLegendIds.filter(function(b){return a.indexOf(b)<0})},i.getValuesAsIdKeyed=function(a){var b={};return a.forEach(function(a){b[a.id]=[],a.values.forEach(function(c){b[a.id].push(c.value)})}),b},i.checkValueInTargets=function(a,b){var c,d,e,f=Object.keys(a);for(c=0;ca})},i.hasPositiveValueInTargets=function(a){return this.checkValueInTargets(a,function(a){return a>0})},i.isOrderDesc=function(){var a=this.config;return"string"==typeof a.data_order&&"desc"===a.data_order.toLowerCase()},i.isOrderAsc=function(){var a=this.config;return"string"==typeof a.data_order&&"asc"===a.data_order.toLowerCase()},i.orderTargets=function(a){var b=this,c=b.config,d=b.isOrderAsc(),e=b.isOrderDesc();return d||e?a.sort(function(a,b){var c=function(a,b){return a+Math.abs(b.value)},e=a.values.reduce(c,0),f=b.values.reduce(c,0);return d?f-e:e-f}):n(c.data_order)&&a.sort(c.data_order),a},i.filterByX=function(a,b){return this.d3.merge(a.map(function(a){return a.values})).filter(function(a){return a.x-b===0})},i.filterRemoveNull=function(a){return a.filter(function(a){return m(a.value)})},i.filterByXDomain=function(a,b){return a.map(function(a){return{id:a.id,id_org:a.id_org,values:a.values.filter(function(a){return b[0]<=a.x&&a.x<=b[1]})}})},i.hasDataLabel=function(){var a=this.config;return"boolean"==typeof a.data_labels&&a.data_labels?!0:!("object"!=typeof a.data_labels||!v(a.data_labels))},i.getDataLabelLength=function(a,b,c){var d=this,e=[0,0],f=1.3;return d.selectChart.select("svg").selectAll(".dummy").data([a,b]).enter().append("text").text(function(a){return d.dataLabelFormat(a.id)(a)}).each(function(a,b){e[b]=this.getBoundingClientRect()[c]*f}).remove(),e},i.isNoneArc=function(a){return this.hasTarget(this.data.targets,a.id)},i.isArc=function(a){return"data"in a&&this.hasTarget(this.data.targets,a.data.id)},i.findSameXOfValues=function(a,b){var c,d=a[b].x,e=[];for(c=b-1;c>=0&&d===a[c].x;c--)e.push(a[c]);for(c=b;cf&&(e=f,c=a)}),c},i.dist=function(a,b){var c=this,d=c.config,e=d.axis_rotated?1:0,f=d.axis_rotated?0:1,g=c.circleY(a,a.index),h=c.x(a.x);return Math.sqrt(Math.pow(h-b[e],2)+Math.pow(g-b[f],2))},i.convertValuesToStep=function(a){var b,c=[].concat(a);if(!this.isCategorized())return a;for(b=a.length+1;b>0;b--)c[b]=c[b-1];return c[0]={x:c[0].x-1,value:c[0].value,id:c[0].id},c[a.length+1]={x:c[a.length].x+1,value:c[a.length].value,id:c[a.length].id},c},i.updateDataAttributes=function(a,b){var c=this,d=c.config,e=d["data_"+a];return"undefined"==typeof b?e:(Object.keys(b).forEach(function(a){e[a]=b[a]}),c.redraw({withLegend:!0}),e)},i.convertUrlToData=function(a,b,c,d,e){var f=this,g=b?b:"csv",h=f.d3.xhr(a);c&&Object.keys(c).forEach(function(a){h.header(a,c[a])}),h.get(function(a,b){var c;if(!b)throw new Error(a.responseURL+" "+a.status+" ("+a.statusText+")");c="json"===g?f.convertJsonToData(JSON.parse(b.response),d):"tsv"===g?f.convertTsvToData(b.response):f.convertCsvToData(b.response),e.call(f,c)})},i.convertXsvToData=function(a,b){var c,d=b.parseRows(a);return 1===d.length?(c=[{}],d[0].forEach(function(a){c[0][a]=null})):c=b.parse(a),c},i.convertCsvToData=function(a){return this.convertXsvToData(a,this.d3.csv)},i.convertTsvToData=function(a){return this.convertXsvToData(a,this.d3.tsv)},i.convertJsonToData=function(a,b){var c,d,e=this,f=[];return b?(b.x?(c=b.value.concat(b.x),e.config.data_x=b.x):c=b.value,f.push(c),a.forEach(function(a){var b=[];c.forEach(function(c){var d=e.findValueInJson(a,c);p(d)&&(d=null),b.push(d)}),f.push(b)}),d=e.convertRowsToData(f)):(Object.keys(a).forEach(function(b){f.push([b].concat(a[b]))}),d=e.convertColumnsToData(f)),d},i.findValueInJson=function(a,b){b=b.replace(/\[(\w+)\]/g,".$1"),b=b.replace(/^\./,"");for(var c=b.split("."),d=0;d=0?d.data.xs[c]=(b&&d.data.xs[c]?d.data.xs[c]:[]).concat(a.map(function(a){return a[f]}).filter(m).map(function(a,b){return d.generateTargetX(a,c,b)})):e.data_x?d.data.xs[c]=d.getOtherTargetXs():v(e.data_xs)&&(d.data.xs[c]=d.getXValuesOfXKey(f,d.data.targets)):d.data.xs[c]=a.map(function(a,b){return b})}),f.forEach(function(a){if(!d.data.xs[a])throw new Error('x is not defined for id = "'+a+'".')}),c=f.map(function(b,c){var f=e.data_idConverter(b);return{id:f,id_org:b,values:a.map(function(a,g){var h,i=d.getXKey(b),j=a[i],k=null===a[b]||isNaN(a[b])?null:+a[b];return d.isCustomX()&&d.isCategorized()&&0===c&&!p(j)?(0===c&&0===g&&(e.axis_x_categories=[]),h=e.axis_x_categories.indexOf(j),-1===h&&(h=e.axis_x_categories.length,e.axis_x_categories.push(j))):h=d.generateTargetX(j,b,g),(p(a[b])||d.data.xs[b].length<=g)&&(h=void 0),{x:h,value:k,id:f}}).filter(function(a){return q(a.x)})}}),c.forEach(function(a){var b;e.data_xSort&&(a.values=a.values.sort(function(a,b){var c=a.x||0===a.x?a.x:1/0,d=b.x||0===b.x?b.x:1/0;return c-d})),b=0,a.values.forEach(function(a){a.index=b++}),d.data.xs[a.id].sort(function(a,b){return a-b})}),d.hasNegativeValue=d.hasNegativeValueInTargets(c),d.hasPositiveValue=d.hasPositiveValueInTargets(c),e.data_type&&d.setTargetType(d.mapToIds(c).filter(function(a){return!(a in e.data_types)}),e.data_type),c.forEach(function(a){d.addCache(a.id_org,a)}),c},i.load=function(a,b){var c=this;a&&(b.filter&&(a=a.filter(b.filter)),(b.type||b.types)&&a.forEach(function(a){var d=b.types&&b.types[a.id]?b.types[a.id]:b.type;c.setTargetType(a.id,d)}),c.data.targets.forEach(function(b){for(var c=0;c0?c:320/(a.hasType("gauge")&&!b.gauge_fullCircle?2:1)},i.getCurrentPaddingTop=function(){var a=this,b=a.config,c=m(b.padding_top)?b.padding_top:0;return a.title&&a.title.node()&&(c+=a.getTitlePadding()),c},i.getCurrentPaddingBottom=function(){var a=this.config;return m(a.padding_bottom)?a.padding_bottom:0},i.getCurrentPaddingLeft=function(a){var b=this,c=b.config;return m(c.padding_left)?c.padding_left:c.axis_rotated?c.axis_x_show?Math.max(r(b.getAxisWidthByAxisId("x",a)),40):1:!c.axis_y_show||c.axis_y_inner?b.axis.getYAxisLabelPosition().isOuter?30:1:r(b.getAxisWidthByAxisId("y",a))},i.getCurrentPaddingRight=function(){var a=this,b=a.config,c=10,d=a.isLegendRight?a.getLegendWidth()+20:0;return m(b.padding_right)?b.padding_right+1:b.axis_rotated?c+d:!b.axis_y2_show||b.axis_y2_inner?2+d+(a.axis.getY2AxisLabelPosition().isOuter?20:0):r(a.getAxisWidthByAxisId("y2"))+d},i.getParentRectValue=function(a){for(var b,c=this.selectChart.node();c&&"BODY"!==c.tagName;){try{b=c.getBoundingClientRect()[a]}catch(d){"width"===a&&(b=c.offsetWidth)}if(b)break;c=c.parentNode}return b},i.getParentWidth=function(){return this.getParentRectValue("width")},i.getParentHeight=function(){var a=this.selectChart.style("height");return a.indexOf("px")>0?+a.replace("px",""):0},i.getSvgLeft=function(a){var b=this,c=b.config,d=c.axis_rotated||!c.axis_rotated&&!c.axis_y_inner,e=c.axis_rotated?l.axisX:l.axisY,f=b.main.select("."+e).node(),g=f&&d?f.getBoundingClientRect():{right:0},h=b.selectChart.node().getBoundingClientRect(),i=b.hasArcType(),j=g.right-h.left-(i?0:b.getCurrentPaddingLeft(a));return j>0?j:0},i.getAxisWidthByAxisId=function(a,b){var c=this,d=c.axis.getLabelPositionById(a);return c.axis.getMaxTickWidth(a,b)+(d.isInner?20:40)},i.getHorizontalAxisHeight=function(a){var b=this,c=b.config,d=30;return"x"!==a||c.axis_x_show?"x"===a&&c.axis_x_height?c.axis_x_height:"y"!==a||c.axis_y_show?"y2"!==a||c.axis_y2_show?("x"===a&&!c.axis_rotated&&c.axis_x_tick_rotate&&(d=30+b.axis.getMaxTickWidth(a)*Math.cos(Math.PI*(90-c.axis_x_tick_rotate)/180)),"y"===a&&c.axis_rotated&&c.axis_y_tick_rotate&&(d=30+b.axis.getMaxTickWidth(a)*Math.cos(Math.PI*(90-c.axis_y_tick_rotate)/180)),d+(b.axis.getLabelPositionById(a).isInner?0:10)+("y2"===a?-10:0)):b.rotated_padding_top:!c.legend_show||b.isLegendRight||b.isLegendInset?1:10:8},i.getEventRectWidth=function(){return Math.max(0,this.xAxis.tickInterval())},i.getShapeIndices=function(a){var b,c,d=this,e=d.config,f={},g=0;return d.filterTargetsToShow(d.data.targets.filter(a,d)).forEach(function(a){for(b=0;b=0&&(j+=h(e[g].value)-i))}),j}},i.isWithinShape=function(a,b){var c,d=this,e=d.d3.select(a);return d.isTargetToShow(b.id)?"circle"===a.nodeName?c=d.isStepType(b)?d.isWithinStep(a,d.getYScale(b.id)(b.value)):d.isWithinCircle(a,1.5*d.pointSelectR(b)):"path"===a.nodeName&&(c=e.classed(l.bar)?d.isWithinBar(a):!0):c=!1,c},i.getInterpolate=function(a){var b=this,c=b.isInterpolationType(b.config.spline_interpolation_type)?b.config.spline_interpolation_type:"cardinal";return b.isSplineType(a)?c:b.isStepType(a)?b.config.line_step_type:"linear"},i.initLine=function(){var a=this;a.main.select("."+l.chart).append("g").attr("class",l.chartLines)},i.updateTargetsForLine=function(a){var b,c,d=this,e=d.config,f=d.classChartLine.bind(d),g=d.classLines.bind(d),h=d.classAreas.bind(d),i=d.classCircles.bind(d),j=d.classFocus.bind(d);b=d.main.select("."+l.chartLines).selectAll("."+l.chartLine).data(a).attr("class",function(a){return f(a)+j(a)}),c=b.enter().append("g").attr("class",f).style("opacity",0).style("pointer-events","none"),c.append("g").attr("class",g),c.append("g").attr("class",h),c.append("g").attr("class",function(a){return d.generateClass(l.selectedCircles,a.id)}),c.append("g").attr("class",i).style("cursor",function(a){return e.data_selection_isselectable(a)?"pointer":null}),a.forEach(function(a){d.main.selectAll("."+l.selectedCircles+d.getTargetSelectorSuffix(a.id)).selectAll("."+l.selectedCircle).each(function(b){b.value=a.values[b.index].value})})},i.updateLine=function(a){var b=this;b.mainLine=b.main.selectAll("."+l.lines).selectAll("."+l.line).data(b.lineData.bind(b)),b.mainLine.enter().append("path").attr("class",b.classLine.bind(b)).style("stroke",b.color),b.mainLine.style("opacity",b.initialOpacity.bind(b)).style("shape-rendering",function(a){return b.isStepType(a)?"crispEdges":""}).attr("transform",null),b.mainLine.exit().transition().duration(a).style("opacity",0).remove()},i.redrawLine=function(a,b){return[(b?this.mainLine.transition(Math.random().toString()):this.mainLine).attr("d",a).style("stroke",this.color).style("opacity",1)]},i.generateDrawLine=function(a,b){var c=this,d=c.config,e=c.d3.svg.line(),f=c.generateGetLinePoints(a,b),g=b?c.getSubYScale:c.getYScale,h=function(a){return(b?c.subxx:c.xx).call(c,a)},i=function(a,b){return d.data_groups.length>0?f(a,b)[0][1]:g.call(c,a.id)(a.value)};return e=d.axis_rotated?e.x(i).y(h):e.x(h).y(i),d.line_connectNull||(e=e.defined(function(a){return null!=a.value})),function(a){var f,h=d.line_connectNull?c.filterRemoveNull(a.values):a.values,i=b?c.x:c.subX,j=g.call(c,a.id),k=0,l=0;return c.isLineType(a)?d.data_regions[a.id]?f=c.lineWithRegions(h,i,j,d.data_regions[a.id]):(c.isStepType(a)&&(h=c.convertValuesToStep(h)),f=e.interpolate(c.getInterpolate(a))(h)):(h[0]&&(k=i(h[0].x),l=j(h[0].value)),f=d.axis_rotated?"M "+l+" "+k:"M "+k+" "+l),f?f:"M 0 0"}},i.generateGetLinePoints=function(a,b){var c=this,d=c.config,e=a.__max__+1,f=c.getShapeX(0,e,a,!!b),g=c.getShapeY(!!b),h=c.getShapeOffset(c.isLineType,a,!!b),i=b?c.getSubYScale:c.getYScale;return function(a,b){var e=i.call(c,a.id)(0),j=h(a,b)||e,k=f(a),l=g(a);return d.axis_rotated&&(0l||a.value<0&&l>e)&&(l=e),[[k,l-(e-j)],[k,l-(e-j)],[k,l-(e-j)],[k,l-(e-j)]]}},i.lineWithRegions=function(a,b,c,d){function e(a,b){var c;for(c=0;c=h;h+=r)x+=i(a[g-1],a[g],h,o);w=a[g].x}return x},i.updateArea=function(a){var b=this,c=b.d3;b.mainArea=b.main.selectAll("."+l.areas).selectAll("."+l.area).data(b.lineData.bind(b)),b.mainArea.enter().append("path").attr("class",b.classArea.bind(b)).style("fill",b.color).style("opacity",function(){return b.orgAreaOpacity=+c.select(this).style("opacity"),0}),b.mainArea.style("opacity",b.orgAreaOpacity),b.mainArea.exit().transition().duration(a).style("opacity",0).remove()},i.redrawArea=function(a,b){return[(b?this.mainArea.transition(Math.random().toString()):this.mainArea).attr("d",a).style("fill",this.color).style("opacity",this.orgAreaOpacity)]},i.generateDrawArea=function(a,b){var c=this,d=c.config,e=c.d3.svg.area(),f=c.generateGetAreaPoints(a,b),g=b?c.getSubYScale:c.getYScale,h=function(a){return(b?c.subxx:c.xx).call(c,a)},i=function(a,b){return d.data_groups.length>0?f(a,b)[0][1]:g.call(c,a.id)(c.getAreaBaseValue(a.id))},j=function(a,b){return d.data_groups.length>0?f(a,b)[1][1]:g.call(c,a.id)(a.value)};return e=d.axis_rotated?e.x0(i).x1(j).y(h):e.x(h).y0(d.area_above?0:i).y1(j),d.line_connectNull||(e=e.defined(function(a){return null!==a.value})),function(a){var b,f=d.line_connectNull?c.filterRemoveNull(a.values):a.values,g=0,h=0;return c.isAreaType(a)?(c.isStepType(a)&&(f=c.convertValuesToStep(f)),b=e.interpolate(c.getInterpolate(a))(f)):(f[0]&&(g=c.x(f[0].x),h=c.getYScale(a.id)(f[0].value)),b=d.axis_rotated?"M "+h+" "+g:"M "+g+" "+h),b?b:"M 0 0"}},i.getAreaBaseValue=function(){return 0},i.generateGetAreaPoints=function(a,b){var c=this,d=c.config,e=a.__max__+1,f=c.getShapeX(0,e,a,!!b),g=c.getShapeY(!!b),h=c.getShapeOffset(c.isAreaType,a,!!b),i=b?c.getSubYScale:c.getYScale;return function(a,b){var e=i.call(c,a.id)(0),j=h(a,b)||e,k=f(a),l=g(a);return d.axis_rotated&&(0l||a.value<0&&l>e)&&(l=e),[[k,j],[k,l-(e-j)],[k,l-(e-j)],[k,j]]}},i.updateCircle=function(){var a=this;a.mainCircle=a.main.selectAll("."+l.circles).selectAll("."+l.circle).data(a.lineOrScatterData.bind(a)),a.mainCircle.enter().append("circle").attr("class",a.classCircle.bind(a)).attr("r",a.pointR.bind(a)).style("fill",a.color),a.mainCircle.style("opacity",a.initialOpacityForCircle.bind(a)),a.mainCircle.exit().remove()},i.redrawCircle=function(a,b,c){var d=this.main.selectAll("."+l.selectedCircle);return[(c?this.mainCircle.transition(Math.random().toString()):this.mainCircle).style("opacity",this.opacityForCircle.bind(this)).style("fill",this.color).attr("cx",a).attr("cy",b),(c?d.transition(Math.random().toString()):d).attr("cx",a).attr("cy",b)]},i.circleX=function(a){return a.x||0===a.x?this.x(a.x):null},i.updateCircleY=function(){var a,b,c=this;c.config.data_groups.length>0?(a=c.getShapeIndices(c.isLineType),b=c.generateGetLinePoints(a),c.circleY=function(a,c){return b(a,c)[0][1]}):c.circleY=function(a){return c.getYScale(a.id)(a.value)}},i.getCircles=function(a,b){var c=this;return(b?c.main.selectAll("."+l.circles+c.getTargetSelectorSuffix(b)):c.main).selectAll("."+l.circle+(m(a)?"-"+a:""))},i.expandCircles=function(a,b,c){var d=this,e=d.pointExpandedR.bind(d);c&&d.unexpandCircles(),d.getCircles(a,b).classed(l.EXPANDED,!0).attr("r",e)},i.unexpandCircles=function(a){var b=this,c=b.pointR.bind(b);b.getCircles(a).filter(function(){return b.d3.select(this).classed(l.EXPANDED)}).classed(l.EXPANDED,!1).attr("r",c)},i.pointR=function(a){var b=this,c=b.config;return b.isStepType(a)?0:n(c.point_r)?c.point_r(a):c.point_r; +},i.pointExpandedR=function(a){var b=this,c=b.config;return c.point_focus_expand_enabled?c.point_focus_expand_r?c.point_focus_expand_r:1.75*b.pointR(a):b.pointR(a)},i.pointSelectR=function(a){var b=this,c=b.config;return n(c.point_select_r)?c.point_select_r(a):c.point_select_r?c.point_select_r:4*b.pointR(a)},i.isWithinCircle=function(a,b){var c=this.d3,d=c.mouse(a),e=c.select(a),f=+e.attr("cx"),g=+e.attr("cy");return Math.sqrt(Math.pow(f-d[0],2)+Math.pow(g-d[1],2))d.bar_width_max?d.bar_width_max:e},i.getBars=function(a,b){var c=this;return(b?c.main.selectAll("."+l.bars+c.getTargetSelectorSuffix(b)):c.main).selectAll("."+l.bar+(m(a)?"-"+a:""))},i.expandBars=function(a,b,c){var d=this;c&&d.unexpandBars(),d.getBars(a,b).classed(l.EXPANDED,!0)},i.unexpandBars=function(a){var b=this;b.getBars(a).classed(l.EXPANDED,!1)},i.generateDrawBar=function(a,b){var c=this,d=c.config,e=c.generateGetBarPoints(a,b);return function(a,b){var c=e(a,b),f=d.axis_rotated?1:0,g=d.axis_rotated?0:1,h="M "+c[0][f]+","+c[0][g]+" L"+c[1][f]+","+c[1][g]+" L"+c[2][f]+","+c[2][g]+" L"+c[3][f]+","+c[3][g]+" z";return h}},i.generateGetBarPoints=function(a,b){var c=this,d=b?c.subXAxis:c.xAxis,e=a.__max__+1,f=c.getBarW(d,e),g=c.getShapeX(f,e,a,!!b),h=c.getShapeY(!!b),i=c.getShapeOffset(c.isBarType,a,!!b),j=b?c.getSubYScale:c.getYScale;return function(a,b){var d=j.call(c,a.id)(0),e=i(a,b)||d,k=g(a),l=h(a);return c.config.axis_rotated&&(0l||a.value<0&&l>d)&&(l=d),[[k,e],[k,l-(d-e)],[k+f,l-(d-e)],[k+f,e]]}},i.isWithinBar=function(a){var b=this.d3.mouse(a),c=a.getBoundingClientRect(),d=a.pathSegList.getItem(0),e=a.pathSegList.getItem(1),f=Math.min(d.x,e.x),g=Math.min(d.y,e.y),h=c.width,i=c.height,j=2,k=f-j,l=f+h+j,m=g+i+j,n=g-j;return kf.width?d=f.width-g.width:0>d&&(d=4)),d},i.getYForText=function(a,b,c){var d,e=this,f=c.getBoundingClientRect();return e.config.axis_rotated?d=(a[0][0]+a[2][0]+.6*f.height)/2:(d=a[2][1],b.value<0||0===b.value&&!e.hasPositiveValue?(d+=f.height,e.isBarType(b)&&e.isSafari()?d-=3:!e.isBarType(b)&&e.isChrome()&&(d+=3)):d+=e.isBarType(b)?-3:-6),null!==b.value||e.config.axis_rotated||(dthis.height&&(d=this.height-4)),d},i.setTargetType=function(a,b){var c=this,d=c.config;c.mapToTargetIds(a).forEach(function(a){c.withoutFadeIn[a]=b===d.data_types[a],d.data_types[a]=b}),a||(d.data_type=b)},i.hasType=function(a,b){var c=this,d=c.config.data_types,e=!1;return b=b||c.data.targets,b&&b.length?b.forEach(function(b){var c=d[b.id];(c&&c.indexOf(a)>=0||!c&&"line"===a)&&(e=!0)}):Object.keys(d).length?Object.keys(d).forEach(function(b){d[b]===a&&(e=!0)}):e=c.config.data_type===a,e},i.hasArcType=function(a){return this.hasType("pie",a)||this.hasType("donut",a)||this.hasType("gauge",a)},i.isLineType=function(a){var b=this.config,c=o(a)?a:a.id;return!b.data_types[c]||["line","spline","area","area-spline","step","area-step"].indexOf(b.data_types[c])>=0},i.isStepType=function(a){var b=o(a)?a:a.id;return["step","area-step"].indexOf(this.config.data_types[b])>=0},i.isSplineType=function(a){var b=o(a)?a:a.id;return["spline","area-spline"].indexOf(this.config.data_types[b])>=0},i.isAreaType=function(a){var b=o(a)?a:a.id;return["area","area-spline","area-step"].indexOf(this.config.data_types[b])>=0},i.isBarType=function(a){var b=o(a)?a:a.id;return"bar"===this.config.data_types[b]},i.isScatterType=function(a){var b=o(a)?a:a.id;return"scatter"===this.config.data_types[b]},i.isPieType=function(a){var b=o(a)?a:a.id;return"pie"===this.config.data_types[b]},i.isGaugeType=function(a){var b=o(a)?a:a.id;return"gauge"===this.config.data_types[b]},i.isDonutType=function(a){var b=o(a)?a:a.id;return"donut"===this.config.data_types[b]},i.isArcType=function(a){return this.isPieType(a)||this.isDonutType(a)||this.isGaugeType(a)},i.lineData=function(a){return this.isLineType(a)?[a]:[]},i.arcData=function(a){return this.isArcType(a.data)?[a]:[]},i.barData=function(a){return this.isBarType(a)?a.values:[]},i.lineOrScatterData=function(a){return this.isLineType(a)||this.isScatterType(a)?a.values:[]},i.barOrLineData=function(a){return this.isBarType(a)||this.isLineType(a)?a.values:[]},i.isInterpolationType=function(a){return["linear","linear-closed","basis","basis-open","basis-closed","bundle","cardinal","cardinal-open","cardinal-closed","monotone"].indexOf(a)>=0},i.initGrid=function(){var a=this,b=a.config,c=a.d3;a.grid=a.main.append("g").attr("clip-path",a.clipPathForGrid).attr("class",l.grid),b.grid_x_show&&a.grid.append("g").attr("class",l.xgrids),b.grid_y_show&&a.grid.append("g").attr("class",l.ygrids),b.grid_focus_show&&a.grid.append("g").attr("class",l.xgridFocus).append("line").attr("class",l.xgridFocus),a.xgrid=c.selectAll([]),b.grid_lines_front||a.initGridLines()},i.initGridLines=function(){var a=this,b=a.d3;a.gridLines=a.main.append("g").attr("clip-path",a.clipPathForGrid).attr("class",l.grid+" "+l.gridLines),a.gridLines.append("g").attr("class",l.xgridLines),a.gridLines.append("g").attr("class",l.ygridLines),a.xgridLines=b.selectAll([])},i.updateXGrid=function(a){var b=this,c=b.config,d=b.d3,e=b.generateGridData(c.grid_x_type,b.x),f=b.isCategorized()?b.xAxis.tickOffset():0;b.xgridAttr=c.axis_rotated?{x1:0,x2:b.width,y1:function(a){return b.x(a)-f},y2:function(a){return b.x(a)-f}}:{x1:function(a){return b.x(a)+f},x2:function(a){return b.x(a)+f},y1:0,y2:b.height},b.xgrid=b.main.select("."+l.xgrids).selectAll("."+l.xgrid).data(e),b.xgrid.enter().append("line").attr("class",l.xgrid),a||b.xgrid.attr(b.xgridAttr).style("opacity",function(){return+d.select(this).attr(c.axis_rotated?"y1":"x1")===(c.axis_rotated?b.height:0)?0:1}),b.xgrid.exit().remove()},i.updateYGrid=function(){var a=this,b=a.config,c=a.yAxis.tickValues()||a.y.ticks(b.grid_y_ticks);a.ygrid=a.main.select("."+l.ygrids).selectAll("."+l.ygrid).data(c),a.ygrid.enter().append("line").attr("class",l.ygrid),a.ygrid.attr("x1",b.axis_rotated?a.y:0).attr("x2",b.axis_rotated?a.y:a.width).attr("y1",b.axis_rotated?0:a.y).attr("y2",b.axis_rotated?a.height:a.y),a.ygrid.exit().remove(),a.smoothLines(a.ygrid,"grid")},i.gridTextAnchor=function(a){return a.position?a.position:"end"},i.gridTextDx=function(a){return"start"===a.position?4:"middle"===a.position?0:-4},i.xGridTextX=function(a){return"start"===a.position?-this.height:"middle"===a.position?-this.height/2:0},i.yGridTextX=function(a){return"start"===a.position?0:"middle"===a.position?this.width/2:this.width},i.updateGrid=function(a){var b,c,d,e=this,f=e.main,g=e.config;e.grid.style("visibility",e.hasArcType()?"hidden":"visible"),f.select("line."+l.xgridFocus).style("visibility","hidden"),g.grid_x_show&&e.updateXGrid(),e.xgridLines=f.select("."+l.xgridLines).selectAll("."+l.xgridLine).data(g.grid_x_lines),b=e.xgridLines.enter().append("g").attr("class",function(a){return l.xgridLine+(a["class"]?" "+a["class"]:"")}),b.append("line").style("opacity",0),b.append("text").attr("text-anchor",e.gridTextAnchor).attr("transform",g.axis_rotated?"":"rotate(-90)").attr("dx",e.gridTextDx).attr("dy",-5).style("opacity",0),e.xgridLines.exit().transition().duration(a).style("opacity",0).remove(),g.grid_y_show&&e.updateYGrid(),e.ygridLines=f.select("."+l.ygridLines).selectAll("."+l.ygridLine).data(g.grid_y_lines),c=e.ygridLines.enter().append("g").attr("class",function(a){return l.ygridLine+(a["class"]?" "+a["class"]:"")}),c.append("line").style("opacity",0),c.append("text").attr("text-anchor",e.gridTextAnchor).attr("transform",g.axis_rotated?"rotate(-90)":"").attr("dx",e.gridTextDx).attr("dy",-5).style("opacity",0),d=e.yv.bind(e),e.ygridLines.select("line").transition().duration(a).attr("x1",g.axis_rotated?d:0).attr("x2",g.axis_rotated?d:e.width).attr("y1",g.axis_rotated?0:d).attr("y2",g.axis_rotated?e.height:d).style("opacity",1),e.ygridLines.select("text").transition().duration(a).attr("x",g.axis_rotated?e.xGridTextX.bind(e):e.yGridTextX.bind(e)).attr("y",d).text(function(a){return a.text}).style("opacity",1),e.ygridLines.exit().transition().duration(a).style("opacity",0).remove()},i.redrawGrid=function(a){var b=this,c=b.config,d=b.xv.bind(b),e=b.xgridLines.select("line"),f=b.xgridLines.select("text");return[(a?e.transition():e).attr("x1",c.axis_rotated?0:d).attr("x2",c.axis_rotated?b.width:d).attr("y1",c.axis_rotated?d:0).attr("y2",c.axis_rotated?d:b.height).style("opacity",1),(a?f.transition():f).attr("x",c.axis_rotated?b.yGridTextX.bind(b):b.xGridTextX.bind(b)).attr("y",d).text(function(a){return a.text}).style("opacity",1)]},i.showXGridFocus=function(a){var b=this,c=b.config,d=a.filter(function(a){return a&&m(a.value)}),e=b.main.selectAll("line."+l.xgridFocus),f=b.xx.bind(b);c.tooltip_show&&(b.hasType("scatter")||b.hasArcType()||(e.style("visibility","visible").data([d[0]]).attr(c.axis_rotated?"y1":"x1",f).attr(c.axis_rotated?"y2":"x2",f),b.smoothLines(e,"grid")))},i.hideXGridFocus=function(){this.main.select("line."+l.xgridFocus).style("visibility","hidden")},i.updateXgridFocus=function(){var a=this,b=a.config;a.main.select("line."+l.xgridFocus).attr("x1",b.axis_rotated?0:-10).attr("x2",b.axis_rotated?a.width:-10).attr("y1",b.axis_rotated?-10:0).attr("y2",b.axis_rotated?-10:a.height)},i.generateGridData=function(a,b){var c,d,e,f,g=this,h=[],i=g.main.select("."+l.axisX).selectAll(".tick").size();if("year"===a)for(c=g.getXDomain(),d=c[0].getFullYear(),e=c[1].getFullYear(),f=d;e>=f;f++)h.push(new Date(f+"-01-01 00:00:00"));else h=b.ticks(10),h.length>i&&(h=h.filter(function(a){return(""+a).indexOf(".")<0}));return h},i.getGridFilterToRemove=function(a){return a?function(b){var c=!1;return[].concat(a).forEach(function(a){("value"in a&&b.value===a.value||"class"in a&&b["class"]===a["class"])&&(c=!0)}),c}:function(){return!0}},i.removeGridLines=function(a,b){var c=this,d=c.config,e=c.getGridFilterToRemove(a),f=function(a){return!e(a)},g=b?l.xgridLines:l.ygridLines,h=b?l.xgridLine:l.ygridLine;c.main.select("."+g).selectAll("."+h).filter(e).transition().duration(d.transition_duration).style("opacity",0).remove(),b?d.grid_x_lines=d.grid_x_lines.filter(f):d.grid_y_lines=d.grid_y_lines.filter(f)},i.initTooltip=function(){var a,b=this,c=b.config;if(b.tooltip=b.selectChart.style("position","relative").append("div").attr("class",l.tooltipContainer).style("position","absolute").style("pointer-events","none").style("display","none"),c.tooltip_init_show){if(b.isTimeSeries()&&o(c.tooltip_init_x)){for(c.tooltip_init_x=b.parseDate(c.tooltip_init_x),a=0;a0&&d>0&&(c=a?q.indexOf(a.id):null,d=b?q.indexOf(b.id):null),p?c-d:d-c})}for(f=0;f"+(g||0===g?""+g+"":"")),h=y(o(a[f].value,a[f].ratio,a[f].id,a[f].index,a)),void 0!==h)){if(null===a[f].name)continue;i=y(n(a[f].name,a[f].ratio,a[f].id,a[f].index)),j=k.levelColor?k.levelColor(a[f].value):d(a[f].id),e+="",e+=""+i+"",e+=""+h+"",e+=""}return e+""},i.tooltipPosition=function(a,b,c,d){var e,f,g,h,i,j=this,k=j.config,l=j.d3,m=j.hasArcType(),n=l.mouse(d);return m?(f=(j.width-(j.isLegendRight?j.getLegendWidth():0))/2+n[0],h=j.height/2+n[1]+20):(e=j.getSvgLeft(!0),k.axis_rotated?(f=e+n[0]+100,g=f+b,i=j.currentWidth-j.getCurrentPaddingRight(),h=j.x(a[0].x)+20):(f=e+j.getCurrentPaddingLeft(!0)+j.x(a[0].x)+20,g=f+b,i=e+j.currentWidth-j.getCurrentPaddingRight(),h=n[1]+15),g>i&&(f-=g-i+20),h+c>j.currentHeight&&(h-=c+30)),0>h&&(h=0),{top:h,left:f}},i.showTooltip=function(a,b){var c,d,e,f=this,g=f.config,h=f.hasArcType(),j=a.filter(function(a){return a&&m(a.value)}),k=g.tooltip_position||i.tooltipPosition;0!==j.length&&g.tooltip_show&&(f.tooltip.html(g.tooltip_contents.call(f,a,f.axis.getXAxisTickFormat(),f.getYFormat(h),f.color)).style("display","block"),c=f.tooltip.property("offsetWidth"),d=f.tooltip.property("offsetHeight"),e=k.call(this,j,c,d,b),f.tooltip.style("top",e.top+"px").style("left",e.left+"px"))},i.hideTooltip=function(){this.tooltip.style("display","none")},i.initLegend=function(){var a=this;return a.legendItemTextBox={},a.legendHasRendered=!1,a.legend=a.svg.append("g").attr("transform",a.getTranslate("legend")),a.config.legend_show?void a.updateLegendWithDefaults():(a.legend.style("visibility","hidden"),void(a.hiddenLegendIds=a.mapToIds(a.data.targets)))},i.updateLegendWithDefaults=function(){var a=this;a.updateLegend(a.mapToIds(a.data.targets),{withTransform:!1,withTransitionForTransform:!1,withTransition:!1})},i.updateSizeForLegend=function(a,b){var c=this,d=c.config,e={top:c.isLegendTop?c.getCurrentPaddingTop()+d.legend_inset_y+5.5:c.currentHeight-a-c.getCurrentPaddingBottom()-d.legend_inset_y,left:c.isLegendLeft?c.getCurrentPaddingLeft()+d.legend_inset_x+.5:c.currentWidth-b-c.getCurrentPaddingRight()-d.legend_inset_x+.5};c.margin3={top:c.isLegendRight?0:c.isLegendInset?e.top:c.currentHeight-a,right:NaN,bottom:0,left:c.isLegendRight?c.currentWidth-b:c.isLegendInset?e.left:0}},i.transformLegend=function(a){var b=this;(a?b.legend.transition():b.legend).attr("transform",b.getTranslate("legend"))},i.updateLegendStep=function(a){this.legendStep=a},i.updateLegendItemWidth=function(a){this.legendItemWidth=a},i.updateLegendItemHeight=function(a){this.legendItemHeight=a},i.getLegendWidth=function(){var a=this;return a.config.legend_show?a.isLegendRight||a.isLegendInset?a.legendItemWidth*(a.legendStep+1):a.currentWidth:0},i.getLegendHeight=function(){var a=this,b=0;return a.config.legend_show&&(b=a.isLegendRight?a.currentHeight:Math.max(20,a.legendItemHeight)*(a.legendStep+1)),b},i.opacityForLegend=function(a){return a.classed(l.legendItemHidden)?null:1},i.opacityForUnfocusedLegend=function(a){return a.classed(l.legendItemHidden)?null:.3},i.toggleFocusLegend=function(a,b){var c=this;a=c.mapToTargetIds(a),c.legend.selectAll("."+l.legendItem).filter(function(b){return a.indexOf(b)>=0}).classed(l.legendItemFocused,b).transition().duration(100).style("opacity",function(){var a=b?c.opacityForLegend:c.opacityForUnfocusedLegend;return a.call(c,c.d3.select(this))})},i.revertLegend=function(){var a=this,b=a.d3;a.legend.selectAll("."+l.legendItem).classed(l.legendItemFocused,!1).transition().duration(100).style("opacity",function(){return a.opacityForLegend(b.select(this))})},i.showLegend=function(a){var b=this,c=b.config;c.legend_show||(c.legend_show=!0,b.legend.style("visibility","visible"),b.legendHasRendered||b.updateLegendWithDefaults()),b.removeHiddenLegendIds(a),b.legend.selectAll(b.selectorLegends(a)).style("visibility","visible").transition().style("opacity",function(){return b.opacityForLegend(b.d3.select(this))})},i.hideLegend=function(a){var b=this,c=b.config;c.legend_show&&u(a)&&(c.legend_show=!1,b.legend.style("visibility","hidden")),b.addHiddenLegendIds(a),b.legend.selectAll(b.selectorLegends(a)).style("opacity",0).style("visibility","hidden")},i.clearLegendItemTextBoxCache=function(){this.legendItemTextBox={}},i.updateLegend=function(a,b,c){function d(a,b){return y.legendItemTextBox[b]||(y.legendItemTextBox[b]=y.getTextRect(a.textContent,l.legendItem,a)),y.legendItemTextBox[b]}function e(b,c,e){function f(a,b){b||(g=(o-G-n)/2,E>g&&(g=(o-n)/2,G=0,M++)),L[a]=M,K[M]=y.isLegendInset?10:g,H[a]=G,G+=n}var g,h,i=0===e,j=e===a.length-1,k=d(b,c),l=k.width+F+(!j||y.isLegendRight||y.isLegendInset?B:0)+z.legend_padding,m=k.height+A,n=y.isLegendRight||y.isLegendInset?m:l,o=y.isLegendRight||y.isLegendInset?y.getLegendHeight():y.getLegendWidth();return i&&(G=0,M=0,C=0,D=0),z.legend_show&&!y.isLegendToShow(c)?void(I[c]=J[c]=L[c]=H[c]=0):(I[c]=l,J[c]=m,(!C||l>=C)&&(C=l),(!D||m>=D)&&(D=m),h=y.isLegendRight||y.isLegendInset?D:C,void(z.legend_equally?(Object.keys(I).forEach(function(a){I[a]=C}),Object.keys(J).forEach(function(a){J[a]=D}),g=(o-h*a.length)/2,E>g?(G=0,M=0,a.forEach(function(a){f(a)})):f(c,!0)):f(c)))}var f,g,h,i,j,k,m,n,o,p,r,s,t,u,v,x,y=this,z=y.config,A=4,B=10,C=0,D=0,E=10,F=z.legend_item_tile_width+5,G=0,H={},I={},J={},K=[0],L={},M=0;a=a.filter(function(a){return!q(z.data_names[a])||null!==z.data_names[a]}),b=b||{},r=w(b,"withTransition",!0),s=w(b,"withTransitionForTransform",!0),y.isLegendInset&&(M=z.legend_inset_step?z.legend_inset_step:a.length,y.updateLegendStep(M)),y.isLegendRight?(f=function(a){return C*L[a]},i=function(a){return K[L[a]]+H[a]}):y.isLegendInset?(f=function(a){return C*L[a]+10},i=function(a){return K[L[a]]+H[a]}):(f=function(a){return K[L[a]]+H[a]},i=function(a){return D*L[a]}),g=function(a,b){return f(a,b)+4+z.legend_item_tile_width},j=function(a,b){return i(a,b)+9},h=function(a,b){return f(a,b)},k=function(a,b){return i(a,b)-5},m=function(a,b){return f(a,b)-2},n=function(a,b){return f(a,b)-2+z.legend_item_tile_width},o=function(a,b){return i(a,b)+4},p=y.legend.selectAll("."+l.legendItem).data(a).enter().append("g").attr("class",function(a){return y.generateClass(l.legendItem,a)}).style("visibility",function(a){return y.isLegendToShow(a)?"visible":"hidden"}).style("cursor","pointer").on("click",function(a){z.legend_item_onclick?z.legend_item_onclick.call(y,a):y.d3.event.altKey?(y.api.hide(),y.api.show(a)):(y.api.toggle(a),y.isTargetToShow(a)?y.api.focus(a):y.api.revert())}).on("mouseover",function(a){z.legend_item_onmouseover?z.legend_item_onmouseover.call(y,a):(y.d3.select(this).classed(l.legendItemFocused,!0),!y.transiting&&y.isTargetToShow(a)&&y.api.focus(a))}).on("mouseout",function(a){z.legend_item_onmouseout?z.legend_item_onmouseout.call(y,a):(y.d3.select(this).classed(l.legendItemFocused,!1),y.api.revert())}),p.append("text").text(function(a){return q(z.data_names[a])?z.data_names[a]:a}).each(function(a,b){e(this,a,b)}).style("pointer-events","none").attr("x",y.isLegendRight||y.isLegendInset?g:-200).attr("y",y.isLegendRight||y.isLegendInset?-200:j),p.append("rect").attr("class",l.legendItemEvent).style("fill-opacity",0).attr("x",y.isLegendRight||y.isLegendInset?h:-200).attr("y",y.isLegendRight||y.isLegendInset?-200:k),p.append("line").attr("class",l.legendItemTile).style("stroke",y.color).style("pointer-events","none").attr("x1",y.isLegendRight||y.isLegendInset?m:-200).attr("y1",y.isLegendRight||y.isLegendInset?-200:o).attr("x2",y.isLegendRight||y.isLegendInset?n:-200).attr("y2",y.isLegendRight||y.isLegendInset?-200:o).attr("stroke-width",z.legend_item_tile_height),x=y.legend.select("."+l.legendBackground+" rect"),y.isLegendInset&&C>0&&0===x.size()&&(x=y.legend.insert("g","."+l.legendItem).attr("class",l.legendBackground).append("rect")),t=y.legend.selectAll("text").data(a).text(function(a){return q(z.data_names[a])?z.data_names[a]:a}).each(function(a,b){e(this,a,b)}),(r?t.transition():t).attr("x",g).attr("y",j),u=y.legend.selectAll("rect."+l.legendItemEvent).data(a),(r?u.transition():u).attr("width",function(a){return I[a]}).attr("height",function(a){return J[a]}).attr("x",h).attr("y",k),v=y.legend.selectAll("line."+l.legendItemTile).data(a),(r?v.transition():v).style("stroke",y.color).attr("x1",m).attr("y1",o).attr("x2",n).attr("y2",o),x&&(r?x.transition():x).attr("height",y.getLegendHeight()-12).attr("width",C*(M+1)+10),y.legend.selectAll("."+l.legendItem).classed(l.legendItemHidden,function(a){return!y.isTargetToShow(a)}),y.updateLegendItemWidth(C),y.updateLegendItemHeight(D),y.updateLegendStep(M),y.updateSizes(),y.updateScales(),y.updateSvgSize(),y.transformAll(s,c),y.legendHasRendered=!0},i.initTitle=function(){var a=this;a.title=a.svg.append("text").text(a.config.title_text).attr("class",a.CLASS.title)},i.redrawTitle=function(){var a=this;a.title.attr("x",a.xForTitle.bind(a)).attr("y",a.yForTitle.bind(a))},i.xForTitle=function(){var a,b=this,c=b.config,d=c.title_position||"left";return a=d.indexOf("right")>=0?b.currentWidth-b.getTextRect(b.title.node().textContent,b.CLASS.title,b.title.node()).width-c.title_padding.right:d.indexOf("center")>=0?(b.currentWidth-b.getTextRect(b.title.node().textContent,b.CLASS.title,b.title.node()).width)/2:c.title_padding.left},i.yForTitle=function(){var a=this;return a.config.title_padding.top+a.getTextRect(a.title.node().textContent,a.CLASS.title,a.title.node()).height},i.getTitlePadding=function(){var a=this;return a.yForTitle()+a.config.title_padding.bottom},c(b,f),f.prototype.init=function(){var a=this.owner,b=a.config,c=a.main;a.axes.x=c.append("g").attr("class",l.axis+" "+l.axisX).attr("clip-path",a.clipPathForXAxis).attr("transform",a.getTranslate("x")).style("visibility",b.axis_x_show?"visible":"hidden"),a.axes.x.append("text").attr("class",l.axisXLabel).attr("transform",b.axis_rotated?"rotate(-90)":"").style("text-anchor",this.textAnchorForXAxisLabel.bind(this)),a.axes.y=c.append("g").attr("class",l.axis+" "+l.axisY).attr("clip-path",b.axis_y_inner?"":a.clipPathForYAxis).attr("transform",a.getTranslate("y")).style("visibility",b.axis_y_show?"visible":"hidden"),a.axes.y.append("text").attr("class",l.axisYLabel).attr("transform",b.axis_rotated?"":"rotate(-90)").style("text-anchor",this.textAnchorForYAxisLabel.bind(this)),a.axes.y2=c.append("g").attr("class",l.axis+" "+l.axisY2).attr("transform",a.getTranslate("y2")).style("visibility",b.axis_y2_show?"visible":"hidden"),a.axes.y2.append("text").attr("class",l.axisY2Label).attr("transform",b.axis_rotated?"":"rotate(-90)").style("text-anchor",this.textAnchorForY2AxisLabel.bind(this))},f.prototype.getXAxis=function(a,b,c,d,e,f,h){var i=this.owner,j=i.config,k={isCategory:i.isCategorized(),withOuterTick:e,tickMultiline:j.axis_x_tick_multiline,tickWidth:j.axis_x_tick_width,tickTextRotate:h?0:j.axis_x_tick_rotate,withoutTransition:f},l=g(i.d3,k).scale(a).orient(b);return i.isTimeSeries()&&d&&"function"!=typeof d&&(d=d.map(function(a){return i.parseDate(a)})),l.tickFormat(c).tickValues(d),i.isCategorized()&&(l.tickCentered(j.axis_x_tick_centered),u(j.axis_x_tick_culling)&&(j.axis_x_tick_culling=!1)),l},f.prototype.updateXAxisTickValues=function(a,b){var c,d=this.owner,e=d.config;return(e.axis_x_tick_fit||e.axis_x_tick_count)&&(c=this.generateTickValues(d.mapTargetsToUniqueXs(a),e.axis_x_tick_count,d.isTimeSeries())),b?b.tickValues(c):(d.xAxis.tickValues(c),d.subXAxis.tickValues(c)),c},f.prototype.getYAxis=function(a,b,c,d,e,f,h){var i=this.owner,j=i.config,k={withOuterTick:e,withoutTransition:f,tickTextRotate:h?0:j.axis_y_tick_rotate},l=g(i.d3,k).scale(a).orient(b).tickFormat(c);return i.isTimeSeriesY()?l.ticks(i.d3.time[j.axis_y_tick_time_value],j.axis_y_tick_time_interval):l.tickValues(d),l},f.prototype.getId=function(a){var b=this.owner.config;return a in b.data_axes?b.data_axes[a]:"y"},f.prototype.getXAxisTickFormat=function(){var a=this.owner,b=a.config,c=a.isTimeSeries()?a.defaultAxisTimeFormat:a.isCategorized()?a.categoryName:function(a){return 0>a?a.toFixed(0):a};return b.axis_x_tick_format&&(n(b.axis_x_tick_format)?c=b.axis_x_tick_format:a.isTimeSeries()&&(c=function(c){return c?a.axisTimeFormat(b.axis_x_tick_format)(c):""})),n(c)?function(b){return c.call(a,b)}:c},f.prototype.getTickValues=function(a,b){return a?a:b?b.tickValues():void 0},f.prototype.getXAxisTickValues=function(){return this.getTickValues(this.owner.config.axis_x_tick_values,this.owner.xAxis)},f.prototype.getYAxisTickValues=function(){return this.getTickValues(this.owner.config.axis_y_tick_values,this.owner.yAxis)},f.prototype.getY2AxisTickValues=function(){return this.getTickValues(this.owner.config.axis_y2_tick_values,this.owner.y2Axis)},f.prototype.getLabelOptionByAxisId=function(a){var b,c=this.owner,d=c.config;return"y"===a?b=d.axis_y_label:"y2"===a?b=d.axis_y2_label:"x"===a&&(b=d.axis_x_label),b},f.prototype.getLabelText=function(a){var b=this.getLabelOptionByAxisId(a);return o(b)?b:b?b.text:null},f.prototype.setLabelText=function(a,b){var c=this.owner,d=c.config,e=this.getLabelOptionByAxisId(a);o(e)?"y"===a?d.axis_y_label=b:"y2"===a?d.axis_y2_label=b:"x"===a&&(d.axis_x_label=b):e&&(e.text=b)},f.prototype.getLabelPosition=function(a,b){var c=this.getLabelOptionByAxisId(a),d=c&&"object"==typeof c&&c.position?c.position:b;return{isInner:d.indexOf("inner")>=0,isOuter:d.indexOf("outer")>=0,isLeft:d.indexOf("left")>=0,isCenter:d.indexOf("center")>=0,isRight:d.indexOf("right")>=0,isTop:d.indexOf("top")>=0,isMiddle:d.indexOf("middle")>=0,isBottom:d.indexOf("bottom")>=0}},f.prototype.getXAxisLabelPosition=function(){return this.getLabelPosition("x",this.owner.config.axis_rotated?"inner-top":"inner-right")},f.prototype.getYAxisLabelPosition=function(){return this.getLabelPosition("y",this.owner.config.axis_rotated?"inner-right":"inner-top")},f.prototype.getY2AxisLabelPosition=function(){return this.getLabelPosition("y2",this.owner.config.axis_rotated?"inner-right":"inner-top")},f.prototype.getLabelPositionById=function(a){return"y2"===a?this.getY2AxisLabelPosition():"y"===a?this.getYAxisLabelPosition():this.getXAxisLabelPosition()},f.prototype.textForXAxisLabel=function(){return this.getLabelText("x")},f.prototype.textForYAxisLabel=function(){return this.getLabelText("y")},f.prototype.textForY2AxisLabel=function(){return this.getLabelText("y2")},f.prototype.xForAxisLabel=function(a,b){var c=this.owner;return a?b.isLeft?0:b.isCenter?c.width/2:c.width:b.isBottom?-c.height:b.isMiddle?-c.height/2:0},f.prototype.dxForAxisLabel=function(a,b){return a?b.isLeft?"0.5em":b.isRight?"-0.5em":"0":b.isTop?"-0.5em":b.isBottom?"0.5em":"0"},f.prototype.textAnchorForAxisLabel=function(a,b){return a?b.isLeft?"start":b.isCenter?"middle":"end":b.isBottom?"start":b.isMiddle?"middle":"end"},f.prototype.xForXAxisLabel=function(){return this.xForAxisLabel(!this.owner.config.axis_rotated,this.getXAxisLabelPosition())},f.prototype.xForYAxisLabel=function(){return this.xForAxisLabel(this.owner.config.axis_rotated,this.getYAxisLabelPosition())},f.prototype.xForY2AxisLabel=function(){return this.xForAxisLabel(this.owner.config.axis_rotated,this.getY2AxisLabelPosition())},f.prototype.dxForXAxisLabel=function(){return this.dxForAxisLabel(!this.owner.config.axis_rotated,this.getXAxisLabelPosition())},f.prototype.dxForYAxisLabel=function(){return this.dxForAxisLabel(this.owner.config.axis_rotated,this.getYAxisLabelPosition())},f.prototype.dxForY2AxisLabel=function(){return this.dxForAxisLabel(this.owner.config.axis_rotated,this.getY2AxisLabelPosition())},f.prototype.dyForXAxisLabel=function(){var a=this.owner,b=a.config,c=this.getXAxisLabelPosition();return b.axis_rotated?c.isInner?"1.2em":-25-this.getMaxTickWidth("x"):c.isInner?"-0.5em":b.axis_x_height?b.axis_x_height-10:"3em"},f.prototype.dyForYAxisLabel=function(){var a=this.owner,b=this.getYAxisLabelPosition();return a.config.axis_rotated?b.isInner?"-0.5em":"3em":b.isInner?"1.2em":-10-(a.config.axis_y_inner?0:this.getMaxTickWidth("y")+10)},f.prototype.dyForY2AxisLabel=function(){var a=this.owner,b=this.getY2AxisLabelPosition();return a.config.axis_rotated?b.isInner?"1.2em":"-2.2em":b.isInner?"-0.5em":15+(a.config.axis_y2_inner?0:this.getMaxTickWidth("y2")+15)},f.prototype.textAnchorForXAxisLabel=function(){var a=this.owner;return this.textAnchorForAxisLabel(!a.config.axis_rotated,this.getXAxisLabelPosition())},f.prototype.textAnchorForYAxisLabel=function(){var a=this.owner;return this.textAnchorForAxisLabel(a.config.axis_rotated,this.getYAxisLabelPosition())},f.prototype.textAnchorForY2AxisLabel=function(){var a=this.owner;return this.textAnchorForAxisLabel(a.config.axis_rotated,this.getY2AxisLabelPosition())},f.prototype.getMaxTickWidth=function(a,b){var c,d,e,f,g,h=this.owner,i=h.config,j=0;return b&&h.currentMaxTickWidths[a]?h.currentMaxTickWidths[a]:(h.svg&&(c=h.filterTargetsToShow(h.data.targets),"y"===a?(d=h.y.copy().domain(h.getYDomain(c,"y")),e=this.getYAxis(d,h.yOrient,i.axis_y_tick_format,h.yAxisTickValues,!1,!0,!0)):"y2"===a?(d=h.y2.copy().domain(h.getYDomain(c,"y2")), +e=this.getYAxis(d,h.y2Orient,i.axis_y2_tick_format,h.y2AxisTickValues,!1,!0,!0)):(d=h.x.copy().domain(h.getXDomain(c)),e=this.getXAxis(d,h.xOrient,h.xAxisTickFormat,h.xAxisTickValues,!1,!0,!0),this.updateXAxisTickValues(c,e)),f=h.d3.select("body").append("div").classed("c3",!0),g=f.append("svg").style("visibility","hidden").style("position","fixed").style("top",0).style("left",0),g.append("g").call(e).each(function(){h.d3.select(this).selectAll("text").each(function(){var a=this.getBoundingClientRect();j=j?h.currentMaxTickWidths[a]:j,h.currentMaxTickWidths[a])},f.prototype.updateLabels=function(a){var b=this.owner,c=b.main.select("."+l.axisX+" ."+l.axisXLabel),d=b.main.select("."+l.axisY+" ."+l.axisYLabel),e=b.main.select("."+l.axisY2+" ."+l.axisY2Label);(a?c.transition():c).attr("x",this.xForXAxisLabel.bind(this)).attr("dx",this.dxForXAxisLabel.bind(this)).attr("dy",this.dyForXAxisLabel.bind(this)).text(this.textForXAxisLabel.bind(this)),(a?d.transition():d).attr("x",this.xForYAxisLabel.bind(this)).attr("dx",this.dxForYAxisLabel.bind(this)).attr("dy",this.dyForYAxisLabel.bind(this)).text(this.textForYAxisLabel.bind(this)),(a?e.transition():e).attr("x",this.xForY2AxisLabel.bind(this)).attr("dx",this.dxForY2AxisLabel.bind(this)).attr("dy",this.dyForY2AxisLabel.bind(this)).text(this.textForY2AxisLabel.bind(this))},f.prototype.getPadding=function(a,b,c,d){var e="number"==typeof a?a:a[b];return m(e)?"ratio"===a.unit?a[b]*d:this.convertPixelsToAxisPadding(e,d):c},f.prototype.convertPixelsToAxisPadding=function(a,b){var c=this.owner,d=c.config.axis_rotated?c.width:c.height;return b*(a/d)},f.prototype.generateTickValues=function(a,b,c){var d,e,f,g,h,i,j,k=a;if(b)if(d=n(b)?b():b,1===d)k=[a[0]];else if(2===d)k=[a[0],a[a.length-1]];else if(d>2){for(g=d-2,e=a[0],f=a[a.length-1],h=(f-e)/(g+1),k=[e],i=0;g>i;i++)j=+e+h*(i+1),k.push(c?new Date(j):j);k.push(f)}return c||(k=k.sort(function(a,b){return a-b})),k},f.prototype.generateTransitions=function(a){var b=this.owner,c=b.axes;return{axisX:a?c.x.transition().duration(a):c.x,axisY:a?c.y.transition().duration(a):c.y,axisY2:a?c.y2.transition().duration(a):c.y2,axisSubX:a?c.subx.transition().duration(a):c.subx}},f.prototype.redraw=function(a,b){var c=this.owner;c.axes.x.style("opacity",b?0:1),c.axes.y.style("opacity",b?0:1),c.axes.y2.style("opacity",b?0:1),c.axes.subx.style("opacity",b?0:1),a.axisX.call(c.xAxis),a.axisY.call(c.yAxis),a.axisY2.call(c.y2Axis),a.axisSubX.call(c.subXAxis)},i.getClipPath=function(b){var c=a.navigator.appVersion.toLowerCase().indexOf("msie 9.")>=0;return"url("+(c?"":document.URL.split("#")[0])+"#"+b+")"},i.appendClip=function(a,b){return a.append("clipPath").attr("id",b).append("rect")},i.getAxisClipX=function(a){var b=Math.max(30,this.margin.left);return a?-(1+b):-(b-1)},i.getAxisClipY=function(a){return a?-20:-this.margin.top},i.getXAxisClipX=function(){var a=this;return a.getAxisClipX(!a.config.axis_rotated)},i.getXAxisClipY=function(){var a=this;return a.getAxisClipY(!a.config.axis_rotated)},i.getYAxisClipX=function(){var a=this;return a.config.axis_y_inner?-1:a.getAxisClipX(a.config.axis_rotated)},i.getYAxisClipY=function(){var a=this;return a.getAxisClipY(a.config.axis_rotated)},i.getAxisClipWidth=function(a){var b=this,c=Math.max(30,b.margin.left),d=Math.max(30,b.margin.right);return a?b.width+2+c+d:b.margin.left+20},i.getAxisClipHeight=function(a){return(a?this.margin.bottom:this.margin.top+this.height)+20},i.getXAxisClipWidth=function(){var a=this;return a.getAxisClipWidth(!a.config.axis_rotated)},i.getXAxisClipHeight=function(){var a=this;return a.getAxisClipHeight(!a.config.axis_rotated)},i.getYAxisClipWidth=function(){var a=this;return a.getAxisClipWidth(a.config.axis_rotated)+(a.config.axis_y_inner?20:0)},i.getYAxisClipHeight=function(){var a=this;return a.getAxisClipHeight(a.config.axis_rotated)},i.initPie=function(){var a=this,b=a.d3,c=a.config;a.pie=b.layout.pie().value(function(a){return a.values.reduce(function(a,b){return a+b.value},0)}),c.data_order||a.pie.sort(null)},i.updateRadius=function(){var a=this,b=a.config,c=b.gauge_width||b.donut_width;a.radiusExpanded=Math.min(a.arcWidth,a.arcHeight)/2,a.radius=.95*a.radiusExpanded,a.innerRadiusRatio=c?(a.radius-c)/a.radius:.6,a.innerRadius=a.hasType("donut")||a.hasType("gauge")?a.radius*a.innerRadiusRatio:0},i.updateArc=function(){var a=this;a.svgArc=a.getSvgArc(),a.svgArcExpanded=a.getSvgArcExpanded(),a.svgArcExpandedSub=a.getSvgArcExpanded(.98)},i.updateAngle=function(a){var b,c,d,e,f=this,g=f.config,h=!1,i=0;return g?(f.pie(f.filterTargetsToShow(f.data.targets)).forEach(function(b){h||b.data.id!==a.data.id||(h=!0,a=b,a.index=i),i++}),isNaN(a.startAngle)&&(a.startAngle=0),isNaN(a.endAngle)&&(a.endAngle=a.startAngle),f.isGaugeType(a.data)&&(b=g.gauge_min,c=g.gauge_max,d=Math.PI*(g.gauge_fullCircle?2:1)/(c-b),e=a.value.375?1.175-36/g.radius:.8)*g.radius/e:0,j="translate("+c*f+","+d*f+")"),j},i.getArcRatio=function(a){var b=this,c=b.config,d=Math.PI*(b.hasType("gauge")&&!c.gauge_fullCircle?1:2);return a?(a.endAngle-a.startAngle)/d:null},i.convertToArcData=function(a){return this.addName({id:a.data.id,value:a.value,ratio:this.getArcRatio(a),index:a.index})},i.textForArcLabel=function(a){var b,c,d,e,f,g=this;return g.shouldShowArcLabel()?(b=g.updateAngle(a),c=b?b.value:null,d=g.getArcRatio(b),e=a.data.id,g.hasType("gauge")||g.meetsArcLabelThreshold(d)?(f=g.getArcLabelFormat(),f?f(c,d,e):g.defaultArcValueFormat(c,d)):""):""},i.expandArc=function(b){var c,d=this;return d.transiting?void(c=a.setInterval(function(){d.transiting||(a.clearInterval(c),d.legend.selectAll(".c3-legend-item-focused").size()>0&&d.expandArc(b))},10)):(b=d.mapToTargetIds(b),void d.svg.selectAll(d.selectorTargets(b,"."+l.chartArc)).each(function(a){d.shouldExpand(a.data.id)&&d.d3.select(this).selectAll("path").transition().duration(d.expandDuration(a.data.id)).attr("d",d.svgArcExpanded).transition().duration(2*d.expandDuration(a.data.id)).attr("d",d.svgArcExpandedSub).each(function(a){d.isDonutType(a.data)})}))},i.unexpandArc=function(a){var b=this;b.transiting||(a=b.mapToTargetIds(a),b.svg.selectAll(b.selectorTargets(a,"."+l.chartArc)).selectAll("path").transition().duration(function(a){return b.expandDuration(a.data.id)}).attr("d",b.svgArc),b.svg.selectAll("."+l.arc).style("opacity",1))},i.expandDuration=function(a){var b=this,c=b.config;return b.isDonutType(a)?c.donut_expand_duration:b.isGaugeType(a)?c.gauge_expand_duration:b.isPieType(a)?c.pie_expand_duration:50},i.shouldExpand=function(a){var b=this,c=b.config;return b.isDonutType(a)&&c.donut_expand||b.isGaugeType(a)&&c.gauge_expand||b.isPieType(a)&&c.pie_expand},i.shouldShowArcLabel=function(){var a=this,b=a.config,c=!0;return a.hasType("donut")?c=b.donut_label_show:a.hasType("pie")&&(c=b.pie_label_show),c},i.meetsArcLabelThreshold=function(a){var b=this,c=b.config,d=b.hasType("donut")?c.donut_label_threshold:c.pie_label_threshold;return a>=d},i.getArcLabelFormat=function(){var a=this,b=a.config,c=b.pie_label_format;return a.hasType("gauge")?c=b.gauge_label_format:a.hasType("donut")&&(c=b.donut_label_format),c},i.getArcTitle=function(){var a=this;return a.hasType("donut")?a.config.donut_title:""},i.updateTargetsForArc=function(a){var b,c,d=this,e=d.main,f=d.classChartArc.bind(d),g=d.classArcs.bind(d),h=d.classFocus.bind(d);b=e.select("."+l.chartArcs).selectAll("."+l.chartArc).data(d.pie(a)).attr("class",function(a){return f(a)+h(a.data)}),c=b.enter().append("g").attr("class",f),c.append("g").attr("class",g),c.append("text").attr("dy",d.hasType("gauge")?"-.1em":".35em").style("opacity",0).style("text-anchor","middle").style("pointer-events","none")},i.initArc=function(){var a=this;a.arcs=a.main.select("."+l.chart).append("g").attr("class",l.chartArcs).attr("transform",a.getTranslate("arc")),a.arcs.append("text").attr("class",l.chartArcsTitle).style("text-anchor","middle").text(a.getArcTitle())},i.redrawArc=function(a,b,c){var d,e=this,f=e.d3,g=e.config,h=e.main;d=h.selectAll("."+l.arcs).selectAll("."+l.arc).data(e.arcData.bind(e)),d.enter().append("path").attr("class",e.classArc.bind(e)).style("fill",function(a){return e.color(a.data)}).style("cursor",function(a){return g.interaction_enabled&&g.data_selection_isselectable(a)?"pointer":null}).style("opacity",0).each(function(a){e.isGaugeType(a.data)&&(a.startAngle=a.endAngle=g.gauge_startingAngle),this._current=a}),d.attr("transform",function(a){return!e.isGaugeType(a.data)&&c?"scale(0)":""}).style("opacity",function(a){return a===this._current?0:1}).on("mouseover",g.interaction_enabled?function(a){var b,c;e.transiting||(b=e.updateAngle(a),b&&(c=e.convertToArcData(b),e.expandArc(b.data.id),e.api.focus(b.data.id),e.toggleFocusLegend(b.data.id,!0),e.config.data_onmouseover(c,this)))}:null).on("mousemove",g.interaction_enabled?function(a){var b,c,d=e.updateAngle(a);d&&(b=e.convertToArcData(d),c=[b],e.showTooltip(c,this))}:null).on("mouseout",g.interaction_enabled?function(a){var b,c;e.transiting||(b=e.updateAngle(a),b&&(c=e.convertToArcData(b),e.unexpandArc(b.data.id),e.api.revert(),e.revertLegend(),e.hideTooltip(),e.config.data_onmouseout(c,this)))}:null).on("click",g.interaction_enabled?function(a,b){var c,d=e.updateAngle(a);d&&(c=e.convertToArcData(d),e.toggleShape&&e.toggleShape(this,c,b),e.config.data_onclick.call(e.api,c,this))}:null).each(function(){e.transiting=!0}).transition().duration(a).attrTween("d",function(a){var b,c=e.updateAngle(a);return c?(isNaN(this._current.startAngle)&&(this._current.startAngle=0),isNaN(this._current.endAngle)&&(this._current.endAngle=this._current.startAngle),b=f.interpolate(this._current,c),this._current=b(0),function(c){var d=b(c);return d.data=a.data,e.getArc(d,!0)}):function(){return"M 0 0"}}).attr("transform",c?"scale(1)":"").style("fill",function(a){return e.levelColor?e.levelColor(a.data.values[0].value):e.color(a.data.id)}).style("opacity",1).call(e.endall,function(){e.transiting=!1}),d.exit().transition().duration(b).style("opacity",0).remove(),h.selectAll("."+l.chartArc).select("text").style("opacity",0).attr("class",function(a){return e.isGaugeType(a.data)?l.gaugeValue:""}).text(e.textForArcLabel.bind(e)).attr("transform",e.transformForArcLabel.bind(e)).style("font-size",function(a){return e.isGaugeType(a.data)?Math.round(e.radius/5)+"px":""}).transition().duration(a).style("opacity",function(a){return e.isTargetToShow(a.data.id)&&e.isArcType(a.data)?1:0}),h.select("."+l.chartArcsTitle).style("opacity",e.hasType("donut")||e.hasType("gauge")?1:0),e.hasType("gauge")&&(e.arcs.select("."+l.chartArcsBackground).attr("d",function(){var a={data:[{value:g.gauge_max}],startAngle:g.gauge_startingAngle,endAngle:-1*g.gauge_startingAngle};return e.getArc(a,!0,!0)}),e.arcs.select("."+l.chartArcsGaugeUnit).attr("dy",".75em").text(g.gauge_label_show?g.gauge_units:""),e.arcs.select("."+l.chartArcsGaugeMin).attr("dx",-1*(e.innerRadius+(e.radius-e.innerRadius)/(g.gauge_fullCircle?1:2))+"px").attr("dy","1.2em").text(g.gauge_label_show?g.gauge_min:""),e.arcs.select("."+l.chartArcsGaugeMax).attr("dx",e.innerRadius+(e.radius-e.innerRadius)/(g.gauge_fullCircle?1:2)+"px").attr("dy","1.2em").text(g.gauge_label_show?g.gauge_max:""))},i.initGauge=function(){var a=this.arcs;this.hasType("gauge")&&(a.append("path").attr("class",l.chartArcsBackground),a.append("text").attr("class",l.chartArcsGaugeUnit).style("text-anchor","middle").style("pointer-events","none"),a.append("text").attr("class",l.chartArcsGaugeMin).style("text-anchor","middle").style("pointer-events","none"),a.append("text").attr("class",l.chartArcsGaugeMax).style("text-anchor","middle").style("pointer-events","none"))},i.getGaugeLabelHeight=function(){return this.config.gauge_label_show?20:0},i.initRegion=function(){var a=this;a.region=a.main.append("g").attr("clip-path",a.clipPath).attr("class",l.regions)},i.updateRegion=function(a){var b=this,c=b.config;b.region.style("visibility",b.hasArcType()?"hidden":"visible"),b.mainRegion=b.main.select("."+l.regions).selectAll("."+l.region).data(c.regions),b.mainRegion.enter().append("g").append("rect").style("fill-opacity",0),b.mainRegion.attr("class",b.classRegion.bind(b)),b.mainRegion.exit().transition().duration(a).style("opacity",0).remove()},i.redrawRegion=function(a){var b=this,c=b.mainRegion.selectAll("rect").each(function(){var a=b.d3.select(this.parentNode).datum();b.d3.select(this).datum(a)}),d=b.regionX.bind(b),e=b.regionY.bind(b),f=b.regionWidth.bind(b),g=b.regionHeight.bind(b);return[(a?c.transition():c).attr("x",d).attr("y",e).attr("width",f).attr("height",g).style("fill-opacity",function(a){return m(a.opacity)?a.opacity:.1})]},i.regionX=function(a){var b,c=this,d=c.config,e="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated&&"start"in a?e(a.start):0:d.axis_rotated?0:"start"in a?c.x(c.isTimeSeries()?c.parseDate(a.start):a.start):0},i.regionY=function(a){var b,c=this,d=c.config,e="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated?0:"end"in a?e(a.end):0:d.axis_rotated&&"start"in a?c.x(c.isTimeSeries()?c.parseDate(a.start):a.start):0},i.regionWidth=function(a){var b,c=this,d=c.config,e=c.regionX(a),f="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated&&"end"in a?f(a.end):c.width:d.axis_rotated?c.width:"end"in a?c.x(c.isTimeSeries()?c.parseDate(a.end):a.end):c.width,e>b?0:b-e},i.regionHeight=function(a){var b,c=this,d=c.config,e=this.regionY(a),f="y"===a.axis?c.y:c.y2;return b="y"===a.axis||"y2"===a.axis?d.axis_rotated?c.height:"start"in a?f(a.start):c.height:d.axis_rotated&&"end"in a?c.x(c.isTimeSeries()?c.parseDate(a.end):a.end):c.height,e>b?0:b-e},i.isRegionOnX=function(a){return!a.axis||"x"===a.axis},i.drag=function(a){var b,c,d,e,f,g,h,i,j=this,k=j.config,m=j.main,n=j.d3;j.hasArcType()||k.data_selection_enabled&&(k.zoom_enabled&&!j.zoom.altDomain||k.data_selection_multiple&&(b=j.dragStart[0],c=j.dragStart[1],d=a[0],e=a[1],f=Math.min(b,d),g=Math.max(b,d),h=k.data_selection_grouped?j.margin.top:Math.min(c,e),i=k.data_selection_grouped?j.height:Math.max(c,e),m.select("."+l.dragarea).attr("x",f).attr("y",h).attr("width",g-f).attr("height",i-h),m.selectAll("."+l.shapes).selectAll("."+l.shape).filter(function(a){return k.data_selection_isselectable(a)}).each(function(a,b){var c,d,e,k,m,o,p=n.select(this),q=p.classed(l.SELECTED),r=p.classed(l.INCLUDED),s=!1;if(p.classed(l.circle))c=1*p.attr("cx"),d=1*p.attr("cy"),m=j.togglePoint,s=c>f&&g>c&&d>h&&i>d;else{if(!p.classed(l.bar))return;o=z(this),c=o.x,d=o.y,e=o.width,k=o.height,m=j.togglePath,s=!(c>g||f>c+e||d>i||h>d+k)}s^r&&(p.classed(l.INCLUDED,!r),p.classed(l.SELECTED,!q),m.call(j,!q,p,a,b))})))},i.dragstart=function(a){var b=this,c=b.config;b.hasArcType()||c.data_selection_enabled&&(b.dragStart=a,b.main.select("."+l.chart).append("rect").attr("class",l.dragarea).style("opacity",.1),b.dragging=!0)},i.dragend=function(){var a=this,b=a.config;a.hasArcType()||b.data_selection_enabled&&(a.main.select("."+l.dragarea).transition().duration(100).style("opacity",0).remove(),a.main.selectAll("."+l.shape).classed(l.INCLUDED,!1),a.dragging=!1)},i.selectPoint=function(a,b,c){var d=this,e=d.config,f=(e.axis_rotated?d.circleY:d.circleX).bind(d),g=(e.axis_rotated?d.circleX:d.circleY).bind(d),h=d.pointSelectR.bind(d);e.data_onselected.call(d.api,b,a.node()),d.main.select("."+l.selectedCircles+d.getTargetSelectorSuffix(b.id)).selectAll("."+l.selectedCircle+"-"+c).data([b]).enter().append("circle").attr("class",function(){return d.generateClass(l.selectedCircle,c)}).attr("cx",f).attr("cy",g).attr("stroke",function(){return d.color(b)}).attr("r",function(a){return 1.4*d.pointSelectR(a)}).transition().duration(100).attr("r",h)},i.unselectPoint=function(a,b,c){var d=this;d.config.data_onunselected.call(d.api,b,a.node()),d.main.select("."+l.selectedCircles+d.getTargetSelectorSuffix(b.id)).selectAll("."+l.selectedCircle+"-"+c).transition().duration(100).attr("r",0).remove()},i.togglePoint=function(a,b,c,d){a?this.selectPoint(b,c,d):this.unselectPoint(b,c,d)},i.selectPath=function(a,b){var c=this;c.config.data_onselected.call(c,b,a.node()),c.config.interaction_brighten&&a.transition().duration(100).style("fill",function(){return c.d3.rgb(c.color(b)).brighter(.75)})},i.unselectPath=function(a,b){var c=this;c.config.data_onunselected.call(c,b,a.node()),c.config.interaction_brighten&&a.transition().duration(100).style("fill",function(){return c.color(b)})},i.togglePath=function(a,b,c,d){a?this.selectPath(b,c,d):this.unselectPath(b,c,d)},i.getToggle=function(a,b){var c,d=this;return"circle"===a.nodeName?c=d.isStepType(b)?function(){}:d.togglePoint:"path"===a.nodeName&&(c=d.togglePath),c},i.toggleShape=function(a,b,c){var d=this,e=d.d3,f=d.config,g=e.select(a),h=g.classed(l.SELECTED),i=d.getToggle(a,b).bind(d);f.data_selection_enabled&&f.data_selection_isselectable(b)&&(f.data_selection_multiple||d.main.selectAll("."+l.shapes+(f.data_selection_grouped?d.getTargetSelectorSuffix(b.id):"")).selectAll("."+l.shape).each(function(a,b){var c=e.select(this);c.classed(l.SELECTED)&&i(!1,c.classed(l.SELECTED,!1),a,b)}),g.classed(l.SELECTED,!h),i(!h,g,b,c))},i.initBrush=function(){var a=this,b=a.d3;a.brush=b.svg.brush().on("brush",function(){a.redrawForBrush()}),a.brush.update=function(){return a.context&&a.context.select("."+l.brush).call(this),this},a.brush.scale=function(b){return a.config.axis_rotated?this.y(b):this.x(b)}},i.initSubchart=function(){var a=this,b=a.config,c=a.context=a.svg.append("g").attr("transform",a.getTranslate("context")),d=b.subchart_show?"visible":"hidden";c.style("visibility",d),c.append("g").attr("clip-path",a.clipPathForSubchart).attr("class",l.chart),c.select("."+l.chart).append("g").attr("class",l.chartBars),c.select("."+l.chart).append("g").attr("class",l.chartLines),c.append("g").attr("clip-path",a.clipPath).attr("class",l.brush).call(a.brush),a.axes.subx=c.append("g").attr("class",l.axisX).attr("transform",a.getTranslate("subx")).attr("clip-path",b.axis_rotated?"":a.clipPathForXAxis).style("visibility",b.subchart_axis_x_show?d:"hidden")},i.updateTargetsForSubchart=function(a){var b,c,d,e,f=this,g=f.context,h=f.config,i=f.classChartBar.bind(f),j=f.classBars.bind(f),k=f.classChartLine.bind(f),m=f.classLines.bind(f),n=f.classAreas.bind(f);h.subchart_show&&(e=g.select("."+l.chartBars).selectAll("."+l.chartBar).data(a).attr("class",i),d=e.enter().append("g").style("opacity",0).attr("class",i),d.append("g").attr("class",j),c=g.select("."+l.chartLines).selectAll("."+l.chartLine).data(a).attr("class",k),b=c.enter().append("g").style("opacity",0).attr("class",k),b.append("g").attr("class",m),b.append("g").attr("class",n),g.selectAll("."+l.brush+" rect").attr(h.axis_rotated?"width":"height",h.axis_rotated?f.width2:f.height2))},i.updateBarForSubchart=function(a){var b=this;b.contextBar=b.context.selectAll("."+l.bars).selectAll("."+l.bar).data(b.barData.bind(b)),b.contextBar.enter().append("path").attr("class",b.classBar.bind(b)).style("stroke","none").style("fill",b.color),b.contextBar.style("opacity",b.initialOpacity.bind(b)),b.contextBar.exit().transition().duration(a).style("opacity",0).remove()},i.redrawBarForSubchart=function(a,b,c){(b?this.contextBar.transition(Math.random().toString()).duration(c):this.contextBar).attr("d",a).style("opacity",1)},i.updateLineForSubchart=function(a){var b=this;b.contextLine=b.context.selectAll("."+l.lines).selectAll("."+l.line).data(b.lineData.bind(b)),b.contextLine.enter().append("path").attr("class",b.classLine.bind(b)).style("stroke",b.color),b.contextLine.style("opacity",b.initialOpacity.bind(b)),b.contextLine.exit().transition().duration(a).style("opacity",0).remove()},i.redrawLineForSubchart=function(a,b,c){(b?this.contextLine.transition(Math.random().toString()).duration(c):this.contextLine).attr("d",a).style("opacity",1)},i.updateAreaForSubchart=function(a){var b=this,c=b.d3;b.contextArea=b.context.selectAll("."+l.areas).selectAll("."+l.area).data(b.lineData.bind(b)),b.contextArea.enter().append("path").attr("class",b.classArea.bind(b)).style("fill",b.color).style("opacity",function(){return b.orgAreaOpacity=+c.select(this).style("opacity"),0}),b.contextArea.style("opacity",0),b.contextArea.exit().transition().duration(a).style("opacity",0).remove()},i.redrawAreaForSubchart=function(a,b,c){(b?this.contextArea.transition(Math.random().toString()).duration(c):this.contextArea).attr("d",a).style("fill",this.color).style("opacity",this.orgAreaOpacity)},i.redrawSubchart=function(a,b,c,d,e,f,g){var h,i,j,k=this,l=k.d3,m=k.config;k.context.style("visibility",m.subchart_show?"visible":"hidden"),m.subchart_show&&(l.event&&"zoom"===l.event.type&&k.brush.extent(k.x.orgDomain()).update(),a&&(k.brush.empty()||k.brush.extent(k.x.orgDomain()).update(),h=k.generateDrawArea(e,!0),i=k.generateDrawBar(f,!0),j=k.generateDrawLine(g,!0),k.updateBarForSubchart(c),k.updateLineForSubchart(c),k.updateAreaForSubchart(c),k.redrawBarForSubchart(i,c,c),k.redrawLineForSubchart(j,c,c),k.redrawAreaForSubchart(h,c,c)))},i.redrawForBrush=function(){var a=this,b=a.x;a.redraw({withTransition:!1,withY:a.config.zoom_rescale,withSubchart:!1,withUpdateXDomain:!0,withDimension:!1}),a.config.subchart_onbrush.call(a.api,b.orgDomain())},i.transformContext=function(a,b){var c,d=this;b&&b.axisSubX?c=b.axisSubX:(c=d.context.select("."+l.axisX),a&&(c=c.transition())),d.context.attr("transform",d.getTranslate("context")),c.attr("transform",d.getTranslate("subx"))},i.getDefaultExtent=function(){var a=this,b=a.config,c=n(b.axis_x_extent)?b.axis_x_extent(a.getXDomain(a.data.targets)):b.axis_x_extent;return a.isTimeSeries()&&(c=[a.parseDate(c[0]),a.parseDate(c[1])]),c},i.initZoom=function(){var a,b=this,c=b.d3,d=b.config;b.zoom=c.behavior.zoom().on("zoomstart",function(){a=c.event.sourceEvent,b.zoom.altDomain=c.event.sourceEvent.altKey?b.x.orgDomain():null,d.zoom_onzoomstart.call(b.api,c.event.sourceEvent)}).on("zoom",function(){b.redrawForZoom.call(b)}).on("zoomend",function(){var e=c.event.sourceEvent;e&&a.clientX===e.clientX&&a.clientY===e.clientY||(b.redrawEventRect(),b.updateZoom(),d.zoom_onzoomend.call(b.api,b.x.orgDomain()))}),b.zoom.scale=function(a){return d.axis_rotated?this.y(a):this.x(a)},b.zoom.orgScaleExtent=function(){var a=d.zoom_extent?d.zoom_extent:[1,10];return[a[0],Math.max(b.getMaxDataCount()/a[1],a[1])]},b.zoom.updateScaleExtent=function(){var a=t(b.x.orgDomain())/t(b.getZoomDomain()),c=this.orgScaleExtent();return this.scaleExtent([c[0]*a,c[1]*a]),this}},i.getZoomDomain=function(){var a=this,b=a.config,c=a.d3,d=c.min([a.orgXDomain[0],b.zoom_x_min]),e=c.max([a.orgXDomain[1],b.zoom_x_max]);return[d,e]},i.updateZoom=function(){var a=this,b=a.config.zoom_enabled?a.zoom:function(){};a.main.select("."+l.zoomRect).call(b).on("dblclick.zoom",null),a.main.selectAll("."+l.eventRect).call(b).on("dblclick.zoom",null)},i.redrawForZoom=function(){var a=this,b=a.d3,c=a.config,d=a.zoom,e=a.x;if(c.zoom_enabled&&0!==a.filterTargetsToShow(a.data.targets).length){if("mousemove"===b.event.sourceEvent.type&&d.altDomain)return e.domain(d.altDomain),void d.scale(e).updateScaleExtent();a.isCategorized()&&e.orgDomain()[0]===a.orgXDomain[0]&&e.domain([a.orgXDomain[0]-1e-10,e.orgDomain()[1]]),a.redraw({withTransition:!1,withY:c.zoom_rescale,withSubchart:!1,withEventRect:!1,withDimension:!1}),"mousemove"===b.event.sourceEvent.type&&(a.cancelClick=!0),c.zoom_onzoom.call(a.api,e.orgDomain())}},i.generateColor=function(){var a=this,b=a.config,c=a.d3,d=b.data_colors,e=v(b.color_pattern)?b.color_pattern:c.scale.category10().range(),f=b.data_color,g=[];return function(a){var b,c=a.id||a.data&&a.data.id||a;return d[c]instanceof Function?b=d[c](a):d[c]?b=d[c]:(g.indexOf(c)<0&&g.push(c),b=e[g.indexOf(c)%e.length],d[c]=b),f instanceof Function?f(b,a):b}},i.generateLevelColor=function(){var a=this,b=a.config,c=b.color_pattern,d=b.color_threshold,e="value"===d.unit,f=d.values&&d.values.length?d.values:[],g=d.max||100;return v(b.color_threshold)?function(a){var b,d,h=c[c.length-1];for(b=0;b=0?l.focused:"")},i.classDefocused=function(a){return" "+(this.defocusedTargetIds.indexOf(a.id)>=0?l.defocused:"")},i.classChartText=function(a){return l.chartText+this.classTarget(a.id)},i.classChartLine=function(a){return l.chartLine+this.classTarget(a.id)},i.classChartBar=function(a){return l.chartBar+this.classTarget(a.id)},i.classChartArc=function(a){return l.chartArc+this.classTarget(a.data.id)},i.getTargetSelectorSuffix=function(a){return a||0===a?("-"+a).replace(/[\s?!@#$%^&*()_=+,.<>'":;\[\]\/|~`{}\\]/g,"-"):""},i.selectorTarget=function(a,b){return(b||"")+"."+l.target+this.getTargetSelectorSuffix(a)},i.selectorTargets=function(a,b){var c=this;return a=a||[],a.length?a.map(function(a){return c.selectorTarget(a,b)}):null},i.selectorLegend=function(a){return"."+l.legendItem+this.getTargetSelectorSuffix(a)},i.selectorLegends=function(a){var b=this;return a&&a.length?a.map(function(a){return b.selectorLegend(a)}):null};var m=i.isValue=function(a){return a||0===a},n=i.isFunction=function(a){return"function"==typeof a},o=i.isString=function(a){return"string"==typeof a},p=i.isUndefined=function(a){return"undefined"==typeof a},q=i.isDefined=function(a){return"undefined"!=typeof a},r=i.ceil10=function(a){return 10*Math.ceil(a/10)},s=i.asHalfPixel=function(a){return Math.ceil(a)+.5},t=i.diffDomain=function(a){return a[1]-a[0]},u=i.isEmpty=function(a){return"undefined"==typeof a||null===a||o(a)&&0===a.length||"object"==typeof a&&0===Object.keys(a).length},v=i.notEmpty=function(a){return!i.isEmpty(a)},w=i.getOption=function(a,b,c){return q(a[b])?a[b]:c},x=i.hasValue=function(a,b){var c=!1;return Object.keys(a).forEach(function(d){a[d]===b&&(c=!0)}),c},y=i.sanitise=function(a){return"string"==typeof a?a.replace(//g,">"):a},z=i.getPathBox=function(a){var b=a.getBoundingClientRect(),c=[a.pathSegList.getItem(0),a.pathSegList.getItem(1)],d=c[0].x,e=Math.min(c[0].y,c[1].y);return{x:d,y:e,width:b.width,height:b.height}};h.focus=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a.filter(c.isTargetToShow,c))),this.revert(),this.defocus(),b.classed(l.focused,!0).classed(l.defocused,!1), +c.hasArcType()&&c.expandArc(a),c.toggleFocusLegend(a,!0),c.focusedTargetIds=a,c.defocusedTargetIds=c.defocusedTargetIds.filter(function(b){return a.indexOf(b)<0})},h.defocus=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a.filter(c.isTargetToShow,c))),b.classed(l.focused,!1).classed(l.defocused,!0),c.hasArcType()&&c.unexpandArc(a),c.toggleFocusLegend(a,!1),c.focusedTargetIds=c.focusedTargetIds.filter(function(b){return a.indexOf(b)<0}),c.defocusedTargetIds=a},h.revert=function(a){var b,c=this.internal;a=c.mapToTargetIds(a),b=c.svg.selectAll(c.selectorTargets(a)),b.classed(l.focused,!1).classed(l.defocused,!1),c.hasArcType()&&c.unexpandArc(a),c.config.legend_show&&(c.showLegend(a.filter(c.isLegendToShow.bind(c))),c.legend.selectAll(c.selectorLegends(a)).filter(function(){return c.d3.select(this).classed(l.legendItemFocused)}).classed(l.legendItemFocused,!1)),c.focusedTargetIds=[],c.defocusedTargetIds=[]},h.show=function(a,b){var c,d=this.internal;a=d.mapToTargetIds(a),b=b||{},d.removeHiddenTargetIds(a),c=d.svg.selectAll(d.selectorTargets(a)),c.transition().style("opacity",1,"important").call(d.endall,function(){c.style("opacity",null).style("opacity",1)}),b.withLegend&&d.showLegend(a),d.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0})},h.hide=function(a,b){var c,d=this.internal;a=d.mapToTargetIds(a),b=b||{},d.addHiddenTargetIds(a),c=d.svg.selectAll(d.selectorTargets(a)),c.transition().style("opacity",0,"important").call(d.endall,function(){c.style("opacity",null).style("opacity",0)}),b.withLegend&&d.hideLegend(a),d.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0})},h.toggle=function(a,b){var c=this,d=this.internal;d.mapToTargetIds(a).forEach(function(a){d.isTargetToShow(a)?c.hide(a,b):c.show(a,b)})},h.zoom=function(a){var b=this.internal;return a&&(b.isTimeSeries()&&(a=a.map(function(a){return b.parseDate(a)})),b.brush.extent(a),b.redraw({withUpdateXDomain:!0,withY:b.config.zoom_rescale}),b.config.zoom_onzoom.call(this,b.x.orgDomain())),b.brush.extent()},h.zoom.enable=function(a){var b=this.internal;b.config.zoom_enabled=a,b.updateAndRedraw()},h.unzoom=function(){var a=this.internal;a.brush.clear().update(),a.redraw({withUpdateXDomain:!0})},h.zoom.max=function(a){var b=this.internal,c=b.config,d=b.d3;return 0===a||a?void(c.zoom_x_max=d.max([b.orgXDomain[1],a])):c.zoom_x_max},h.zoom.min=function(a){var b=this.internal,c=b.config,d=b.d3;return 0===a||a?void(c.zoom_x_min=d.min([b.orgXDomain[0],a])):c.zoom_x_min},h.zoom.range=function(a){return arguments.length?(q(a.max)&&this.domain.max(a.max),void(q(a.min)&&this.domain.min(a.min))):{max:this.domain.max(),min:this.domain.min()}},h.load=function(a){var b=this.internal,c=b.config;return a.xs&&b.addXs(a.xs),"names"in a&&h.data.names.bind(this)(a.names),"classes"in a&&Object.keys(a.classes).forEach(function(b){c.data_classes[b]=a.classes[b]}),"categories"in a&&b.isCategorized()&&(c.axis_x_categories=a.categories),"axes"in a&&Object.keys(a.axes).forEach(function(b){c.data_axes[b]=a.axes[b]}),"colors"in a&&Object.keys(a.colors).forEach(function(b){c.data_colors[b]=a.colors[b]}),"cacheIds"in a&&b.hasCaches(a.cacheIds)?void b.load(b.getCaches(a.cacheIds),a.done):void("unload"in a?b.unload(b.mapToTargetIds("boolean"==typeof a.unload&&a.unload?null:a.unload),function(){b.loadFromArgs(a)}):b.loadFromArgs(a))},h.unload=function(a){var b=this.internal;a=a||{},a instanceof Array?a={ids:a}:"string"==typeof a&&(a={ids:[a]}),b.unload(b.mapToTargetIds(a.ids),function(){b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0}),a.done&&a.done()})},h.flow=function(a){var b,c,d,e,f,g,h,i,j=this.internal,k=[],l=j.getMaxDataCount(),n=0,o=0;if(a.json)c=j.convertJsonToData(a.json,a.keys);else if(a.rows)c=j.convertRowsToData(a.rows);else{if(!a.columns)return;c=j.convertColumnsToData(a.columns)}b=j.convertDataToTargets(c,!0),j.data.targets.forEach(function(a){var c,d,e=!1;for(c=0;cd;d++)b[c].values[d].index=o+d,j.isTimeSeries()||(b[c].values[d].x=o+d);a.values=a.values.concat(b[c].values),b.splice(c,1);break}e||k.push(a.id)}),j.data.targets.forEach(function(a){var b,c;for(b=0;bc;c++)a.values.push({id:a.id,index:o+c,x:j.isTimeSeries()?j.getOtherTargetX(o+c):o+c,value:null})}),j.data.targets.length&&b.forEach(function(a){var b,c=[];for(b=j.data.targets[0].values[0].index;o>b;b++)c.push({id:a.id,index:b,x:j.isTimeSeries()?j.getOtherTargetX(b):b,value:null});a.values.forEach(function(a){a.index+=o,j.isTimeSeries()||(a.x+=o)}),a.values=c.concat(a.values)}),j.data.targets=j.data.targets.concat(b),d=j.getMaxDataCount(),f=j.data.targets[0],g=f.values[0],q(a.to)?(n=0,i=j.isTimeSeries()?j.parseDate(a.to):a.to,f.values.forEach(function(a){a.x1?f.values[f.values.length-1].x-g.x:g.x-j.getXDomain(j.data.targets)[0]:1,e=[g.x-h,g.x],j.updateXDomain(null,!0,!0,!1,e)),j.updateTargets(j.data.targets),j.redraw({flow:{index:g.index,length:n,duration:m(a.duration)?a.duration:j.config.transition_duration,done:a.done,orgDataCount:l},withLegend:!0,withTransition:l>1,withTrimXDomain:!1,withUpdateXAxis:!0})},i.generateFlow=function(a){var b=this,c=b.config,d=b.d3;return function(){var e,f,g,h=a.targets,i=a.flow,j=a.drawBar,k=a.drawLine,m=a.drawArea,n=a.cx,o=a.cy,p=a.xv,q=a.xForText,r=a.yForText,s=a.duration,u=1,v=i.index,w=i.length,x=b.getValueOnIndex(b.data.targets[0].values,v),y=b.getValueOnIndex(b.data.targets[0].values,v+w),z=b.x.domain(),A=i.duration||s,B=i.done||function(){},C=b.generateWait(),D=b.xgrid||d.selectAll([]),E=b.xgridLines||d.selectAll([]),F=b.mainRegion||d.selectAll([]),G=b.mainText||d.selectAll([]),H=b.mainBar||d.selectAll([]),I=b.mainLine||d.selectAll([]),J=b.mainArea||d.selectAll([]),K=b.mainCircle||d.selectAll([]);b.flowing=!0,b.data.targets.forEach(function(a){a.values.splice(0,w)}),g=b.updateXDomain(h,!0,!0),b.updateXGrid&&b.updateXGrid(!0),i.orgDataCount?e=1===i.orgDataCount||(x&&x.x)===(y&&y.x)?b.x(z[0])-b.x(g[0]):b.isTimeSeries()?b.x(z[0])-b.x(g[0]):b.x(x.x)-b.x(y.x):1!==b.data.targets[0].values.length?e=b.x(z[0])-b.x(g[0]):b.isTimeSeries()?(x=b.getValueOnIndex(b.data.targets[0].values,0),y=b.getValueOnIndex(b.data.targets[0].values,b.data.targets[0].values.length-1),e=b.x(x.x)-b.x(y.x)):e=t(g)/2,u=t(z)/t(g),f="translate("+e+",0) scale("+u+",1)",b.hideXGridFocus(),d.transition().ease("linear").duration(A).each(function(){C.add(b.axes.x.transition().call(b.xAxis)),C.add(H.transition().attr("transform",f)),C.add(I.transition().attr("transform",f)),C.add(J.transition().attr("transform",f)),C.add(K.transition().attr("transform",f)),C.add(G.transition().attr("transform",f)),C.add(F.filter(b.isRegionOnX).transition().attr("transform",f)),C.add(D.transition().attr("transform",f)),C.add(E.transition().attr("transform",f))}).call(C,function(){var a,d=[],e=[],f=[];if(w){for(a=0;w>a;a++)d.push("."+l.shape+"-"+(v+a)),e.push("."+l.text+"-"+(v+a)),f.push("."+l.eventRect+"-"+(v+a));b.svg.selectAll("."+l.shapes).selectAll(d).remove(),b.svg.selectAll("."+l.texts).selectAll(e).remove(),b.svg.selectAll("."+l.eventRects).selectAll(f).remove(),b.svg.select("."+l.xgrid).remove()}D.attr("transform",null).attr(b.xgridAttr),E.attr("transform",null),E.select("line").attr("x1",c.axis_rotated?0:p).attr("x2",c.axis_rotated?b.width:p),E.select("text").attr("x",c.axis_rotated?b.width:0).attr("y",p),H.attr("transform",null).attr("d",j),I.attr("transform",null).attr("d",k),J.attr("transform",null).attr("d",m),K.attr("transform",null).attr("cx",n).attr("cy",o),G.attr("transform",null).attr("x",q).attr("y",r).style("fill-opacity",b.opacityForText.bind(b)),F.attr("transform",null),F.select("rect").filter(b.isRegionOnX).attr("x",b.regionX.bind(b)).attr("width",b.regionWidth.bind(b)),c.interaction_enabled&&b.redrawEventRect(),B(),b.flowing=!1})}},h.selected=function(a){var b=this.internal,c=b.d3;return c.merge(b.main.selectAll("."+l.shapes+b.getTargetSelectorSuffix(a)).selectAll("."+l.shape).filter(function(){return c.select(this).classed(l.SELECTED)}).map(function(a){return a.map(function(a){var b=a.__data__;return b.data?b.data:b})}))},h.select=function(a,b,c){var d=this.internal,e=d.d3,f=d.config;f.data_selection_enabled&&d.main.selectAll("."+l.shapes).selectAll("."+l.shape).each(function(g,h){var i=e.select(this),j=g.data?g.data.id:g.id,k=d.getToggle(this,g).bind(d),m=f.data_selection_grouped||!a||a.indexOf(j)>=0,n=!b||b.indexOf(h)>=0,o=i.classed(l.SELECTED);i.classed(l.line)||i.classed(l.area)||(m&&n?f.data_selection_isselectable(g)&&!o&&k(!0,i.classed(l.SELECTED,!0),g,h):q(c)&&c&&o&&k(!1,i.classed(l.SELECTED,!1),g,h))})},h.unselect=function(a,b){var c=this.internal,d=c.d3,e=c.config;e.data_selection_enabled&&c.main.selectAll("."+l.shapes).selectAll("."+l.shape).each(function(f,g){var h=d.select(this),i=f.data?f.data.id:f.id,j=c.getToggle(this,f).bind(c),k=e.data_selection_grouped||!a||a.indexOf(i)>=0,m=!b||b.indexOf(g)>=0,n=h.classed(l.SELECTED);h.classed(l.line)||h.classed(l.area)||k&&m&&e.data_selection_isselectable(f)&&n&&j(!1,h.classed(l.SELECTED,!1),f,g)})},h.transform=function(a,b){var c=this.internal,d=["pie","donut"].indexOf(a)>=0?{withTransform:!0}:null;c.transformTo(b,a,d)},i.transformTo=function(a,b,c){var d=this,e=!d.hasArcType(),f=c||{withTransitionForAxis:e};f.withTransitionForTransform=!1,d.transiting=!1,d.setTargetType(a,b),d.updateTargets(d.data.targets),d.updateAndRedraw(f)},h.groups=function(a){var b=this.internal,c=b.config;return p(a)?c.data_groups:(c.data_groups=a,b.redraw(),c.data_groups)},h.xgrids=function(a){var b=this.internal,c=b.config;return a?(c.grid_x_lines=a,b.redrawWithoutRescale(),c.grid_x_lines):c.grid_x_lines},h.xgrids.add=function(a){var b=this.internal;return this.xgrids(b.config.grid_x_lines.concat(a?a:[]))},h.xgrids.remove=function(a){var b=this.internal;b.removeGridLines(a,!0)},h.ygrids=function(a){var b=this.internal,c=b.config;return a?(c.grid_y_lines=a,b.redrawWithoutRescale(),c.grid_y_lines):c.grid_y_lines},h.ygrids.add=function(a){var b=this.internal;return this.ygrids(b.config.grid_y_lines.concat(a?a:[]))},h.ygrids.remove=function(a){var b=this.internal;b.removeGridLines(a,!1)},h.regions=function(a){var b=this.internal,c=b.config;return a?(c.regions=a,b.redrawWithoutRescale(),c.regions):c.regions},h.regions.add=function(a){var b=this.internal,c=b.config;return a?(c.regions=c.regions.concat(a),b.redrawWithoutRescale(),c.regions):c.regions},h.regions.remove=function(a){var b,c,d,e=this.internal,f=e.config;return a=a||{},b=e.getOption(a,"duration",f.transition_duration),c=e.getOption(a,"classes",[l.region]),d=e.main.select("."+l.regions).selectAll(c.map(function(a){return"."+a})),(b?d.transition().duration(b):d).style("opacity",0).remove(),f.regions=f.regions.filter(function(a){var b=!1;return a["class"]?(a["class"].split(" ").forEach(function(a){c.indexOf(a)>=0&&(b=!0)}),!b):!0}),f.regions},h.data=function(a){var b=this.internal.data.targets;return"undefined"==typeof a?b:b.filter(function(b){return[].concat(a).indexOf(b.id)>=0})},h.data.shown=function(a){return this.internal.filterTargetsToShow(this.data(a))},h.data.values=function(a){var b,c=null;return a&&(b=this.data(a),c=b[0]?b[0].values.map(function(a){return a.value}):null),c},h.data.names=function(a){return this.internal.clearLegendItemTextBoxCache(),this.internal.updateDataAttributes("names",a)},h.data.colors=function(a){return this.internal.updateDataAttributes("colors",a)},h.data.axes=function(a){return this.internal.updateDataAttributes("axes",a)},h.category=function(a,b){var c=this.internal,d=c.config;return arguments.length>1&&(d.axis_x_categories[a]=b,c.redraw()),d.axis_x_categories[a]},h.categories=function(a){var b=this.internal,c=b.config;return arguments.length?(c.axis_x_categories=a,b.redraw(),c.axis_x_categories):c.axis_x_categories},h.color=function(a){var b=this.internal;return b.color(a)},h.x=function(a){var b=this.internal;return arguments.length&&(b.updateTargetX(b.data.targets,a),b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})),b.data.xs},h.xs=function(a){var b=this.internal;return arguments.length&&(b.updateTargetXs(b.data.targets,a),b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})),b.data.xs},h.axis=function(){},h.axis.labels=function(a){var b=this.internal;arguments.length&&(Object.keys(a).forEach(function(c){b.axis.setLabelText(c,a[c])}),b.axis.updateLabels())},h.axis.max=function(a){var b=this.internal,c=b.config;return arguments.length?("object"==typeof a?(m(a.x)&&(c.axis_x_max=a.x),m(a.y)&&(c.axis_y_max=a.y),m(a.y2)&&(c.axis_y2_max=a.y2)):c.axis_y_max=c.axis_y2_max=a,void b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})):{x:c.axis_x_max,y:c.axis_y_max,y2:c.axis_y2_max}},h.axis.min=function(a){var b=this.internal,c=b.config;return arguments.length?("object"==typeof a?(m(a.x)&&(c.axis_x_min=a.x),m(a.y)&&(c.axis_y_min=a.y),m(a.y2)&&(c.axis_y2_min=a.y2)):c.axis_y_min=c.axis_y2_min=a,void b.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})):{x:c.axis_x_min,y:c.axis_y_min,y2:c.axis_y2_min}},h.axis.range=function(a){return arguments.length?(q(a.max)&&this.axis.max(a.max),void(q(a.min)&&this.axis.min(a.min))):{max:this.axis.max(),min:this.axis.min()}},h.legend=function(){},h.legend.show=function(a){var b=this.internal;b.showLegend(b.mapToTargetIds(a)),b.updateAndRedraw({withLegend:!0})},h.legend.hide=function(a){var b=this.internal;b.hideLegend(b.mapToTargetIds(a)),b.updateAndRedraw({withLegend:!0})},h.resize=function(a){var b=this.internal,c=b.config;c.size_width=a?a.width:null,c.size_height=a?a.height:null,this.flush()},h.flush=function(){var a=this.internal;a.updateAndRedraw({withLegend:!0,withTransition:!1,withTransitionForTransform:!1})},h.destroy=function(){var b=this.internal;if(a.clearInterval(b.intervalForObserveInserted),void 0!==b.resizeTimeout&&a.clearTimeout(b.resizeTimeout),a.detachEvent)a.detachEvent("onresize",b.resizeFunction);else if(a.removeEventListener)a.removeEventListener("resize",b.resizeFunction);else{var c=a.onresize;c&&c.add&&c.remove&&c.remove(b.resizeFunction)}return b.selectChart.classed("c3",!1).html(""),Object.keys(b).forEach(function(a){b[a]=null}),null},h.tooltip=function(){},h.tooltip.show=function(a){var b,c,d=this.internal;a.mouse&&(c=a.mouse),a.data?d.isMultipleX()?(c=[d.x(a.data.x),d.getYScale(a.data.id)(a.data.value)],b=null):b=m(a.data.index)?a.data.index:d.getIndexByX(a.data.x):"undefined"!=typeof a.x?b=d.getIndexByX(a.x):"undefined"!=typeof a.index&&(b=a.index),d.dispatchEvent("mouseover",b,c),d.dispatchEvent("mousemove",b,c),d.config.tooltip_onshow.call(d,a.data)},h.tooltip.hide=function(){this.internal.dispatchEvent("mouseout",0),this.internal.config.tooltip_onhide.call(this)};var A;i.isSafari=function(){var b=a.navigator.userAgent;return b.indexOf("Safari")>=0&&b.indexOf("Chrome")<0},i.isChrome=function(){var b=a.navigator.userAgent;return b.indexOf("Chrome")>=0},Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),function(){"SVGPathSeg"in a||(a.SVGPathSeg=function(a,b,c){this.pathSegType=a,this.pathSegTypeAsLetter=b,this._owningPathSegList=c},SVGPathSeg.PATHSEG_UNKNOWN=0,SVGPathSeg.PATHSEG_CLOSEPATH=1,SVGPathSeg.PATHSEG_MOVETO_ABS=2,SVGPathSeg.PATHSEG_MOVETO_REL=3,SVGPathSeg.PATHSEG_LINETO_ABS=4,SVGPathSeg.PATHSEG_LINETO_REL=5,SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS=6,SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL=7,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS=8,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL=9,SVGPathSeg.PATHSEG_ARC_ABS=10,SVGPathSeg.PATHSEG_ARC_REL=11,SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS=12,SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL=13,SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS=14,SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL=15,SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS=16,SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL=17,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS=18,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL=19,SVGPathSeg.prototype._segmentChanged=function(){this._owningPathSegList&&this._owningPathSegList.segmentChanged(this)},a.SVGPathSegClosePath=function(a){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CLOSEPATH,"z",a)},SVGPathSegClosePath.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegClosePath.prototype.toString=function(){return"[object SVGPathSegClosePath]"},SVGPathSegClosePath.prototype._asPathString=function(){return this.pathSegTypeAsLetter},SVGPathSegClosePath.prototype.clone=function(){return new SVGPathSegClosePath(void 0)},a.SVGPathSegMovetoAbs=function(a,b,c){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_MOVETO_ABS,"M",a),this._x=b,this._y=c},SVGPathSegMovetoAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegMovetoAbs.prototype.toString=function(){return"[object SVGPathSegMovetoAbs]"},SVGPathSegMovetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},SVGPathSegMovetoAbs.prototype.clone=function(){return new SVGPathSegMovetoAbs(void 0,this._x,this._y)},Object.defineProperty(SVGPathSegMovetoAbs.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegMovetoAbs.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegMovetoRel=function(a,b,c){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_MOVETO_REL,"m",a),this._x=b,this._y=c},SVGPathSegMovetoRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegMovetoRel.prototype.toString=function(){return"[object SVGPathSegMovetoRel]"},SVGPathSegMovetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},SVGPathSegMovetoRel.prototype.clone=function(){return new SVGPathSegMovetoRel(void 0,this._x,this._y)},Object.defineProperty(SVGPathSegMovetoRel.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegMovetoRel.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegLinetoAbs=function(a,b,c){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_ABS,"L",a),this._x=b,this._y=c},SVGPathSegLinetoAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegLinetoAbs.prototype.toString=function(){return"[object SVGPathSegLinetoAbs]"},SVGPathSegLinetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},SVGPathSegLinetoAbs.prototype.clone=function(){return new SVGPathSegLinetoAbs(void 0,this._x,this._y)},Object.defineProperty(SVGPathSegLinetoAbs.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegLinetoAbs.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegLinetoRel=function(a,b,c){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_REL,"l",a),this._x=b,this._y=c},SVGPathSegLinetoRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegLinetoRel.prototype.toString=function(){return"[object SVGPathSegLinetoRel]"},SVGPathSegLinetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},SVGPathSegLinetoRel.prototype.clone=function(){return new SVGPathSegLinetoRel(void 0,this._x,this._y)},Object.defineProperty(SVGPathSegLinetoRel.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegLinetoRel.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegCurvetoCubicAbs=function(a,b,c,d,e,f,g){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS,"C",a),this._x=b,this._y=c,this._x1=d,this._y1=e,this._x2=f,this._y2=g},SVGPathSegCurvetoCubicAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegCurvetoCubicAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicAbs]"},SVGPathSegCurvetoCubicAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},SVGPathSegCurvetoCubicAbs.prototype.clone=function(){return new SVGPathSegCurvetoCubicAbs(void 0,this._x,this._y,this._x1,this._y1,this._x2,this._y2)},Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x1",{get:function(){return this._x1},set:function(a){this._x1=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y1",{get:function(){return this._y1},set:function(a){this._y1=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x2",{get:function(){return this._x2},set:function(a){this._x2=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y2",{get:function(){return this._y2},set:function(a){this._y2=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegCurvetoCubicRel=function(a,b,c,d,e,f,g){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL,"c",a),this._x=b,this._y=c,this._x1=d,this._y1=e,this._x2=f,this._y2=g},SVGPathSegCurvetoCubicRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegCurvetoCubicRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicRel]"},SVGPathSegCurvetoCubicRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},SVGPathSegCurvetoCubicRel.prototype.clone=function(){return new SVGPathSegCurvetoCubicRel(void 0,this._x,this._y,this._x1,this._y1,this._x2,this._y2)},Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x1",{get:function(){return this._x1},set:function(a){this._x1=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y1",{get:function(){return this._y1},set:function(a){this._y1=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x2",{get:function(){return this._x2},set:function(a){this._x2=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y2",{get:function(){return this._y2},set:function(a){this._y2=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegCurvetoQuadraticAbs=function(a,b,c,d,e){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS,"Q",a),this._x=b,this._y=c,this._x1=d,this._y1=e},SVGPathSegCurvetoQuadraticAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegCurvetoQuadraticAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticAbs]"},SVGPathSegCurvetoQuadraticAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y},SVGPathSegCurvetoQuadraticAbs.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticAbs(void 0,this._x,this._y,this._x1,this._y1)},Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"x1",{get:function(){return this._x1},set:function(a){this._x1=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"y1",{get:function(){return this._y1},set:function(a){this._y1=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegCurvetoQuadraticRel=function(a,b,c,d,e){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL,"q",a),this._x=b,this._y=c,this._x1=d,this._y1=e},SVGPathSegCurvetoQuadraticRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegCurvetoQuadraticRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticRel]"},SVGPathSegCurvetoQuadraticRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y},SVGPathSegCurvetoQuadraticRel.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticRel(void 0,this._x,this._y,this._x1,this._y1)},Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"x1",{get:function(){return this._x1},set:function(a){this._x1=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"y1",{get:function(){return this._y1},set:function(a){this._y1=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegArcAbs=function(a,b,c,d,e,f,g,h){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_ARC_ABS,"A",a),this._x=b,this._y=c,this._r1=d,this._r2=e,this._angle=f,this._largeArcFlag=g,this._sweepFlag=h},SVGPathSegArcAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegArcAbs.prototype.toString=function(){return"[object SVGPathSegArcAbs]"},SVGPathSegArcAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y},SVGPathSegArcAbs.prototype.clone=function(){return new SVGPathSegArcAbs(void 0,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag)},Object.defineProperty(SVGPathSegArcAbs.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcAbs.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcAbs.prototype,"r1",{get:function(){return this._r1},set:function(a){this._r1=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcAbs.prototype,"r2",{get:function(){return this._r2},set:function(a){this._r2=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcAbs.prototype,"angle",{get:function(){return this._angle},set:function(a){this._angle=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcAbs.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag},set:function(a){this._largeArcFlag=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcAbs.prototype,"sweepFlag",{get:function(){return this._sweepFlag},set:function(a){this._sweepFlag=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegArcRel=function(a,b,c,d,e,f,g,h){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_ARC_REL,"a",a),this._x=b,this._y=c,this._r1=d,this._r2=e,this._angle=f,this._largeArcFlag=g,this._sweepFlag=h},SVGPathSegArcRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegArcRel.prototype.toString=function(){return"[object SVGPathSegArcRel]"},SVGPathSegArcRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y},SVGPathSegArcRel.prototype.clone=function(){return new SVGPathSegArcRel(void 0,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag)},Object.defineProperty(SVGPathSegArcRel.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcRel.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcRel.prototype,"r1",{get:function(){return this._r1},set:function(a){this._r1=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcRel.prototype,"r2",{get:function(){return this._r2},set:function(a){this._r2=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcRel.prototype,"angle",{get:function(){return this._angle},set:function(a){this._angle=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcRel.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag},set:function(a){this._largeArcFlag=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegArcRel.prototype,"sweepFlag",{get:function(){return this._sweepFlag},set:function(a){this._sweepFlag=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegLinetoHorizontalAbs=function(a,b){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS,"H",a),this._x=b},SVGPathSegLinetoHorizontalAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegLinetoHorizontalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalAbs]"},SVGPathSegLinetoHorizontalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x},SVGPathSegLinetoHorizontalAbs.prototype.clone=function(){return new SVGPathSegLinetoHorizontalAbs(void 0,this._x)},Object.defineProperty(SVGPathSegLinetoHorizontalAbs.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegLinetoHorizontalRel=function(a,b){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL,"h",a),this._x=b},SVGPathSegLinetoHorizontalRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegLinetoHorizontalRel.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalRel]"},SVGPathSegLinetoHorizontalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x},SVGPathSegLinetoHorizontalRel.prototype.clone=function(){return new SVGPathSegLinetoHorizontalRel(void 0,this._x)},Object.defineProperty(SVGPathSegLinetoHorizontalRel.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegLinetoVerticalAbs=function(a,b){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS,"V",a),this._y=b},SVGPathSegLinetoVerticalAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegLinetoVerticalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalAbs]"},SVGPathSegLinetoVerticalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y},SVGPathSegLinetoVerticalAbs.prototype.clone=function(){return new SVGPathSegLinetoVerticalAbs(void 0,this._y)},Object.defineProperty(SVGPathSegLinetoVerticalAbs.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegLinetoVerticalRel=function(a,b){ +SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL,"v",a),this._y=b},SVGPathSegLinetoVerticalRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegLinetoVerticalRel.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalRel]"},SVGPathSegLinetoVerticalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y},SVGPathSegLinetoVerticalRel.prototype.clone=function(){return new SVGPathSegLinetoVerticalRel(void 0,this._y)},Object.defineProperty(SVGPathSegLinetoVerticalRel.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegCurvetoCubicSmoothAbs=function(a,b,c,d,e){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS,"S",a),this._x=b,this._y=c,this._x2=d,this._y2=e},SVGPathSegCurvetoCubicSmoothAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegCurvetoCubicSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothAbs]"},SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},SVGPathSegCurvetoCubicSmoothAbs.prototype.clone=function(){return new SVGPathSegCurvetoCubicSmoothAbs(void 0,this._x,this._y,this._x2,this._y2)},Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"x2",{get:function(){return this._x2},set:function(a){this._x2=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"y2",{get:function(){return this._y2},set:function(a){this._y2=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegCurvetoCubicSmoothRel=function(a,b,c,d,e){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL,"s",a),this._x=b,this._y=c,this._x2=d,this._y2=e},SVGPathSegCurvetoCubicSmoothRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegCurvetoCubicSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothRel]"},SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},SVGPathSegCurvetoCubicSmoothRel.prototype.clone=function(){return new SVGPathSegCurvetoCubicSmoothRel(void 0,this._x,this._y,this._x2,this._y2)},Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"x2",{get:function(){return this._x2},set:function(a){this._x2=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"y2",{get:function(){return this._y2},set:function(a){this._y2=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegCurvetoQuadraticSmoothAbs=function(a,b,c){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS,"T",a),this._x=b,this._y=c},SVGPathSegCurvetoQuadraticSmoothAbs.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothAbs]"},SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticSmoothAbs(void 0,this._x,this._y)},Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),a.SVGPathSegCurvetoQuadraticSmoothRel=function(a,b,c){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,"t",a),this._x=b,this._y=c},SVGPathSegCurvetoQuadraticSmoothRel.prototype=Object.create(SVGPathSeg.prototype),SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothRel]"},SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticSmoothRel(void 0,this._x,this._y)},Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype,"x",{get:function(){return this._x},set:function(a){this._x=a,this._segmentChanged()},enumerable:!0}),Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype,"y",{get:function(){return this._y},set:function(a){this._y=a,this._segmentChanged()},enumerable:!0}),SVGPathElement.prototype.createSVGPathSegClosePath=function(){return new SVGPathSegClosePath(void 0)},SVGPathElement.prototype.createSVGPathSegMovetoAbs=function(a,b){return new SVGPathSegMovetoAbs(void 0,a,b)},SVGPathElement.prototype.createSVGPathSegMovetoRel=function(a,b){return new SVGPathSegMovetoRel(void 0,a,b)},SVGPathElement.prototype.createSVGPathSegLinetoAbs=function(a,b){return new SVGPathSegLinetoAbs(void 0,a,b)},SVGPathElement.prototype.createSVGPathSegLinetoRel=function(a,b){return new SVGPathSegLinetoRel(void 0,a,b)},SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs=function(a,b,c,d,e,f){return new SVGPathSegCurvetoCubicAbs(void 0,a,b,c,d,e,f)},SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel=function(a,b,c,d,e,f){return new SVGPathSegCurvetoCubicRel(void 0,a,b,c,d,e,f)},SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs=function(a,b,c,d){return new SVGPathSegCurvetoQuadraticAbs(void 0,a,b,c,d)},SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel=function(a,b,c,d){return new SVGPathSegCurvetoQuadraticRel(void 0,a,b,c,d)},SVGPathElement.prototype.createSVGPathSegArcAbs=function(a,b,c,d,e,f,g){return new SVGPathSegArcAbs(void 0,a,b,c,d,e,f,g)},SVGPathElement.prototype.createSVGPathSegArcRel=function(a,b,c,d,e,f,g){return new SVGPathSegArcRel(void 0,a,b,c,d,e,f,g)},SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs=function(a){return new SVGPathSegLinetoHorizontalAbs(void 0,a)},SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel=function(a){return new SVGPathSegLinetoHorizontalRel(void 0,a)},SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs=function(a){return new SVGPathSegLinetoVerticalAbs(void 0,a)},SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel=function(a){return new SVGPathSegLinetoVerticalRel(void 0,a)},SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs=function(a,b,c,d){return new SVGPathSegCurvetoCubicSmoothAbs(void 0,a,b,c,d)},SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel=function(a,b,c,d){return new SVGPathSegCurvetoCubicSmoothRel(void 0,a,b,c,d)},SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs=function(a,b){return new SVGPathSegCurvetoQuadraticSmoothAbs(void 0,a,b)},SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel=function(a,b){return new SVGPathSegCurvetoQuadraticSmoothRel(void 0,a,b)}),"SVGPathSegList"in a||(a.SVGPathSegList=function(a){this._pathElement=a,this._list=this._parsePath(this._pathElement.getAttribute("d")),this._mutationObserverConfig={attributes:!0,attributeFilter:["d"]},this._pathElementMutationObserver=new MutationObserver(this._updateListFromPathMutations.bind(this)),this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig)},Object.defineProperty(SVGPathSegList.prototype,"numberOfItems",{get:function(){return this._checkPathSynchronizedToList(),this._list.length},enumerable:!0}),Object.defineProperty(SVGPathElement.prototype,"pathSegList",{get:function(){return this._pathSegList||(this._pathSegList=new SVGPathSegList(this)),this._pathSegList},enumerable:!0}),Object.defineProperty(SVGPathElement.prototype,"normalizedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),Object.defineProperty(SVGPathElement.prototype,"animatedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),Object.defineProperty(SVGPathElement.prototype,"animatedNormalizedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),SVGPathSegList.prototype._checkPathSynchronizedToList=function(){this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords())},SVGPathSegList.prototype._updateListFromPathMutations=function(a){if(this._pathElement){var b=!1;a.forEach(function(a){"d"==a.attributeName&&(b=!0)}),b&&(this._list=this._parsePath(this._pathElement.getAttribute("d")))}},SVGPathSegList.prototype._writeListToPath=function(){this._pathElementMutationObserver.disconnect(),this._pathElement.setAttribute("d",SVGPathSegList._pathSegArrayAsString(this._list)),this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig)},SVGPathSegList.prototype.segmentChanged=function(a){this._writeListToPath()},SVGPathSegList.prototype.clear=function(){this._checkPathSynchronizedToList(),this._list.forEach(function(a){a._owningPathSegList=null}),this._list=[],this._writeListToPath()},SVGPathSegList.prototype.initialize=function(a){return this._checkPathSynchronizedToList(),this._list=[a],a._owningPathSegList=this,this._writeListToPath(),a},SVGPathSegList.prototype._checkValidIndex=function(a){if(isNaN(a)||0>a||a>=this.numberOfItems)throw"INDEX_SIZE_ERR"},SVGPathSegList.prototype.getItem=function(a){return this._checkPathSynchronizedToList(),this._checkValidIndex(a),this._list[a]},SVGPathSegList.prototype.insertItemBefore=function(a,b){return this._checkPathSynchronizedToList(),b>this.numberOfItems&&(b=this.numberOfItems),a._owningPathSegList&&(a=a.clone()),this._list.splice(b,0,a),a._owningPathSegList=this,this._writeListToPath(),a},SVGPathSegList.prototype.replaceItem=function(a,b){return this._checkPathSynchronizedToList(),a._owningPathSegList&&(a=a.clone()),this._checkValidIndex(b),this._list[b]=a,a._owningPathSegList=this,this._writeListToPath(),a},SVGPathSegList.prototype.removeItem=function(a){this._checkPathSynchronizedToList(),this._checkValidIndex(a);var b=this._list[a];return this._list.splice(a,1),this._writeListToPath(),b},SVGPathSegList.prototype.appendItem=function(a){return this._checkPathSynchronizedToList(),a._owningPathSegList&&(a=a.clone()),this._list.push(a),a._owningPathSegList=this,this._writeListToPath(),a},SVGPathSegList._pathSegArrayAsString=function(a){var b="",c=!0;return a.forEach(function(a){c?(c=!1,b+=a._asPathString()):b+=" "+a._asPathString()}),b},SVGPathSegList.prototype._parsePath=function(a){if(!a||0==a.length)return[];var b=this,c=function(){this.pathSegList=[]};c.prototype.appendSegment=function(a){this.pathSegList.push(a)};var d=function(a){this._string=a,this._currentIndex=0,this._endIndex=this._string.length,this._previousCommand=SVGPathSeg.PATHSEG_UNKNOWN,this._skipOptionalSpaces()};d.prototype._isCurrentSpace=function(){var a=this._string[this._currentIndex];return" ">=a&&(" "==a||"\n"==a||" "==a||"\r"==a||"\f"==a)},d.prototype._skipOptionalSpaces=function(){for(;this._currentIndex="0"&&"9">=a)&&b!=SVGPathSeg.PATHSEG_CLOSEPATH?b==SVGPathSeg.PATHSEG_MOVETO_ABS?SVGPathSeg.PATHSEG_LINETO_ABS:b==SVGPathSeg.PATHSEG_MOVETO_REL?SVGPathSeg.PATHSEG_LINETO_REL:b:SVGPathSeg.PATHSEG_UNKNOWN},d.prototype.initialCommandIsMoveTo=function(){if(!this.hasMoreData())return!0;var a=this.peekSegmentType();return a==SVGPathSeg.PATHSEG_MOVETO_ABS||a==SVGPathSeg.PATHSEG_MOVETO_REL},d.prototype._parseNumber=function(){var a=0,b=0,c=1,d=0,e=1,f=1,g=this._currentIndex;if(this._skipOptionalSpaces(),this._currentIndex"9")&&"."!=this._string.charAt(this._currentIndex))){for(var h=this._currentIndex;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)this._currentIndex++;if(this._currentIndex!=h)for(var i=this._currentIndex-1,j=1;i>=h;)b+=j*(this._string.charAt(i--)-"0"),j*=10;if(this._currentIndex=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9")return;for(;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)d+=(this._string.charAt(this._currentIndex++)-"0")*(c*=.1)}if(this._currentIndex!=g&&this._currentIndex+1=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9")return;for(;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)a*=10,a+=this._string.charAt(this._currentIndex)-"0",this._currentIndex++}var k=b+d;if(k*=e,a&&(k*=Math.pow(10,f*a)),g!=this._currentIndex)return this._skipOptionalSpacesOrDelimiter(),k}},d.prototype._parseArcFlag=function(){if(!(this._currentIndex>=this._endIndex)){var a=!1,b=this._string.charAt(this._currentIndex++);if("0"==b)a=!1;else{if("1"!=b)return;a=!0}return this._skipOptionalSpacesOrDelimiter(),a}},d.prototype.parseSegment=function(){var a=this._string[this._currentIndex],c=this._pathSegTypeFromChar(a);if(c==SVGPathSeg.PATHSEG_UNKNOWN){if(this._previousCommand==SVGPathSeg.PATHSEG_UNKNOWN)return null;if(c=this._nextCommandHelper(a,this._previousCommand),c==SVGPathSeg.PATHSEG_UNKNOWN)return null}else this._currentIndex++;switch(this._previousCommand=c,c){case SVGPathSeg.PATHSEG_MOVETO_REL:return new SVGPathSegMovetoRel(b,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_MOVETO_ABS:return new SVGPathSegMovetoAbs(b,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_REL:return new SVGPathSegLinetoRel(b,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_ABS:return new SVGPathSegLinetoAbs(b,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:return new SVGPathSegLinetoHorizontalRel(b,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:return new SVGPathSegLinetoHorizontalAbs(b,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:return new SVGPathSegLinetoVerticalRel(b,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:return new SVGPathSegLinetoVerticalAbs(b,this._parseNumber());case SVGPathSeg.PATHSEG_CLOSEPATH:return this._skipOptionalSpaces(),new SVGPathSegClosePath(b);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:var d={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicRel(b,d.x,d.y,d.x1,d.y1,d.x2,d.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:var d={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicAbs(b,d.x,d.y,d.x1,d.y1,d.x2,d.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:var d={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicSmoothRel(b,d.x,d.y,d.x2,d.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:var d={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicSmoothAbs(b,d.x,d.y,d.x2,d.y2);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:var d={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoQuadraticRel(b,d.x,d.y,d.x1,d.y1);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:var d={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoQuadraticAbs(b,d.x,d.y,d.x1,d.y1);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:return new SVGPathSegCurvetoQuadraticSmoothRel(b,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:return new SVGPathSegCurvetoQuadraticSmoothAbs(b,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_ARC_REL:var d={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegArcRel(b,d.x,d.y,d.x1,d.y1,d.arcAngle,d.arcLarge,d.arcSweep);case SVGPathSeg.PATHSEG_ARC_ABS:var d={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegArcAbs(b,d.x,d.y,d.x1,d.y1,d.arcAngle,d.arcLarge,d.arcSweep);default:throw"Unknown path seg type."}};var e=new c,f=new d(a);if(!f.initialCommandIsMoveTo())return[];for(;f.hasMoreData();){var g=f.parseSegment();if(!g)return[];e.appendSegment(g)}return e.pathSegList})}(),"function"==typeof define&&define.amd?define("c3",["d3"],function(){return k}):"undefined"!=typeof exports&&"undefined"!=typeof module?module.exports=k:a.c3=k}(window); \ No newline at end of file diff --git a/Modules/C3js/resource/d3.min.js b/Modules/C3js/resource/d3.min.js new file mode 100644 index 0000000000..166487309a --- /dev/null +++ b/Modules/C3js/resource/d3.min.js @@ -0,0 +1,5 @@ +!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ +r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], +shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; +if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); \ No newline at end of file diff --git a/Modules/C3js/resource/empty.html b/Modules/C3js/resource/empty.html new file mode 100644 index 0000000000..0051579c81 --- /dev/null +++ b/Modules/C3js/resource/empty.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/Modules/C3js/src/C3Data.cpp b/Modules/C3js/src/C3Data.cpp new file mode 100644 index 0000000000..de0a9c58f9 --- /dev/null +++ b/Modules/C3js/src/C3Data.cpp @@ -0,0 +1,23 @@ +/*=================================================================== + +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 + +void QmitkC3Data::ClearData() +{ + this->m_Frequency.clear(); + this->m_Measurement.clear(); +} diff --git a/Modules/C3js/src/QmitkC3Data.cpp b/Modules/C3js/src/QmitkC3Data.cpp new file mode 100644 index 0000000000..86438248ad --- /dev/null +++ b/Modules/C3js/src/QmitkC3Data.cpp @@ -0,0 +1,40 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include + +QmitkC3Data::QmitkC3Data() +{ +} + +QmitkC3Data::QmitkC3Data(HistogramType::ConstPointer histogram) + : m_Histogram(histogram), + m_UseLineChart(false), + m_ShowSubchart(true) +{ +} + +void QmitkC3Data::SetAppearance(bool UseLineChart = false, bool ShowSubChart = true) +{ + m_UseLineChart = UseLineChart; + m_ShowSubchart = ShowSubChart; +} + +void QmitkC3Data::ClearData() +{ + this->m_YData.clear(); + this->m_XData.clear(); +} \ No newline at end of file diff --git a/Modules/C3js/src/QmitkC3jsWidget.cpp b/Modules/C3js/src/QmitkC3jsWidget.cpp new file mode 100644 index 0000000000..1bee6b8939 --- /dev/null +++ b/Modules/C3js/src/QmitkC3jsWidget.cpp @@ -0,0 +1,285 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include +#include +#include +#include + +#include + +#include + +#include "mitkImageTimeSelector.h" + +class QmitkC3jsWidget::Impl final +{ +public: + explicit Impl(QWidget* parent); + ~Impl(); + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + QWebChannel* GetWebChannel(); + + void ClearJavaScriptChart(); + void initializeJavaScriptChart(); + void callJavaScriptFuntion(QString command); + + QmitkC3Data* GetC3Data() { return &m_c3Data; }; + + mitk::Image::Pointer GetImage() { return m_Image; }; const + void SetImage(const mitk::Image::Pointer image) { m_Image = image; }; + + mitk::PlanarFigure::ConstPointer GetPlanarFigure() { return m_PlanarFigure; }; const + void SetPlanarFigure(const mitk::PlanarFigure::ConstPointer planarFigure) { m_PlanarFigure = planarFigure; }; + +private: + QWidget* m_Parent; + QWebChannel* m_WebChannel; + QWebEngineView* m_WebEngineView; + + QmitkC3Data m_c3Data; + + /** + * \brief Reference image. + * + * Holds the image to calculate an intensity profile. + */ + mitk::Image::Pointer m_Image; + + /** + * \brief Pathelement. + * + * Holds a not closed planar figure to calculate an intensity profile. + */ + mitk::PlanarFigure::ConstPointer m_PlanarFigure; +}; + +QmitkC3jsWidget::Impl::Impl(QWidget* parent) + : m_WebChannel(new QWebChannel(parent)), + m_WebEngineView(new QWebEngineView(parent)), + m_Parent(parent) +{ + //disable context menu for QWebEngineView + m_WebEngineView->setContextMenuPolicy(Qt::NoContextMenu); + + //Set the webengineview to an initial empty page. The actual chart will be loaded once the data is calculated. + m_WebEngineView->setUrl(QUrl(QStringLiteral("qrc:///C3js/empty.html"))); + m_WebEngineView->page()->setWebChannel(m_WebChannel); + + connect( m_WebEngineView, SIGNAL( loadFinished(bool) ), parent, SLOT( OnLoadFinished(bool) ) ); + + auto layout = new QGridLayout(parent); + layout->setMargin(0); + layout->addWidget(m_WebEngineView); + + parent->setLayout(layout); +} + +QmitkC3jsWidget::Impl::~Impl() +{ +} + +QWebChannel* QmitkC3jsWidget::Impl::GetWebChannel() +{ + return m_WebChannel; +} + +QmitkC3jsWidget::QmitkC3jsWidget(QWidget* parent) + : QWidget(parent), + m_Impl(new Impl(this)) +{ + m_Statistics = mitk::ImageStatisticsCalculator::StatisticsContainer::New(); +} + +void QmitkC3jsWidget::Impl::callJavaScriptFuntion(QString command) +{ + m_WebEngineView->page()->runJavaScript(command); +} + +void QmitkC3jsWidget::Impl::ClearJavaScriptChart() +{ + m_WebEngineView->setUrl(QUrl(QStringLiteral("qrc:///C3js/empty.html"))); +} + +void QmitkC3jsWidget::Impl::initializeJavaScriptChart() +{ + m_WebChannel->registerObject(QStringLiteral("initValues"), &m_c3Data); + m_WebEngineView->load(QUrl(QStringLiteral("qrc:///C3js/QmitkC3jsWidget.html"))); +} + +QmitkC3jsWidget::QmitkC3jsWidget(const QString& id, QObject* object, QWidget* parent) + : QWidget(parent), + m_Impl(new Impl(this)) +{ + if (!id.isEmpty() && object != nullptr) + m_Impl->GetWebChannel()->registerObject(id, object); + + m_Statistics = mitk::ImageStatisticsCalculator::StatisticsContainer::New(); +} + +QmitkC3jsWidget::~QmitkC3jsWidget() +{ + delete m_Impl; +} + +void QmitkC3jsWidget::OnLoadFinished(bool isLoadSuccessfull) +{ + emit PageSuccessfullyLoaded(); +} + +void QmitkC3jsWidget::TransformView(QString transformTo) +{ + QString command = QString("transformView('" + transformTo + "')"); + m_Impl->callJavaScriptFuntion(command); +} + +void QmitkC3jsWidget::SendCommand(QString command) +{ + m_Impl->callJavaScriptFuntion(command); +} + +void QmitkC3jsWidget::SetAppearance(bool useLineChart, bool showSubChart) +{ + this->m_Impl->GetC3Data()->SetAppearance(useLineChart, showSubChart); +} + +// method to expose data to JavaScript by using properties +void QmitkC3jsWidget::ComputeHistogram(HistogramType* histogram, bool useLineChart, bool showSubChart) +{ + this->m_Impl->GetC3Data()->SetHistogram(histogram); + + SetAppearance(useLineChart, showSubChart); + HistogramConstIteratorType startIt = this->m_Impl->GetC3Data()->GetHistogram()->End(); + HistogramConstIteratorType endIt = this->m_Impl->GetC3Data()->GetHistogram()->End(); + HistogramConstIteratorType it = this->m_Impl->GetC3Data()->GetHistogram()->Begin(); + + + + //Clear old data befor loading new data. + this->m_Impl->GetC3Data()->ClearData(); + + unsigned int i = 0; + bool firstValue = false; + // removes frequencies of 0, which are outside the first and last bin + for (; it != this->m_Impl->GetC3Data()->GetHistogram()->End(); ++it) + { + if (it.GetFrequency() > 0.0) + { + endIt = it; + if (!firstValue) + { + firstValue = true; + startIt = it; + } + } + } + ++endIt; + // generating Lists of measurement and frequencies + for (it = startIt; it != endIt; ++it, ++i) + { + QVariant frequency = QVariant::fromValue(it.GetFrequency()); + QVariant measurement = it.GetMeasurementVector()[0]; + this->m_Impl->GetC3Data()->GetYDataPointer()->insert(i, frequency); + this->m_Impl->GetC3Data()->GetXDataPointer()->insert(i, measurement); + } + + m_Impl->initializeJavaScriptChart(); +} + +void QmitkC3jsWidget::ComputeIntensityProfile(unsigned int timeStep, bool computeStatistics) +{ + this->ClearHistogram(); + //m_Impl->GetC3Data()->ClearData(); + //m_ParametricPath->Initialize(); + + if (m_Impl->GetPlanarFigure().IsNull()) + { + mitkThrow() << "PlanarFigure not set!"; + } + + if (m_Impl->GetImage().IsNull()) + { + mitkThrow() << "Image not set!"; + } + + mitk::Image::Pointer image; + + if (m_Impl->GetImage()->GetDimension() == 4) + { + mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); + timeSelector->SetInput(m_Impl->GetImage()); + timeSelector->SetTimeNr(timeStep); + timeSelector->Update(); + + image = timeSelector->GetOutput(); + } + else + { + image = m_Impl->GetImage(); + } + + mitk::IntensityProfile::Pointer intensityProfile = mitk::ComputeIntensityProfile( + image, const_cast(m_Impl->GetPlanarFigure().GetPointer())); + + //m_Frequency.clear(); + //m_Measurement.clear(); + + int i = -1; + mitk::IntensityProfile::ConstIterator end = intensityProfile->End(); + + for (mitk::IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) + { + m_Impl->GetC3Data()->GetYDataPointer()->push_back(it.GetMeasurementVector()[0]); + //m_Impl->GetC3Data()->GetFrequencyPointer()->push_back(50000); + m_Impl->GetC3Data()->GetXDataPointer()->push_back(++i); + } + + if (computeStatistics) + { + mitk::ComputeIntensityProfileStatistics(intensityProfile, m_Statistics); + } + + m_Impl->initializeJavaScriptChart(); +} + +void QmitkC3jsWidget::ClearHistogram() +{ + m_Impl->GetC3Data()->ClearData(); + m_Impl->ClearJavaScriptChart(); +} + +mitk::Image::Pointer QmitkC3jsWidget::GetImage() const +{ + return m_Impl->GetImage(); +} + +void QmitkC3jsWidget::SetImage(const mitk::Image::Pointer image) +{ + m_Impl->SetImage(image); +} + +mitk::PlanarFigure::ConstPointer QmitkC3jsWidget::GetPlanarFigure() const +{ + return m_Impl->GetPlanarFigure(); +} + +void QmitkC3jsWidget::SetPlanarFigure(const mitk::PlanarFigure::ConstPointer planarFigure) +{ + m_Impl->SetPlanarFigure(planarFigure); +} \ No newline at end of file diff --git a/Modules/Core/include/mitkDisplayInteractor.h b/Modules/Core/include/mitkDisplayInteractor.h index 12127371e3..5be2331035 100644 --- a/Modules/Core/include/mitkDisplayInteractor.h +++ b/Modules/Core/include/mitkDisplayInteractor.h @@ -1,262 +1,276 @@ /*=================================================================== 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 mitkDisplayInteractor_h #define mitkDisplayInteractor_h #include "mitkInteractionEventObserver.h" #include namespace mitk { /** *\class DisplayInteractor *@brief Observer that manages the interaction with the display. * * This includes the interaction of Zooming, Panning, Scrolling and adjusting the LevelWindow. * * @ingroup Interaction **/ /** * Inherits from mitk::InteractionEventObserver since it doesn't alter any data (only their representation), * and its actions cannot be associated with a DataNode. Also inherits from EventStateMachine */ class MITKCORE_EXPORT DisplayInteractor : public EventStateMachine, public InteractionEventObserver { public: mitkClassMacro(DisplayInteractor, EventStateMachine) itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** * By this function the Observer gets notified about new events. * Here it is adapted to pass the events to the state machine in order to use * its infrastructure. * It also checks if event is to be accepted when it already has been processed by a DataInteractor. */ virtual void Notify(InteractionEvent *interactionEvent, bool isHandled) override; protected: DisplayInteractor(); virtual ~DisplayInteractor(); /** * Derived function. * Connects the action names used in the state machine pattern with functions implemented within * this InteractionEventObserver. This is only necessary here because the events are processed by the state machine. */ void ConnectActionsAndFunctions() override; /** * Derived function. * Is executed when config object is set / changed. * Here it is used to read out the parameters set in the configuration file, * and set the member variables accordingly. */ virtual void ConfigurationChanged() override; /** * Derived function. * Is executed when config object is set / changed. * Here it is used to read out the parameters set in the configuration file, * and set the member variables accordingly. */ virtual bool FilterEvents(InteractionEvent *interactionEvent, DataNode *dataNode) override; virtual bool CheckPositionEvent(const InteractionEvent *interactionEvent); virtual bool CheckRotationPossible(const InteractionEvent *interactionEvent); virtual bool CheckSwivelPossible(const InteractionEvent *interactionEvent); /** * \brief Initializes an interaction, saves the pointers start position for further reference. */ virtual void Init(StateMachineAction *, InteractionEvent *); /** * \brief Performs panning of the data set in the render window. */ virtual void Move(StateMachineAction *, InteractionEvent *); /** * \brief Sets crosshair at clicked position* */ virtual void SetCrosshair(StateMachineAction *, InteractionEvent *); /** * \brief Performs zooming relative to mouse/pointer movement. * * Behavior is determined by \see m_ZoomDirection and \see m_ZoomFactor. * */ virtual void Zoom(StateMachineAction *, InteractionEvent *); /** * \brief Performs scrolling relative to mouse/pointer movement. * * Behavior is determined by \see m_ScrollDirection and \see m_AutoRepeat. * */ virtual void Scroll(StateMachineAction *, InteractionEvent *); /** * \brief Scrolls one layer up */ virtual void ScrollOneDown(StateMachineAction *, InteractionEvent *); /** * \brief Scrolls one layer down */ virtual void ScrollOneUp(StateMachineAction *, InteractionEvent *); /** * \brief Adjusts the level windows relative to mouse/pointer movement. */ virtual void AdjustLevelWindow(StateMachineAction *, InteractionEvent *); /** * \brief Starts crosshair rotation */ virtual void StartRotation(StateMachineAction *, InteractionEvent *); /** * \brief Ends crosshair rotation */ virtual void EndRotation(StateMachineAction *, InteractionEvent *); /** * \brief */ virtual void Rotate(StateMachineAction *, InteractionEvent *event); virtual void Swivel(StateMachineAction *, InteractionEvent *event); /** * \brief Updates the Statusbar information with the information about the clicked position */ virtual void UpdateStatusbar(StateMachineAction *, InteractionEvent *event); /** * \brief Method to retrieve bool-value for given property from string-property * in given propertylist. */ bool GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char *propertyName, bool defaultValue); // Typedefs typedef std::vector SNCVector; private: mitk::DataNode::Pointer GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes, mitk::Point3D worldposition, BaseRenderer *ren); + /** + * @brief UpdateStatusBar + * @param image3D + * @param idx + * @param time + * @param component If the PixelType of image3D is a vector (for example a 2D velocity vector), then only one of the vector components can be + * displayed at once. Setting this parameter will determine which of the vector's components will be used to determine the displayed PixelValue. + * Set this to 0 for scalar images + */ + void UpdateStatusBar(itk::SmartPointer image3D, itk::Index<3> idx, TimeStepType time=0, int component=0); + + /** * \brief Coordinate of the pointer at begin of an interaction translated to mm unit */ mitk::Point2D m_StartCoordinateInMM; /** * \brief Coordinate of the pointer in the last step within an interaction. */ mitk::Point2D m_LastDisplayCoordinate; /** * \brief Coordinate of the pointer in the last step within an interaction translated to mm unit */ mitk::Point2D m_LastCoordinateInMM; /** * \brief Current coordinates of the pointer. */ mitk::Point2D m_CurrentDisplayCoordinate; + + /** * \brief Modifier that defines how many slices are scrolled per pixel that the mouse has moved * * This modifier defines how many slices the scene is scrolled per pixel that the mouse cursor has moved. * By default the modifier is 4. This means that when the user moves the cursor by 4 pixels in Y-direction * the scene is scrolled by one slice. If the user has moved the the cursor by 20 pixels, the scene is * scrolled by 5 slices. * * If the cursor has moved less than m_IndexToSliceModifier pixels the scene is scrolled by one slice. */ int m_IndexToSliceModifier; /** Defines behavior at end of data set. * If set to true it will restart at end of data set from the beginning. */ bool m_AutoRepeat; /** * Defines scroll behavior. * Default is up/down movement of pointer performs scrolling */ std::string m_ScrollDirection; /** * Defines how the axis of interaction influences scroll behavior. */ bool m_InvertScrollDirection; /** * Defines scroll behavior. * Default is up/down movement of pointer performs zooming */ std::string m_ZoomDirection; /** * Defines how the axis of interaction influences zoom behavior. */ bool m_InvertZoomDirection; /** * Defines how the axis of interaction influences move behavior. */ bool m_InvertMoveDirection; /** * Defines level/window behavior. * Default is left/right movement of pointer modifies the level. */ std::string m_LevelDirection; /** * Defines how the axis of interaction influences level/window behavior. */ bool m_InvertLevelWindowDirection; /** * Determines if the Observer reacts to events that already have been processed by a DataInteractor. * The default value is false. */ bool m_AlwaysReact; /** * Factor to adjust zooming speed. */ float m_ZoomFactor; ///// Members to deal with rotating slices /** * @brief m_LinkPlanes Determines if angle between crosshair remains fixed when rotating */ bool m_LinkPlanes; SNCVector m_RotatableSNCs; /// all SNCs that currently have CreatedWorldGeometries, that can be rotated. SNCVector m_SNCsToBeRotated; /// all SNCs that will be rotated (exceptions are the ones parallel to the one being clicked) Point3D m_LastCursorPosition; /// used for calculation of the rotation angle Point3D m_CenterOfRotation; /// used for calculation of the rotation angle Point2D m_ReferenceCursor; Vector3D m_RotationPlaneNormal; Vector3D m_RotationPlaneXVector; Vector3D m_RotationPlaneYVector; Vector3D m_PreviousRotationAxis; ScalarType m_PreviousRotationAngle; }; } #endif diff --git a/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp b/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp index e32d8b95c8..ec9ccb5226 100644 --- a/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp @@ -1,928 +1,929 @@ /*=================================================================== 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 "mitkDisplayInteractor.h" #include "mitkBaseRenderer.h" #include "mitkCameraController.h" #include "mitkInteractionPositionEvent.h" #include "mitkPropertyList.h" #include #include #include // level window #include "mitkLevelWindow.h" #include "mitkLevelWindowProperty.h" #include "mitkLine.h" #include "mitkNodePredicateDataType.h" #include "mitkStandaloneDataStorage.h" #include "vtkRenderWindowInteractor.h" // Rotation #include "mitkInteractionConst.h" #include "rotate_cursor.xpm" #include #include // #include "mitkImage.h" #include "mitkImagePixelReadAccessor.h" #include "mitkPixelTypeMultiplex.h" #include "mitkStatusBar.h" void mitk::DisplayInteractor::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled || m_AlwaysReact) { this->HandleEvent(interactionEvent, NULL); } } void mitk::DisplayInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("check_position_event", CheckPositionEvent); CONNECT_CONDITION("check_can_rotate", CheckRotationPossible); CONNECT_CONDITION("check_can_swivel", CheckSwivelPossible); CONNECT_FUNCTION("init", Init); CONNECT_FUNCTION("move", Move); CONNECT_FUNCTION("zoom", Zoom); CONNECT_FUNCTION("scroll", Scroll); CONNECT_FUNCTION("ScrollOneDown", ScrollOneDown); CONNECT_FUNCTION("ScrollOneUp", ScrollOneUp); CONNECT_FUNCTION("levelWindow", AdjustLevelWindow); CONNECT_FUNCTION("setCrosshair", SetCrosshair); CONNECT_FUNCTION("updateStatusbar", UpdateStatusbar) + CONNECT_FUNCTION("startRotation", StartRotation); CONNECT_FUNCTION("endRotation", EndRotation); CONNECT_FUNCTION("rotate", Rotate); CONNECT_FUNCTION("swivel", Swivel); } mitk::DisplayInteractor::DisplayInteractor() : m_IndexToSliceModifier(4), m_AutoRepeat(false), m_InvertScrollDirection(false), m_InvertZoomDirection(false), m_InvertMoveDirection(false), m_InvertLevelWindowDirection(false), m_AlwaysReact(false), m_ZoomFactor(2), m_LinkPlanes(true) { m_StartCoordinateInMM.Fill(0); m_LastDisplayCoordinate.Fill(0); m_LastCoordinateInMM.Fill(0); m_CurrentDisplayCoordinate.Fill(0); } mitk::DisplayInteractor::~DisplayInteractor() { } bool mitk::DisplayInteractor::CheckPositionEvent(const InteractionEvent *interactionEvent) { const InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == NULL) { return false; } return true; } bool mitk::DisplayInteractor::CheckRotationPossible(const mitk::InteractionEvent *interactionEvent) { // Decide between moving and rotation slices. /* Detailed logic: 1. Find the SliceNavigationController that has sent the event: this one defines our rendering plane and will NOT be rotated. Needs not even be counted or checked. 2. Inspect every other SliceNavigationController - calculate the line intersection of this SliceNavigationController's plane with our rendering plane - if there is NO interesection, ignore and continue - IF there is an intersection - check the mouse cursor's distance from that line. 0. if the line is NOT near the cursor, remember the plane as "one of the other planes" (which can be rotated in "locked" mode) 1. on first line near the cursor, just remember this intersection line as THE other plane that we want to rotate 2. on every consecutive line near the cursor, check if the line is geometrically identical to the line that we want to rotate - if yes, we just push this line to the "other" lines and rotate it along - if no, then we have a situation where the mouse is near two other lines (e.g. crossing point) and don't want to rotate */ const InteractionPositionEvent *posEvent = dynamic_cast(interactionEvent); if (posEvent == nullptr) return false; BaseRenderer *clickedRenderer = posEvent->GetSender(); const PlaneGeometry *ourViewportGeometry = (clickedRenderer->GetCurrentWorldPlaneGeometry()); if (!ourViewportGeometry) return false; Point3D cursorPosition = posEvent->GetPositionInWorld(); const PlaneGeometry *geometryToBeRotated = NULL; // this one is under the mouse cursor const PlaneGeometry *anyOtherGeometry = NULL; // this is also visible (for calculation of intersection ONLY) Line3D intersectionLineWithGeometryToBeRotated; bool hitMultipleLines(false); m_SNCsToBeRotated.clear(); const double threshholdDistancePixels = 12.0; auto renWindows = interactionEvent->GetSender()->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; const PlaneGeometry *otherRenderersRenderPlane = snc->GetCurrentPlaneGeometry(); if (otherRenderersRenderPlane == NULL) continue; // ignore, we don't see a plane // check if there is an intersection Line3D intersectionLine; // between rendered/clicked geometry and the one being analyzed if (!ourViewportGeometry->IntersectionLine(otherRenderersRenderPlane, intersectionLine)) { continue; // we ignore this plane, it's parallel to our plane } // check distance from intersection line double distanceFromIntersectionLine = intersectionLine.Distance(cursorPosition); // far away line, only remember for linked rotation if necessary if (distanceFromIntersectionLine > threshholdDistancePixels) { anyOtherGeometry = otherRenderersRenderPlane; // we just take the last one, so overwrite each iteration (we just // need some crossing point) // TODO what about multiple crossings? NOW we have undefined behavior / random crossing point is used if (m_LinkPlanes) { m_SNCsToBeRotated.push_back(snc); } } else // close to cursor { if (geometryToBeRotated == NULL) // first one close to the cursor { geometryToBeRotated = otherRenderersRenderPlane; intersectionLineWithGeometryToBeRotated = intersectionLine; m_SNCsToBeRotated.push_back(snc); } else { // compare to the line defined by geometryToBeRotated: if identical, just rotate this otherRenderersRenderPlane // together with the primary one // if different, DON'T rotate if (intersectionLine.IsParallel(intersectionLineWithGeometryToBeRotated) && intersectionLine.Distance(intersectionLineWithGeometryToBeRotated.GetPoint1()) < mitk::eps) { m_SNCsToBeRotated.push_back(snc); } else { hitMultipleLines = true; } } } } bool moveSlices(true); if (geometryToBeRotated && anyOtherGeometry && ourViewportGeometry && !hitMultipleLines) { // assure all three are valid, so calculation of center of rotation can be done moveSlices = false; } // question in state machine is: "rotate?" if (moveSlices) // i.e. NOT rotate { return false; } else { // we DO have enough information for rotation m_LastCursorPosition = intersectionLineWithGeometryToBeRotated.Project( cursorPosition); // remember where the last cursor position ON THE LINE has been observed if (anyOtherGeometry->IntersectionPoint( intersectionLineWithGeometryToBeRotated, m_CenterOfRotation)) // find center of rotation by intersection with any of the OTHER lines { return true; } else { return false; } } return false; } bool mitk::DisplayInteractor::CheckSwivelPossible(const mitk::InteractionEvent *interactionEvent) { const ScalarType ThresholdDistancePixels = 6.0; // Decide between moving and rotation: if we're close to the crossing // point of the planes, moving mode is entered, otherwise // rotation/swivel mode const InteractionPositionEvent *posEvent = dynamic_cast(interactionEvent); BaseRenderer *renderer = interactionEvent->GetSender(); if (!posEvent || !renderer) return false; const Point3D &cursor = posEvent->GetPositionInWorld(); m_SNCsToBeRotated.clear(); const PlaneGeometry *clickedGeometry(NULL); const PlaneGeometry *otherGeometry1(NULL); const PlaneGeometry *otherGeometry2(NULL); auto renWindows = interactionEvent->GetSender()->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; // unsigned int slice = (*iter)->GetSlice()->GetPos(); // unsigned int time = (*iter)->GetTime()->GetPos(); const PlaneGeometry *planeGeometry = snc->GetCurrentPlaneGeometry(); if (!planeGeometry) continue; if (snc == renderer->GetSliceNavigationController()) { clickedGeometry = planeGeometry; m_SNCsToBeRotated.push_back(snc); } else { if (otherGeometry1 == NULL) { otherGeometry1 = planeGeometry; } else { otherGeometry2 = planeGeometry; } if (m_LinkPlanes) { // If planes are linked, apply rotation to all planes m_SNCsToBeRotated.push_back(snc); } } } mitk::Line3D line; mitk::Point3D point; if ((clickedGeometry != NULL) && (otherGeometry1 != NULL) && (otherGeometry2 != NULL) && clickedGeometry->IntersectionLine(otherGeometry1, line) && otherGeometry2->IntersectionPoint(line, point)) { m_CenterOfRotation = point; if (m_CenterOfRotation.EuclideanDistanceTo(cursor) < ThresholdDistancePixels) { return false; } else { m_ReferenceCursor = posEvent->GetPointerPositionOnScreen(); // Get main axes of rotation plane and store it for rotation step m_RotationPlaneNormal = clickedGeometry->GetNormal(); ScalarType xVector[] = {1.0, 0.0, 0.0}; ScalarType yVector[] = {0.0, 1.0, 0.0}; clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(xVector), m_RotationPlaneXVector); clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(yVector), m_RotationPlaneYVector); m_RotationPlaneNormal.Normalize(); m_RotationPlaneXVector.Normalize(); m_RotationPlaneYVector.Normalize(); m_PreviousRotationAxis.Fill(0.0); m_PreviousRotationAxis[2] = 1.0; m_PreviousRotationAngle = 0.0; return true; } } else { return false; } return false; } void mitk::DisplayInteractor::Init(StateMachineAction *, InteractionEvent *interactionEvent) { InteractionPositionEvent *positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); m_CurrentDisplayCoordinate = m_LastDisplayCoordinate; positionEvent->GetSender()->DisplayToPlane(m_LastDisplayCoordinate, m_StartCoordinateInMM); m_LastCoordinateInMM = m_StartCoordinateInMM; } void mitk::DisplayInteractor::Move(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer *sender = interactionEvent->GetSender(); InteractionPositionEvent *positionEvent = static_cast(interactionEvent); float invertModifier = -1.0; if (m_InvertMoveDirection) { invertModifier = 1.0; } // perform translation Vector2D moveVector = (positionEvent->GetPointerPositionOnScreen() - m_LastDisplayCoordinate) * invertModifier; moveVector *= sender->GetScaleFactorMMPerDisplayUnit(); sender->GetCameraController()->MoveBy(moveVector); sender->GetRenderingManager()->RequestUpdate(sender->GetRenderWindow()); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); } void mitk::DisplayInteractor::SetCrosshair(mitk::StateMachineAction *, mitk::InteractionEvent *interactionEvent) { const BaseRenderer::Pointer sender = interactionEvent->GetSender(); auto renWindows = sender->GetRenderingManager()->GetAllRegisteredRenderWindows(); InteractionPositionEvent *positionEvent = static_cast(interactionEvent); Point3D pos = positionEvent->GetPositionInWorld(); for (auto renWin : renWindows) { if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard2D && renWin != sender->GetRenderWindow()) BaseRenderer::GetInstance(renWin)->GetSliceNavigationController()->SelectSliceByPoint(pos); } } void mitk::DisplayInteractor::Zoom(StateMachineAction *, InteractionEvent *interactionEvent) { const BaseRenderer::Pointer sender = interactionEvent->GetSender(); InteractionPositionEvent *positionEvent = static_cast(interactionEvent); float factor = 1.0; float distance = 0; if (m_ZoomDirection == "updown") { distance = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; } else { distance = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; } if (m_InvertZoomDirection) { distance *= -1.0; } // set zooming speed if (distance < 0.0) { factor = 1.0 / m_ZoomFactor; } else if (distance > 0.0) { factor = 1.0 * m_ZoomFactor; } if (factor != 1.0) { sender->GetCameraController()->Zoom(factor, m_StartCoordinateInMM); sender->GetRenderingManager()->RequestUpdate(sender->GetRenderWindow()); } m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); } void mitk::DisplayInteractor::Scroll(StateMachineAction *, InteractionEvent *interactionEvent) { InteractionPositionEvent *positionEvent = static_cast(interactionEvent); mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (sliceNaviController) { int delta = 0; // Scrolling direction if (m_ScrollDirection == "updown") { delta = static_cast(m_LastDisplayCoordinate[1] - positionEvent->GetPointerPositionOnScreen()[1]); } else { delta = static_cast(m_LastDisplayCoordinate[0] - positionEvent->GetPointerPositionOnScreen()[0]); } if (m_InvertScrollDirection) { delta *= -1; } // Set how many pixels the mouse has to be moved to scroll one slice // if we moved less than 'm_IndexToSliceModifier' pixels slice ONE slice only if (delta > 0 && delta < m_IndexToSliceModifier) { delta = m_IndexToSliceModifier; } else if (delta < 0 && delta > -m_IndexToSliceModifier) { delta = -m_IndexToSliceModifier; } delta /= m_IndexToSliceModifier; int newPos = sliceNaviController->GetSlice()->GetPos() + delta; // if auto repeat is on, start at first slice if you reach the last slice and vice versa int maxSlices = sliceNaviController->GetSlice()->GetSteps(); if (m_AutoRepeat) { while (newPos < 0) { newPos += maxSlices; } while (newPos >= maxSlices) { newPos -= maxSlices; } } else { // if the new slice is below 0 we still show slice 0 // due to the stepper using unsigned int we have to do this ourselves if (newPos < 1) { newPos = 0; } } // set the new position sliceNaviController->GetSlice()->SetPos(newPos); m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); } } void mitk::DisplayInteractor::ScrollOneDown(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (!sliceNaviController->GetSliceLocked()) { mitk::Stepper *stepper = sliceNaviController->GetSlice(); if (stepper->GetSteps() <= 1) { stepper = sliceNaviController->GetTime(); } stepper->Next(); } } void mitk::DisplayInteractor::ScrollOneUp(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (!sliceNaviController->GetSliceLocked()) { mitk::Stepper *stepper = sliceNaviController->GetSlice(); if (stepper->GetSteps() <= 1) { stepper = sliceNaviController->GetTime(); } stepper->Previous(); } } void mitk::DisplayInteractor::AdjustLevelWindow(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer::Pointer sender = interactionEvent->GetSender(); InteractionPositionEvent *positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // search for active image mitk::DataStorage::Pointer storage = sender->GetDataStorage(); mitk::DataNode::Pointer node = NULL; mitk::DataStorage::SetOfObjects::ConstPointer allImageNodes = storage->GetSubset(mitk::NodePredicateDataType::New("Image")); for (unsigned int i = 0; i < allImageNodes->size(); i++) { bool isActiveImage = false; bool propFound = allImageNodes->at(i)->GetBoolProperty("imageForLevelWindow", isActiveImage); if (propFound && isActiveImage) { node = allImageNodes->at(i); continue; } } if (node.IsNull()) { node = storage->GetNode(mitk::NodePredicateDataType::New("Image")); } if (node.IsNull()) { return; } mitk::LevelWindow lv = mitk::LevelWindow(); node->GetLevelWindow(lv); ScalarType level = lv.GetLevel(); ScalarType window = lv.GetWindow(); int levelIndex = 0; int windowIndex = 1; if (m_LevelDirection != "leftright") { levelIndex = 1; windowIndex = 0; } int directionModifier = 1; if (m_InvertLevelWindowDirection) { directionModifier = -1; } // calculate adjustments from mouse movements level += (m_CurrentDisplayCoordinate[levelIndex] - m_LastDisplayCoordinate[levelIndex]) * static_cast(2) * directionModifier; window += (m_CurrentDisplayCoordinate[windowIndex] - m_LastDisplayCoordinate[windowIndex]) * static_cast(2) * directionModifier; lv.SetLevelWindow(level, window); dynamic_cast(node->GetProperty("levelwindow"))->SetLevelWindow(lv); sender->GetRenderingManager()->RequestUpdateAll(); } void mitk::DisplayInteractor::StartRotation(mitk::StateMachineAction *, mitk::InteractionEvent *) { this->SetMouseCursor(rotate_cursor_xpm, 0, 0); } void mitk::DisplayInteractor::EndRotation(mitk::StateMachineAction *, mitk::InteractionEvent *) { this->ResetMouseCursor(); } void mitk::DisplayInteractor::Rotate(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const InteractionPositionEvent *posEvent = dynamic_cast(event); if (posEvent == nullptr) return; Point3D cursor = posEvent->GetPositionInWorld(); Vector3D toProjected = m_LastCursorPosition - m_CenterOfRotation; Vector3D toCursor = cursor - m_CenterOfRotation; // cross product: | A x B | = |A| * |B| * sin(angle) Vector3D axisOfRotation; vnl_vector_fixed vnlDirection = vnl_cross_3d(toCursor.GetVnlVector(), toProjected.GetVnlVector()); axisOfRotation.SetVnlVector(vnlDirection); // scalar product: A * B = |A| * |B| * cos(angle) // tan = sin / cos ScalarType angle = -atan2((double)(axisOfRotation.GetNorm()), (double)(toCursor * toProjected)); angle *= 180.0 / vnl_math::pi; m_LastCursorPosition = cursor; // create RotationOperation and apply to all SNCs that should be rotated RotationOperation rotationOperation(OpROTATE, m_CenterOfRotation, axisOfRotation, angle); // iterate the OTHER slice navigation controllers: these are filled in DoDecideBetweenRotationAndSliceSelection for (SNCVector::iterator iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; timeGeometry->ExecuteOperation(&rotationOperation); (*iter)->SendCreatedWorldGeometryUpdate(); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::DisplayInteractor::Swivel(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const InteractionPositionEvent *posEvent = dynamic_cast(event); if (!posEvent) return; // Determine relative mouse movement projected onto world space Point2D cursor = posEvent->GetPointerPositionOnScreen(); Vector2D relativeCursor = cursor - m_ReferenceCursor; Vector3D relativeCursorAxis = m_RotationPlaneXVector * relativeCursor[0] + m_RotationPlaneYVector * relativeCursor[1]; // Determine rotation axis (perpendicular to rotation plane and cursor // movement) Vector3D rotationAxis = itk::CrossProduct(m_RotationPlaneNormal, relativeCursorAxis); ScalarType rotationAngle = relativeCursor.GetNorm() / 2.0; // Restore the initial plane pose by undoing the previous rotation // operation RotationOperation op(OpROTATE, m_CenterOfRotation, m_PreviousRotationAxis, -m_PreviousRotationAngle); SNCVector::iterator iter; for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; timeGeometry->ExecuteOperation(&op); (*iter)->SendCreatedWorldGeometryUpdate(); } } // Apply new rotation operation to all relevant SNCs RotationOperation op2(OpROTATE, m_CenterOfRotation, rotationAxis, rotationAngle); for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { // Retrieve the TimeGeometry of this SliceNavigationController TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; // Execute the new rotation timeGeometry->ExecuteOperation(&op2); // Notify listeners (*iter)->SendCreatedWorldGeometryUpdate(); } } m_PreviousRotationAxis = rotationAxis; m_PreviousRotationAngle = rotationAngle; RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::DisplayInteractor::UpdateStatusbar(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const InteractionPositionEvent *posEvent = dynamic_cast(event); if (!posEvent) return; std::string statusText; TNodePredicateDataType::Pointer isImageData = TNodePredicateDataType::New(); mitk::DataStorage::SetOfObjects::ConstPointer nodes = posEvent->GetSender()->GetDataStorage()->GetSubset(isImageData).GetPointer(); mitk::Point3D worldposition = posEvent->GetPositionInWorld(); mitk::Image::Pointer image3D; mitk::DataNode::Pointer node; mitk::DataNode::Pointer topSourceNode; int component = 0; node = this->GetTopLayerNode(nodes, worldposition, posEvent->GetSender()); if (node.IsNotNull()) { bool isBinary(false); node->GetBoolProperty("binary", isBinary); if (isBinary) { mitk::DataStorage::SetOfObjects::ConstPointer sourcenodes = posEvent->GetSender()->GetDataStorage()->GetSources(node, NULL, true); if (!sourcenodes->empty()) { topSourceNode = this->GetTopLayerNode(sourcenodes, worldposition, posEvent->GetSender()); } if (topSourceNode.IsNotNull()) { image3D = dynamic_cast(topSourceNode->GetData()); topSourceNode->GetIntProperty("Image.Displayed Component", component); } else { image3D = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } } else { image3D = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } } // get the position and gray value from the image and build up status bar text if (image3D.IsNotNull()) { itk::Index<3> p; image3D->GetGeometry()->WorldToIndex(worldposition, p); mitk::ScalarType pixelValue; mitkPixelTypeMultiplex5(mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, image3D->GetVolumeData(posEvent->GetSender()->GetTimeStep()), p, pixelValue, component); mitk::StatusBar::GetInstance()->DisplayImageInfo(worldposition, p, posEvent->GetSender()->GetTime(), pixelValue); } else { mitk::StatusBar::GetInstance()->DisplayImageInfoInvalid(); } } void mitk::DisplayInteractor::ConfigurationChanged() { mitk::PropertyList::Pointer properties = GetAttributes(); // auto repeat std::string strAutoRepeat = ""; if (properties->GetStringProperty("autoRepeat", strAutoRepeat)) { if (strAutoRepeat == "true") { m_AutoRepeat = true; } else { m_AutoRepeat = false; } } // pixel movement for scrolling one slice std::string strPixelPerSlice = ""; if (properties->GetStringProperty("pixelPerSlice", strPixelPerSlice)) { m_IndexToSliceModifier = atoi(strPixelPerSlice.c_str()); } else { m_IndexToSliceModifier = 4; } // scroll direction if (!properties->GetStringProperty("scrollDirection", m_ScrollDirection)) { m_ScrollDirection = "updown"; } m_InvertScrollDirection = GetBoolProperty(properties, "invertScrollDirection", false); // zoom direction if (!properties->GetStringProperty("zoomDirection", m_ZoomDirection)) { m_ZoomDirection = "updown"; } m_InvertZoomDirection = GetBoolProperty(properties, "invertZoomDirection", false); m_InvertMoveDirection = GetBoolProperty(properties, "invertMoveDirection", false); if (!properties->GetStringProperty("levelWindowDirection", m_LevelDirection)) { m_LevelDirection = "leftright"; } m_InvertLevelWindowDirection = GetBoolProperty(properties, "invertLevelWindowDirection", false); // coupled rotation std::string strCoupled = ""; if (properties->GetStringProperty("coupled", strCoupled)) { if (strCoupled == "true") m_LinkPlanes = true; else m_LinkPlanes = false; } // zoom factor std::string strZoomFactor = ""; properties->GetStringProperty("zoomFactor", strZoomFactor); m_ZoomFactor = .05; if (atoi(strZoomFactor.c_str()) > 0) { m_ZoomFactor = 1.0 + (atoi(strZoomFactor.c_str()) / 100.0); } // allwaysReact std::string strAlwaysReact = ""; if (properties->GetStringProperty("alwaysReact", strAlwaysReact)) { if (strAlwaysReact == "true") { m_AlwaysReact = true; } else { m_AlwaysReact = false; } } else { m_AlwaysReact = false; } } bool mitk::DisplayInteractor::FilterEvents(InteractionEvent *interactionEvent, DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } bool mitk::DisplayInteractor::GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char *propertyName, bool defaultValue) { std::string valueAsString; if (!propertyList->GetStringProperty(propertyName, valueAsString)) { return defaultValue; } else { if (valueAsString == "true") { return true; } else { return false; } } } mitk::DataNode::Pointer mitk::DisplayInteractor::GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes, mitk::Point3D worldposition, BaseRenderer *ren) { mitk::DataNode::Pointer node; if (nodes.IsNotNull()) { int maxlayer = -32768; bool isHelper(false); for (unsigned int x = 0; x < nodes->size(); x++) { nodes->at(x)->GetBoolProperty("helper object", isHelper); if (nodes->at(x)->GetData()->GetGeometry()->IsInside(worldposition) && isHelper == false) { int layer = 0; if (!(nodes->at(x)->GetIntProperty("layer", layer))) continue; if (layer > maxlayer) { if (static_cast(nodes->at(x))->IsVisible(ren)) { node = nodes->at(x); maxlayer = layer; } } } } } return node; } diff --git a/Modules/DiffusionImaging/MiniApps/CMakeLists.txt b/Modules/DiffusionImaging/MiniApps/CMakeLists.txt index 5b02b7e213..742c5e1dc4 100755 --- a/Modules/DiffusionImaging/MiniApps/CMakeLists.txt +++ b/Modules/DiffusionImaging/MiniApps/CMakeLists.txt @@ -1,57 +1,58 @@ if(BUILD_DiffusionMiniApps OR MITK_BUILD_ALL_APPS) # needed include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # list of diffusion miniapps # if an app requires additional dependencies # they are added after a "^^" and separated by "_" set( diffusionminiapps NetworkCreation^^MitkFiberTracking_MitkConnectomics NetworkStatistics^^MitkConnectomics + Fiberfox^^MitkFiberTracking MultishellMethods^^MitkFiberTracking PeaksAngularError^^MitkFiberTracking PeakExtraction^^MitkFiberTracking FiberExtraction^^MitkFiberTracking FiberProcessing^^MitkFiberTracking FiberDirectionExtraction^^MitkFiberTracking LocalDirectionalFiberPlausibility^^MitkFiberTracking StreamlineTracking^^MitkFiberTracking GibbsTracking^^MitkFiberTracking TractometerMetrics^^MitkFiberTracking FileFormatConverter^^MitkFiberTracking DFTraining^^MitkFiberTracking DFTracking^^MitkFiberTracking ) foreach(diffusionminiapp ${diffusionminiapps}) # extract mini app name and dependencies string(REPLACE "^^" "\\;" miniapp_info ${diffusionminiapp}) set(miniapp_info_list ${miniapp_info}) list(GET miniapp_info_list 0 appname) list(GET miniapp_info_list 1 raw_dependencies) string(REPLACE "_" "\\;" dependencies "${raw_dependencies}") set(dependencies_list ${dependencies}) mitkFunctionCreateCommandLineApp( NAME ${appname} DEPENDS MitkCore MitkDiffusionCore ${dependencies_list} PACKAGE_DEPENDS ITK ) endforeach() # This mini app does not depend on mitkDiffusionImaging at all mitkFunctionCreateCommandLineApp( NAME Dicom2Nrrd DEPENDS MitkCore ${dependencies_list} ) if(EXECUTABLE_IS_ENABLED) MITK_INSTALL_TARGETS(EXECUTABLES ${EXECUTABLE_TARGET}) endif() endif() diff --git a/Modules/DiffusionImaging/MiniApps/ExtractImageStatistics.cpp b/Modules/DiffusionImaging/MiniApps/ExtractImageStatistics.cpp index e30bbfa158..b89d0995b8 100644 --- a/Modules/DiffusionImaging/MiniApps/ExtractImageStatistics.cpp +++ b/Modules/DiffusionImaging/MiniApps/ExtractImageStatistics.cpp @@ -1,127 +1,129 @@ /*=================================================================== 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 "MiniAppManager.h" +//#include "MiniAppManager.h" -#include "ctkCommandLineParser.h" +//#include "ctkCommandLineParser.h" +#include "mitkCommandLineParser.cpp" #include "mitkImage.h" #include "mitkImageStatisticsCalculator.h" #include "mitkIOUtil.h" #include #include #include int ExtractImageStatistics(int argc, char* argv[]) { mitkCommandLineParser parser; parser.setTitle("Extract Image Statistics"); parser.setCategory("Preprocessing Tools"); parser.setDescription(""); parser.setContributor("MBI"); parser.setArgumentPrefix("--", "-"); parser.addArgument("help", "h", mitkCommandLineParser::String, "Help:", "Show this help text"); parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image", us::Any(),false); parser.addArgument("mask", "m", mitkCommandLineParser::InputFile, "Mask:", "mask image / roi image denotin area on which statistics are calculated", us::Any(),false); parser.addArgument("out", "o", mitkCommandLineParser::OutputFile, "Output", "output file (default: filenameOfRoi.nrrd_statistics.txt)", us::Any()); map parsedArgs = parser.parseArguments(argc, argv); if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << "\n\n MiniApp Description: \nCalculates statistics on the supplied image using given mask." << endl; std::cout << "Output is written to the designated output file in this order:" << endl; std::cout << "Mean, Standard Deviation, RMS, Max, Min, Number of Voxels, Volume [mm3]" << endl; std::cout << "\n\n Parameters:"<< endl; std::cout << parser.helpText(); return EXIT_SUCCESS; } + // Parameters: bool ignoreZeroValues = false; unsigned int timeStep = 0; std::string inputImageFile = us::any_cast(parsedArgs["input"]); std::string maskImageFile = us::any_cast(parsedArgs["mask"]); std::string outFile; if (parsedArgs.count("out") || parsedArgs.count("o") ) outFile = us::any_cast(parsedArgs["out"]); else outFile = inputImageFile + "_statistics.txt"; // Load image and mask mitk::Image::Pointer maskImage = mitk::IOUtil::LoadImage(maskImageFile); mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); // Calculate statistics mitk::ImageStatisticsCalculator::Statistics statisticsStruct; mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); try { calculator->SetImage(inputImage); calculator->SetImageMask(maskImage); calculator->SetMaskingModeToImage(); } catch( const itk::ExceptionObject& e) { MITK_ERROR << "Statistic Calculation Failed - ITK Exception:" << e.what(); return -1; } calculator->SetDoIgnorePixelValue(ignoreZeroValues); calculator->SetIgnorePixelValue(0); try { calculator->ComputeStatistics(timeStep); } catch ( mitk::Exception& e) { MITK_ERROR<< "MITK Exception: " << e.what(); return -1; } statisticsStruct = calculator->GetStatistics(timeStep); // Calculate Volume double volume = 0; const mitk::BaseGeometry *geometry = inputImage->GetGeometry(); if ( geometry != NULL ) { const mitk::Vector3D &spacing = inputImage->GetGeometry()->GetSpacing(); volume = spacing[0] * spacing[1] * spacing[2] * (double) statisticsStruct.GetN(); } // Write Results to file std::ofstream output; output.open(outFile.c_str()); output << statisticsStruct.GetMean() << " , "; output << statisticsStruct.GetSigma() << " , "; output << statisticsStruct.GetRMS() << " , "; output << statisticsStruct.GetMax() << " , "; output << statisticsStruct.GetMin() << " , "; output << statisticsStruct.GetN() << " , "; output << volume << "\n"; output.flush(); output.close(); return 0; } RegisterDiffusionMiniApp(ExtractImageStatistics); diff --git a/Modules/DiffusionImaging/MiniApps/HotspotMiniApp.cpp b/Modules/DiffusionImaging/MiniApps/HotspotMiniApp.cpp new file mode 100644 index 0000000000..3dae227d76 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/HotspotMiniApp.cpp @@ -0,0 +1,103 @@ +/*=================================================================== + +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 "mitkCommandLineParser.h" +#include "mitkImage.h" +#include "mitkIOUtil.h" +#include +#include +#include +#include + + +int main( int argc, char* argv[] ) +{ + mitkCommandLineParser parser; + + parser.setTitle("Test basic hotspot functionality"); + parser.setCategory("Preprocessing Tools"); + parser.setDescription(""); + parser.setContributor("MBI"); + + parser.setArgumentPrefix("--", "-"); + parser.addArgument("help", "h", mitkCommandLineParser::String, "Help:", "Show this help text"); + parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image", us::Any(),false); + parser.addArgument("mask", "m", mitkCommandLineParser::InputFile, "Mask:", "mask image / roi image denotin area on which statistics are calculated", us::Any(),true); + parser.addArgument("out", "o", mitkCommandLineParser::OutputFile, "Output", "output file (default: hotspotMiniApp_output.nrrd)", us::Any()); + + std::cout << "test...." << std::endl; + + std::map parsedArgs = parser.parseArguments(argc, argv); + std::cout << "parsedArgs.size()= " << parsedArgs.size() << std::endl; + if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) + { +// std::cout << "\n\n MiniApp Description: \nCalculates and saves the hotspot of an image." << endl; +// std::cout << "Output is written to the designated output file" << endl; +// std::cout << parser.helpText(); +// return EXIT_SUCCESS; + } + + // Parameters: + std::string inputImageFile; + if (!parsedArgs.count("i") && !parsedArgs.count("input")) + { + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd"; + } + else + { + inputImageFile = us::any_cast(parsedArgs["input"]); + } + + mitk::Image::Pointer maskImage; + if (parsedArgs.count("mask") || parsedArgs.count("m")) + { + std::string maskImageFile = us::any_cast(parsedArgs["mask"]); + maskImage = mitk::IOUtil::LoadImage(maskImageFile); + } + + std::string outFile; + if (parsedArgs.count("out") || parsedArgs.count("o") ) + outFile = us::any_cast(parsedArgs["out"]); + else + outFile = "hotspotMiniApp_output.nrrd"; + + // Load image and mask + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + + // Calculate statistics + mitk::HotspotMaskGenerator::Pointer hotspotCalc = mitk::HotspotMaskGenerator::New(); + hotspotCalc->SetInputImage(inputImage); + hotspotCalc->SetHotspotRadiusInMM(10); + hotspotCalc->SetTimeStep(0); + mitk::Image::Pointer outImage; + try + { + outImage = hotspotCalc->GetMask(); + } + catch( const itk::ExceptionObject& e) + { + MITK_ERROR << "Failed - ITK Exception:" << e.what(); + return -1; + } + + if (outImage != nullptr) + { + mitk::IOUtil::SaveImage(outImage, outFile); + } + + + return EXIT_SUCCESS; +} diff --git a/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniApp.cpp b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniApp.cpp new file mode 100644 index 0000000000..f2bef60001 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniApp.cpp @@ -0,0 +1,218 @@ +/*=================================================================== + +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 "mitkCommandLineParser.h" +#include "mitkImage.h" +#include "mitkImageStatisticsCalculator.h" +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct statistics_res{ + double mean, variance, min, max, count, moment; +}; + +void printstats(statistics_res s) +{ + std::cout << "mean: " << s.mean << std::endl + << "variance: " << s.variance << std::endl + << "min: " << s.min << std::endl + << "max: " << s.max << std::endl + << "count: " << s.count << std::endl + << "moment: " << s.moment << std::endl; +} + +template < typename TPixel, unsigned int VImageDimension > +void get_statistics_boost(itk::Image* itkImage, statistics_res& res){ + typedef itk::Image ImageType; + + itk::ImageRegionConstIterator it(itkImage, itkImage->GetLargestPossibleRegion()); + + TPixel currentPixel; + int ctr=0; + double sum=0; + + boost::accumulators::accumulator_set> > acc; + + for (it.GoToBegin(); !it.IsAtEnd(); ++it) + { + acc(it.Get()); +// currentPixel = it.Get(); +// sum+=currentPixel; +// ctr+=1; + } + +// res.mean=(double)sum/(double)ctr; + res.mean = boost::accumulators::mean(acc); + res.variance = boost::accumulators::variance(acc); + res.min = boost::accumulators::min(acc); + res.max = boost::accumulators::max(acc); + res.count = boost::accumulators::count(acc); + res.moment = boost::accumulators::moment<2>(acc); + + std::cout << "sum: " << sum << " N: " << ctr << " mean: " << res.mean << std::endl; +} + +int main( int argc, char* argv[] ) +{ + mitkCommandLineParser parser; + + parser.setTitle("Extract Image Statistics"); + parser.setCategory("Preprocessing Tools"); + parser.setDescription(""); + parser.setContributor("MBI"); + + parser.setArgumentPrefix("--", "-"); + parser.addArgument("help", "h", mitkCommandLineParser::String, "Help:", "Show this help text"); + parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image", us::Any(),false); + parser.addArgument("mask", "m", mitkCommandLineParser::InputFile, "Mask:", "mask image / roi image denotin area on which statistics are calculated", us::Any(),true); + parser.addArgument("out", "o", mitkCommandLineParser::OutputFile, "Output", "output file (default: filenameOfRoi.nrrd_statistics.txt)", us::Any()); + + std::cout << "test...." << std::endl; + + std::map parsedArgs = parser.parseArguments(argc, argv); + std::cout << "parsedArgs.size()= " << parsedArgs.size() << std::endl; + if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) + { + std::cout << "\n\n MiniApp Description: \nCalculates statistics on the supplied image using given mask." << endl; + std::cout << "Output is written to the designated output file in this order:" << endl; + std::cout << "Mean, Standard Deviation, RMS, Max, Min, Number of Voxels, Volume [mm3]" << endl; + std::cout << "\n\n Parameters:"<< endl; + std::cout << parser.helpText(); + return EXIT_SUCCESS; + } + + + // Parameters: + bool ignoreZeroValues = false; + unsigned int timeStep = 0; + + std::string inputImageFile = us::any_cast(parsedArgs["input"]); + mitk::Image::Pointer maskImage; + if (parsedArgs.count("mask") || parsedArgs.count("m")) + { + std::string maskImageFile = us::any_cast(parsedArgs["mask"]); + maskImage = mitk::IOUtil::LoadImage(maskImageFile); + } + + std::string outFile; + if (parsedArgs.count("out") || parsedArgs.count("o") ) + outFile = us::any_cast(parsedArgs["out"]); + else + outFile = inputImageFile + "_statistics.txt"; + + // Load image and mask + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + + // Calculate statistics + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer statisticsStruct; + mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); + try + { + calculator->SetInputImage(inputImage); + if (parsedArgs.count("mask") || parsedArgs.count("m")) + { + mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); + imgMask->SetImageMask(maskImage); + imgMask->SetTimeStep(timeStep); + calculator->SetMask(imgMask.GetPointer()); + } + else + { + calculator->SetMask(nullptr); + } + + } + catch( const itk::ExceptionObject& e) + { + MITK_ERROR << "Statistic Calculation Failed - ITK Exception:" << e.what(); + return -1; + } + + + if (ignoreZeroValues) + { + // TODO, cannot have more than one mask, using ignore pixel value will override the image mask :-c + mitk::IgnorePixelMaskGenerator::Pointer ignorePixelMask = mitk::IgnorePixelMaskGenerator::New(); + ignorePixelMask->SetInputImage(inputImage); + ignorePixelMask->SetTimeStep(timeStep); + ignorePixelMask->SetIgnoredPixelValue(0); + calculator->SetMask(ignorePixelMask.GetPointer()); + } + + std::cout << "calculating statistics itk: " << std::endl; + try + { + statisticsStruct = calculator->GetStatistics(timeStep); + } + catch ( mitk::Exception& e) + { + MITK_ERROR<< "MITK Exception: " << e.what(); + return -1; + } + + + // Calculate Volume + double volume = 0; + const mitk::BaseGeometry *geometry = inputImage->GetGeometry(); + if ( geometry != NULL ) + { + const mitk::Vector3D &spacing = inputImage->GetGeometry()->GetSpacing(); + volume = spacing[0] * spacing[1] * spacing[2] * (double) statisticsStruct->GetN(); + } + + // Write Results to file + std::ofstream output; + output.open(outFile.c_str()); + output << statisticsStruct->GetMean() << " , "; + output << statisticsStruct->GetStd() << " , "; + output << statisticsStruct->GetRMS() << " , "; + output << statisticsStruct->GetMax() << " , "; + output << statisticsStruct->GetMin() << " , "; + output << statisticsStruct->GetN() << " , "; + output << volume << "\n"; + + output.flush(); + output.close(); + + std::cout << "calculating statistics boost: " << std::endl; + + statistics_res res; + AccessByItk_n(inputImage, get_statistics_boost, (res)); + + printstats(res); + + return EXIT_SUCCESS; +} diff --git a/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_3.cpp b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_3.cpp new file mode 100644 index 0000000000..ed343d9861 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_3.cpp @@ -0,0 +1,84 @@ +/*=================================================================== + +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 "mitkCommandLineParser.h" +#include "mitkImage.h" +#include +#include +#include +#include +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +int main( int argc, char* argv[] ) +{ + unsigned int timeStep = 0; + std::string inputImageFile, maskImageFile; + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd"; + maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_someSegmentation.nrrd"; + // Load image + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + mitk::Image::Pointer maskImage = mitk::IOUtil::LoadImage(maskImageFile); + + + // Calculate statistics + mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); + + std::cout << "calculating statistics (unmasked) itk: " << std::endl; + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result; + + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(maskImage); + + calculator->SetMask(imgMaskGen.GetPointer()); + calculator->SetInputImage(inputImage); + calculator->SetNBinsForHistogramStatistics(100); + + for (unsigned int i=0; i < inputImage->GetTimeSteps(); i++) + { + std::cout << "Results for time step " << i << ":" << std::endl; + result = calculator->GetStatistics(i, 1); + result->Print(); + std::cout << std::endl; + } + MITK_INFO << "-------------"; + mitk::Image::Pointer test = imgMaskGen->GetMask(); + MITK_INFO << "-------------"; + + mitk::Image::Pointer test2 = imgMaskGen->GetMask(); + MITK_INFO << "-------------"; + imgMaskGen->SetTimeStep(2); + mitk::Image::Pointer test3 = imgMaskGen->GetMask(); + + + return EXIT_SUCCESS; +} diff --git a/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_v2.cpp b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_v2.cpp new file mode 100644 index 0000000000..a81d680524 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_v2.cpp @@ -0,0 +1,360 @@ +/*=================================================================== + +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 "mitkCommandLineParser.h" +#include "mitkImage.h" +#include +#include +#include +#include +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct statistics_res{ + double mean, variance, min, max, count, moment; +}; + +long getTimeInMs() +{ + std::chrono::milliseconds ms = std::chrono::duration_cast< std::chrono::milliseconds > (std::chrono::system_clock::now().time_since_epoch()); + long time = ms.count(); + return time; +} + +//std::string printstats(mitk::ImageStatisticsCalculator::Statistics statisticsStruct) +//{ +// std::string res = ""; +// res += std::string("Entropy ") + std::to_string(statisticsStruct.GetEntropy()) + std::string("\n"); +// res += std::string("Kurtosis ") + std::to_string(statisticsStruct.GetKurtosis()) + std::string("\n"); +// res += std::string("MPP ") + std::to_string(statisticsStruct.GetMPP()) + std::string("\n"); +// res += std::string("Max ") + std::to_string(statisticsStruct.GetMax()) + std::string("\n"); +// res += std::string("Mean ") + std::to_string(statisticsStruct.GetMean()) + std::string("\n"); +// res += std::string("Median ") + std::to_string(statisticsStruct.GetMedian()) + std::string("\n"); +// res += std::string("Min ") + std::to_string(statisticsStruct.GetMin()) + std::string("\n"); +// res += std::string("N ") + std::to_string(statisticsStruct.GetN()) + std::string("\n"); +// res += std::string("RMS ") + std::to_string(statisticsStruct.GetRMS()) + std::string("\n"); +// res += std::string("Skewness ") + std::to_string(statisticsStruct.GetSkewness()) + std::string("\n"); +// res += std::string("Std ") + std::to_string(statisticsStruct.GetSigma()) + std::string("\n"); +// res += std::string("UPP ") + std::to_string(statisticsStruct.GetUPP()) + std::string("\n"); +// res += std::string("Uniformity ") + std::to_string(statisticsStruct.GetUniformity()) + std::string("\n"); +// res += std::string("Variance ") + std::to_string(statisticsStruct.GetVariance()) + std::string("\n"); +// vnl_vector minIndex = statisticsStruct.GetMinIndex(); +// vnl_vector maxIndex = statisticsStruct.GetMaxIndex(); + +// res += "Min Index: "; +// for (auto it = minIndex.begin(); it != minIndex.end(); it++) +// { +// res += std::to_string(*it) + " "; +// } +// res += std::string("\n"); + +// res += "Max Index: "; +// for (auto it = maxIndex.begin(); it != maxIndex.end(); it++) +// { +// res += std::to_string(*it) + " "; +// } +// res += std::string("\n"); +// return res; +//} + +template +void printMap(std::map input) +{ + for (auto it = input.begin(); it != input.end(); ++it) + { + std::cout << it->first<< ": " << it->second<< std::endl; + } + std::cout << std::endl; +} + +template < typename TPixel, unsigned int VImageDimension > +void get_statistics_boost(itk::Image* itkImage, statistics_res& res){ + typedef itk::Image ImageType; + + itk::ImageRegionConstIterator it(itkImage, itkImage->GetLargestPossibleRegion()); + + int ctr=0; + double sum=0; + + boost::accumulators::accumulator_set> > acc; + + for (it.GoToBegin(); !it.IsAtEnd(); ++it) + { + acc(it.Get()); +// currentPixel = it.Get(); +// sum+=currentPixel; +// ctr+=1; + } + +// res.mean=(double)sum/(double)ctr; + res.mean = boost::accumulators::mean(acc); + res.variance = boost::accumulators::variance(acc); + res.min = boost::accumulators::min(acc); + res.max = boost::accumulators::max(acc); + res.count = boost::accumulators::count(acc); + res.moment = boost::accumulators::moment<2>(acc); + + std::cout << "sum: " << sum << " N: " << ctr << " mean: " << res.mean << std::endl; +} + +void compute_statistics(std::string inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd", + std::string outfname = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_statistics_new.txt", + std::string outfname2 = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_statistics_old.txt", + std::string maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_someSegmentation.nrrd", + std::string pFfile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_rectangle.pf" ) +{ + ofstream outFile; + outFile.open(outfname); + + unsigned int timeStep = 0; + + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + + mitk::Image::Pointer maskImage; + maskImage = mitk::IOUtil::LoadImage(maskImageFile); + + std::vector loadedObjects; + loadedObjects = mitk::IOUtil::Load(pFfile); + mitk::BaseData::Pointer pf = loadedObjects[0]; + mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(pf.GetPointer()); + + mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result; + calculator->SetInputImage(inputImage); + calculator->SetNBinsForHistogramStatistics(100); + + long start_time; + + outFile << "Calculating Statistics on Pic3D" << std::endl << std::endl; + + outFile << "New Image Statistics Calculator" << std::endl; + + start_time = getTimeInMs(); + outFile << "1) unmasked: " << std::endl; + calculator->SetMask(nullptr); + result = calculator->GetStatistics(timeStep); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + + start_time = getTimeInMs(); + outFile << "2) planarfigure: " << std::endl; + mitk::PlanarFigureMaskGenerator::Pointer planarFigMaskExtr = mitk::PlanarFigureMaskGenerator::New(); + planarFigMaskExtr->SetPlanarFigure(planarFigure); + planarFigMaskExtr->SetInputImage(inputImage); + calculator->SetMask(planarFigMaskExtr.GetPointer()); + result = calculator->GetStatistics(timeStep, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + + start_time = getTimeInMs(); + outFile << "3) ignore pixel value mask: " << std::endl; + mitk::IgnorePixelMaskGenerator::Pointer ignPixVal = mitk::IgnorePixelMaskGenerator::New(); + ignPixVal->SetInputImage(inputImage); + ignPixVal->SetIgnoredPixelValue(0); + calculator->SetMask(ignPixVal.GetPointer()); + result = calculator->GetStatistics(timeStep, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + + start_time = getTimeInMs(); + outFile << "4) image mask: " << std::endl; + mitk::ImageMaskGenerator::Pointer binaryImageMaskGen = mitk::ImageMaskGenerator::New(); + binaryImageMaskGen->SetImageMask(maskImage); + calculator->SetMask(binaryImageMaskGen.GetPointer()); + result = calculator->GetStatistics(timeStep, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + + start_time = getTimeInMs(); + outFile << "5) hotspot mask: " << std::endl; + mitk::HotspotMaskGenerator::Pointer hotSpotMaskGen = mitk::HotspotMaskGenerator::New(); + hotSpotMaskGen->SetInputImage(inputImage); + hotSpotMaskGen->SetHotspotRadiusInMM(10); + hotSpotMaskGen->SetTimeStep(0); + calculator->SetMask(hotSpotMaskGen.GetPointer()); + result = calculator->GetStatistics(timeStep, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + //mitk::IOUtil::SaveImage(hotSpotMaskGen->GetMask(), "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusTHotspot.nrrd"); + + outFile << "6) all time steps (image mask): " << std::endl; + if (inputImage->GetTimeSteps() > 1) + { + start_time = getTimeInMs(); + for (unsigned int i=0; i < inputImage->GetTimeSteps(); i++) + { + outFile << "timestep: " << i << std::endl; + calculator->SetMask(binaryImageMaskGen.GetPointer()); + result = calculator->GetStatistics(i, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + } + } + else + { + outFile << "Input image has only 1 time step!" << std::endl; + } + + outFile.flush(); + outFile.close(); + + // ----------------------------------------------------------------------------------------------------------------------------------------------------------- +// outFile.open(outfname2); + +// std::cout << std::endl << std::endl << "calculating statistics old imgstatcalc: " << std::endl; +// mitk::ImageStatisticsCalculator::Statistics statisticsStruct; +// mitk::ImageStatisticsCalculator::Pointer calculator_old; + + + +// start_time = getTimeInMs(); +// outFile << "1) unmasked: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetMaskingModeToNone(); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + +// start_time = getTimeInMs(); +// outFile << "2) planarFigure: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetPlanarFigure(planarFigure); +// calculator_old->SetMaskingModeToPlanarFigure(); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + +// start_time = getTimeInMs(); +// outFile << "3) IgnorePixelValue: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetMaskingModeToNone(); +// calculator_old->SetDoIgnorePixelValue(true); +// calculator_old->SetIgnorePixelValue(0); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; +// calculator_old->SetDoIgnorePixelValue(false); + +// start_time = getTimeInMs(); +// outFile << "4) Image Mask: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetImageMask(maskImage); +// calculator_old->SetMaskingModeToImage(); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + +// start_time = getTimeInMs(); +// outFile << "5) Hotspot Mask: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetMaskingModeToNone(); +// calculator_old->SetCalculateHotspot(true); +// calculator_old->SetHotspotRadiusInMM(10); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep).GetHotspotStatistics(); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; +// calculator_old->SetCalculateHotspot(false); + +// outFile << "6) all time steps (image mask): " << std::endl; +// if (inputImage->GetTimeSteps() > 1) +// { +// start_time = getTimeInMs(); +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetImageMask(maskImage); +// calculator_old->SetMaskingModeToImage(); +// for (unsigned int i=0; i < inputImage->GetTimeSteps(); i++) +// { +// calculator_old->ComputeStatistics(i); +// statisticsStruct = calculator_old->GetStatistics(i); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; +// } +// } +// else +// { +// outFile << "Input image has only 1 time step!" << std::endl; +// } + +// outFile.flush(); +// outFile.close(); +} + +int main( int argc, char* argv[] ) +{ + std::string inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd"; + std::string outfname = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_statistics_new.txt"; + std::string outfname2 = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_statistics_old.txt"; + std::string maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_someSegmentation.nrrd"; + std::string pFfile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_rectangle.pf"; + compute_statistics(inputImageFile, outfname, outfname2, maskImageFile, pFfile); + + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT.nrrd"; + outfname = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT_statistics_new.txt"; + outfname2 = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT_statistics_old.txt"; + maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT_someSegmentation.nrrd"; + pFfile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT_ellipse.pf"; + compute_statistics(inputImageFile, outfname, outfname2, maskImageFile, pFfile); + + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct.pic"; + outfname = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct_statistics_new.txt"; + outfname2 = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct_statistics_old.txt"; + maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct_segmentation.nrrd"; + pFfile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct_PF.pf"; + compute_statistics(inputImageFile, outfname, outfname2, maskImageFile, pFfile); + + + return EXIT_SUCCESS; +} diff --git a/Modules/DiffusionImaging/MiniApps/PlanarFigureMaskCreatorMiniapp.cpp b/Modules/DiffusionImaging/MiniApps/PlanarFigureMaskCreatorMiniapp.cpp new file mode 100644 index 0000000000..ec163dc9e1 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/PlanarFigureMaskCreatorMiniapp.cpp @@ -0,0 +1,131 @@ +/*=================================================================== + +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 "mitkCommandLineParser.h" +#include "mitkImage.h" +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include +#include + + +int main( int argc, char* argv[] ) +{ + mitkCommandLineParser parser; + + parser.setTitle("Test basic hotspot functionality"); + parser.setCategory("Preprocessing Tools"); + parser.setDescription(""); + parser.setContributor("MBI"); + + parser.setArgumentPrefix("--", "-"); + parser.addArgument("help", "h", mitkCommandLineParser::String, "Help:", "Show this help text"); + parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image", us::Any(),false); + parser.addArgument("planarfigure", "p", mitkCommandLineParser::InputFile, "PlanarFigure:", "mask image / roi image denotin area on which statistics are calculated", us::Any(),false); + parser.addArgument("out", "o", mitkCommandLineParser::OutputFile, "Output", "output file (default: hotspotMiniApp_output.nrrd)", us::Any()); + + std::cout << "test...." << std::endl; + + std::map parsedArgs = parser.parseArguments(argc, argv); + std::cout << "parsedArgs.size()= " << parsedArgs.size() << std::endl; + if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) + { +// std::cout << "\n\n MiniApp Description: \nCalculates and saves the hotspot of an image." << endl; +// std::cout << "Output is written to the designated output file" << endl; +// std::cout << parser.helpText(); +// return EXIT_SUCCESS; + } + + // Parameters: + std::string inputImageFile; + if (!parsedArgs.count("i") && !parsedArgs.count("input")) + { + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd"; + } + else + { + inputImageFile = us::any_cast(parsedArgs["input"]); + } + + std::string inputPFFile; + if (parsedArgs.count("planarfigure") || parsedArgs.count("p")) + { + inputPFFile = us::any_cast(parsedArgs["planarfigure"]); + } + else + { + inputPFFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_rectangle.pf"; + } + + + mitk::PlanarFigure::Pointer planarFigure; + try + { + std::vector loadedObjects; + // try except + loadedObjects = mitk::IOUtil::Load(inputPFFile); + mitk::BaseData::Pointer pf = loadedObjects[0]; + planarFigure = dynamic_cast(pf.GetPointer()); + if (planarFigure.IsNull()) + { + MITK_ERROR << "something went wrong"; + return -1; + } + } + catch (const itk::ExceptionObject& e) + { + MITK_ERROR << "Failed: " << e.what(); + return -1; + } + + std::string outFile; + if (parsedArgs.count("out") || parsedArgs.count("o") ) + outFile = us::any_cast(parsedArgs["out"]); + else + outFile = "planarFigureExtraction_output.nrrd"; + + // Load image and mask + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + + // Calculate statistics + mitk::PlanarFigureMaskGenerator::Pointer planarFigMaskExtr = mitk::PlanarFigureMaskGenerator::New(); + planarFigMaskExtr->SetPlanarFigure(planarFigure); + planarFigMaskExtr->SetInputImage(inputImage); + + mitk::Image::Pointer outImage; + try + { + outImage = planarFigMaskExtr->GetMask(); + } + catch( const itk::ExceptionObject& e) + { + MITK_ERROR << "Failed - ITK Exception:" << e.what(); + return -1; + } + + mitk::BaseGeometry *imageGeo = outImage->GetGeometry(); + std::cout << "origin: " << imageGeo->GetOrigin()[0] << " " << imageGeo->GetOrigin()[1] << " " << imageGeo->GetOrigin()[2] << std::endl; + if (outImage != nullptr) + { + mitk::IOUtil::SaveImage(outImage, outFile); + } + + + return EXIT_SUCCESS; +} diff --git a/Modules/GraphAlgorithms/CMakeLists.txt b/Modules/GraphAlgorithms/CMakeLists.txt index d5f4be6021..64c609e707 100644 --- a/Modules/GraphAlgorithms/CMakeLists.txt +++ b/Modules/GraphAlgorithms/CMakeLists.txt @@ -1,4 +1,4 @@ MITK_CREATE_MODULE( - DEPENDS MitkImageStatistics +# DEPENDS MitkImageStatistics WARNINGS_AS_ERRORS ) diff --git a/Modules/ImageStatistics/CMakeLists.txt b/Modules/ImageStatistics/CMakeLists.txt index 5264c1c320..fa0a7cf973 100644 --- a/Modules/ImageStatistics/CMakeLists.txt +++ b/Modules/ImageStatistics/CMakeLists.txt @@ -1,13 +1,13 @@ MITK_CREATE_MODULE( - DEPENDS MitkImageExtraction MitkPlanarFigure + DEPENDS MitkImageExtraction MitkPlanarFigure MitkMultilabel PACKAGE_DEPENDS PUBLIC ITK|ITKIOXML PRIVATE ITK|ITKVTK+ITKConvolution # WARNINGS_AS_ERRORS ) if(BUILD_TESTING) add_subdirectory(Testing) endif(BUILD_TESTING) diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp index 1cec357532..1eb1a62353 100644 --- a/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp @@ -1,507 +1,1709 @@ /*=================================================================== 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 "mitkImageStatisticsCalculator.h" #include #include #include #include #include #include #include #include +#include +#include +#include + /** * \brief Test class for mitkImageStatisticsCalculator * * This test covers: * - instantiation of an ImageStatisticsCalculator class * - correctness of statistics when using PlanarFigures for masking */ class mitkImageStatisticsCalculatorTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkImageStatisticsCalculatorTestSuite); MITK_TEST(TestUninitializedImage); MITK_TEST(TestCase1); MITK_TEST(TestCase2); MITK_TEST(TestCase3); MITK_TEST(TestCase4); MITK_TEST(TestCase5); MITK_TEST(TestCase6); MITK_TEST(TestCase7); MITK_TEST(TestCase8); MITK_TEST(TestCase9); MITK_TEST(TestCase10); MITK_TEST(TestCase11); MITK_TEST(TestCase12); MITK_TEST(TestImageMaskingEmpty); MITK_TEST(TestImageMaskingNonEmpty); MITK_TEST(TestRecomputeOnModifiedMask); + MITK_TEST(TestPic3DStatistics); + MITK_TEST(TestPic3DAxialPlanarFigureMaskStatistics); + MITK_TEST(TestPic3DSagittalPlanarFigureMaskStatistics); + MITK_TEST(TestPic3DCoronalPlanarFigureMaskStatistics); + MITK_TEST(TestPic3DImageMaskStatistics_label1); + MITK_TEST(TestPic3DImageMaskStatistics_label2); + MITK_TEST(TestPic3DIgnorePixelValueMaskStatistics); + MITK_TEST(TestPic3DSecondaryMaskStatistics); + MITK_TEST(TestUS4DCylStatistics_time1); + MITK_TEST(TestUS4DCylAxialPlanarFigureMaskStatistics_time1); + MITK_TEST(TestUS4DCylSagittalPlanarFigureMaskStatistics_time1); + MITK_TEST(TestUS4DCylCoronalPlanarFigureMaskStatistics_time1); + MITK_TEST(TestUS4DCylImageMaskStatistics_time1_label_1); + MITK_TEST(TestUS4DCylImageMaskStatistics_time2_label_1); + MITK_TEST(TestUS4DCylImageMaskStatistics_time1_label_2); + MITK_TEST(TestUS4DCylIgnorePixelValueMaskStatistics_time1); + MITK_TEST(TestUS4DCylSecondaryMaskStatistics_time1); CPPUNIT_TEST_SUITE_END(); public: void setUp() override; void TestUninitializedImage(); void TestCase1(); void TestCase2(); void TestCase3(); void TestCase4(); void TestCase5(); void TestCase6(); void TestCase7(); void TestCase8(); void TestCase9(); void TestCase10(); void TestCase11(); void TestCase12(); void TestImageMaskingEmpty(); void TestImageMaskingNonEmpty(); void TestRecomputeOnModifiedMask(); + void TestPic3DStatistics(); + void TestPic3DAxialPlanarFigureMaskStatistics(); + void TestPic3DSagittalPlanarFigureMaskStatistics(); + void TestPic3DCoronalPlanarFigureMaskStatistics(); + void TestPic3DImageMaskStatistics_label1(); + void TestPic3DImageMaskStatistics_label2(); + void TestPic3DIgnorePixelValueMaskStatistics(); + void TestPic3DSecondaryMaskStatistics(); + + void TestUS4DCylStatistics_time1(); + void TestUS4DCylAxialPlanarFigureMaskStatistics_time1(); + void TestUS4DCylSagittalPlanarFigureMaskStatistics_time1(); + void TestUS4DCylCoronalPlanarFigureMaskStatistics_time1(); + void TestUS4DCylImageMaskStatistics_time1_label_1(); + void TestUS4DCylImageMaskStatistics_time2_label_1(); + void TestUS4DCylImageMaskStatistics_time1_label_2(); + void TestUS4DCylIgnorePixelValueMaskStatistics_time1(); + void TestUS4DCylSecondaryMaskStatistics_time1(); + + void TestDifferentNBinsForHistogramStatistics(); + void TestDifferentBinSizeForHistogramStatistic(); + + void TestSwitchFromBinSizeToNBins(); + void TestSwitchFromNBinsToBinSize(); + private: - mitk::Image::Pointer m_Image; + mitk::Image::Pointer m_TestImage; + + mitk::Image::Pointer m_Pic3DImage; + mitk::Image::Pointer m_Pic3DImageMask; + mitk::Image::Pointer m_Pic3DImageMask2; + mitk::PlanarFigure::Pointer m_Pic3DPlanarFigureAxial; + mitk::PlanarFigure::Pointer m_Pic3DPlanarFigureSagittal; + mitk::PlanarFigure::Pointer m_Pic3DPlanarFigureCoronal; + + mitk::Image::Pointer m_US4DImage; + mitk::Image::Pointer m_US4DImageMask; + mitk::Image::Pointer m_US4DImageMask2; + mitk::PlanarFigure::Pointer m_US4DPlanarFigureAxial; + mitk::PlanarFigure::Pointer m_US4DPlanarFigureSagittal; + mitk::PlanarFigure::Pointer m_US4DPlanarFigureCoronal; + mitk::PlaneGeometry::Pointer m_Geometry; // calculate statistics for the given image and planarpolygon - const mitk::ImageStatisticsCalculator::Statistics ComputeStatistics( mitk::Image::Pointer image, + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer ComputeStatistics( mitk::Image::Pointer image, mitk::PlanarFigure::Pointer polygon ); - // calculate statistics for the given image and planarpolygon - const mitk::ImageStatisticsCalculator::Statistics ComputeStatistics( mitk::Image::Pointer image, + // calculate statistics for the given image and mask + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer ComputeStatistics( mitk::Image::Pointer image, mitk::Image::Pointer image_mask ); - void VerifyStatistics(const mitk::ImageStatisticsCalculator::Statistics& stats, + // universal function to calculate statistics + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer ComputeStatisticsNew(mitk::Image::Pointer image, + int timeStep=0, + mitk::MaskGenerator::Pointer maskGen=nullptr, + mitk::MaskGenerator::Pointer secondardMaskGen=nullptr, + unsigned short label=1); + + void VerifyStatistics(mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats, double testMean, double testSD, double testMedian=0); + + void VerifyStatistics(mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats, + long N, + double mean, + double MPP, + double median, + double skewness, + double kurtosis, + double uniformity, + double UPP, + double variance, + double stdev, + double min, + double max, + double RMS, + double entropy, + vnl_vector minIndex, + vnl_vector maxIndex); }; void mitkImageStatisticsCalculatorTestSuite::setUp() { - std::string filename = this->GetTestDataFilePath("ImageStatistics/testimage.dcm"); - if (filename.empty()) + std::string filename = this->GetTestDataFilePath("ImageStatistics/testimage.dcm"); + + std::string Pic3DFile = this->GetTestDataFilePath("Pic3D.nrrd"); + std::string Pic3DImageMaskFile = this->GetTestDataFilePath("ImageStatistics/Pic3D-labels.nrrd"); + std::string Pic3DImageMaskFile2 = this->GetTestDataFilePath("ImageStatistics/Pic3D-labels2.nrrd"); + std::string Pic3DAxialPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/Pic3DAxialPlanarFigure.pf"); + std::string Pic3DSagittalPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/Pic3DSagittalPlanarFigure.pf"); + std::string Pic3DCoronalPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/Pic3DCoronalPlanarFigure.pf"); + + std::string US4DFile = this->GetTestDataFilePath("US4DCyl.nrrd"); + std::string US4DImageMaskFile = this->GetTestDataFilePath("ImageStatistics/US4D-labels.nrrd"); + std::string US4DImageMaskFile2 = this->GetTestDataFilePath("ImageStatistics/US4D-labels2.nrrd"); + std::string US4DAxialPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/US4DAxialPlanarFigure.pf"); + std::string US4DSagittalPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/US4DSagittalPlanarFigure.pf"); + std::string US4DCoronalPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/US4DCoronalPlanarFigure.pf"); + + if (filename.empty() || + Pic3DFile.empty() || Pic3DImageMaskFile.empty() || + Pic3DAxialPlanarFigureFile.empty() || Pic3DSagittalPlanarFigureFile.empty() || Pic3DCoronalPlanarFigureFile.empty() || + US4DFile.empty() || US4DImageMaskFile.empty() || + US4DAxialPlanarFigureFile.empty() || US4DSagittalPlanarFigureFile.empty() || US4DCoronalPlanarFigureFile.empty()) { MITK_TEST_FAILED_MSG( << "Could not find test file" ) } MITK_TEST_OUTPUT(<< "Loading test image '" << filename << "'") - m_Image = mitk::IOUtil::LoadImage(filename); - MITK_TEST_CONDITION_REQUIRED( m_Image.IsNotNull(), "Loaded an mitk::Image" ); + m_TestImage = mitk::IOUtil::LoadImage(filename); + MITK_TEST_CONDITION_REQUIRED( m_TestImage.IsNotNull(), "Loaded an mitk::Image" ); + + m_Geometry = m_TestImage->GetSlicedGeometry()->GetPlaneGeometry(0); + MITK_TEST_CONDITION_REQUIRED( m_Geometry.IsNotNull(), "Getting image geometry" ); + + m_Pic3DImage = mitk::IOUtil::LoadImage(Pic3DFile); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DImage.IsNotNull(), "Loaded Pic3D" ); + m_Pic3DImageMask = mitk::IOUtil::LoadImage(Pic3DImageMaskFile); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DImageMask.IsNotNull(), "Loaded Pic3D image mask" ); + m_Pic3DImageMask2 = mitk::IOUtil::LoadImage(Pic3DImageMaskFile2); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DImageMask2.IsNotNull(), "Loaded Pic3D image secondary mask" ); + m_Pic3DPlanarFigureAxial = dynamic_cast(mitk::IOUtil::Load(Pic3DAxialPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DPlanarFigureAxial.IsNotNull(), "Loaded Pic3D axial planarFigure" ); + m_Pic3DPlanarFigureSagittal = dynamic_cast(mitk::IOUtil::Load(Pic3DSagittalPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DPlanarFigureSagittal.IsNotNull(), "Loaded Pic3D sagittal planarFigure" ); + m_Pic3DPlanarFigureCoronal = dynamic_cast(mitk::IOUtil::Load(Pic3DCoronalPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DPlanarFigureCoronal.IsNotNull(), "Loaded Pic3D coronal planarFigure" ); + + m_US4DImage = mitk::IOUtil::LoadImage(US4DFile); + MITK_TEST_CONDITION_REQUIRED( m_US4DImage.IsNotNull(), "Loaded US4D" ); + m_US4DImageMask = mitk::IOUtil::LoadImage(US4DImageMaskFile); + MITK_TEST_CONDITION_REQUIRED( m_US4DImageMask.IsNotNull(), "Loaded US4D image mask" ); + m_US4DImageMask2 = mitk::IOUtil::LoadImage(US4DImageMaskFile2); + MITK_TEST_CONDITION_REQUIRED( m_US4DImageMask2.IsNotNull(), "Loaded US4D image mask2" ); + m_US4DPlanarFigureAxial = dynamic_cast(mitk::IOUtil::Load(US4DAxialPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_US4DPlanarFigureAxial.IsNotNull(), "Loaded US4D axial planarFigure" ); + m_US4DPlanarFigureSagittal = dynamic_cast(mitk::IOUtil::Load(US4DSagittalPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_US4DPlanarFigureSagittal.IsNotNull(), "Loaded US4D sagittal planarFigure" ); + m_US4DPlanarFigureCoronal = dynamic_cast(mitk::IOUtil::Load(US4DCoronalPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_US4DPlanarFigureCoronal.IsNotNull(), "Loaded US4D coronal planarFigure" ); - m_Geometry = m_Image->GetSlicedGeometry()->GetPlaneGeometry(0); - MITK_TEST_CONDITION_REQUIRED( m_Geometry.IsNotNull(), "Getting image geometry" ) } void mitkImageStatisticsCalculatorTestSuite::TestCase1() { /***************************** * one whole white pixel * -> mean of 255 expected ******************************/ + MITK_INFO << std::endl << "Test case 1:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 10.5 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 10.5; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 255.0, 0.0, 255.0); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 255.0, 0.0, 255.0); } void mitkImageStatisticsCalculatorTestSuite::TestCase2() { /***************************** * half pixel in x-direction (white) * -> mean of 255 expected ******************************/ + MITK_INFO << std::endl << "Test case 2:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 10.0 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 10.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 255.0, 0.0, 255.0); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 255.0, 0.0, 255.0); } void mitkImageStatisticsCalculatorTestSuite::TestCase3() { /***************************** * half pixel in diagonal-direction (white) * -> mean of 255 expected ******************************/ + MITK_INFO << std::endl << "Test case 3:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 10.5 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 255.0, 0.0, 255.0); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 255.0, 0.0, 255.0); } void mitkImageStatisticsCalculatorTestSuite::TestCase4() { /***************************** * one pixel (white) + 2 half pixels (white) + 1 half pixel (black) * -> mean of 191.25 expected ******************************/ + MITK_INFO << std::endl << "Test case 4:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 1.1; pnt1[1] = 1.1; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 2.0; pnt2[1] = 2.0; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 3.0; pnt3[1] = 1.0; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 2.0; pnt4[1] = 0.0; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 191.25, 127.5, 254.50); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 191.25, 110.41, 242.250); } void mitkImageStatisticsCalculatorTestSuite::TestCase5() { /***************************** * whole pixel (white) + half pixel (gray) in x-direction * -> mean of 191.5 expected ******************************/ + MITK_INFO << std::endl << "Test case 5:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 191.50, 89.80, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 191.50, 63.50, 134.340); } void mitkImageStatisticsCalculatorTestSuite::TestCase6() { /***************************** * quarter pixel (black) + whole pixel (white) + half pixel (gray) in x-direction * -> mean of 191.5 expected ******************************/ + MITK_INFO << std::endl << "Test case 6:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.25; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.25; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 191.5, 89.80, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 191.5, 63.50, 134.340); } void mitkImageStatisticsCalculatorTestSuite::TestCase7() { /***************************** * half pixel (black) + whole pixel (white) + half pixel (gray) in x-direction * -> mean of 127.66 expected ******************************/ + MITK_INFO << std::endl << "Test case 7:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.0; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.0; pnt3[1] = 4.0; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.0; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 127.66, 127.5, 128.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 127.66, 104.1, 140.250); } void mitkImageStatisticsCalculatorTestSuite::TestCase8() { /***************************** * whole pixel (gray) * -> mean of 128 expected ******************************/ + MITK_INFO << std::endl << "Test case 8:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 11.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 11.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 128.0, 0.0, 128.0); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 128.0, 0.0, 128.0); } void mitkImageStatisticsCalculatorTestSuite::TestCase9() { /***************************** * whole pixel (gray) + half pixel (white) in y-direction * -> mean of 191.5 expected ******************************/ + MITK_INFO << std::endl << "Test case 9:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 12.0; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 12.0; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 191.5, 89.80, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 191.5, 63.50, 134.340); } void mitkImageStatisticsCalculatorTestSuite::TestCase10() { /***************************** * 2 whole pixel (white) + 2 whole pixel (black) in y-direction * -> mean of 127.66 expected ******************************/ + MITK_INFO << std::endl << "Test case 10:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 13.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 13.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 127.66, 127.5, 128.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 127.66, 104.1, 140.250); } void mitkImageStatisticsCalculatorTestSuite::TestCase11() { /***************************** * 9 whole pixels (white) + 3 half pixels (white) * + 3 whole pixel (black) [ + 3 slightly less than half pixels (black)] * -> mean of 204.0 expected ******************************/ + MITK_INFO << std::endl << "Test case 11:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 0.5; pnt1[1] = 0.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 3.5; pnt2[1] = 3.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 8.4999; pnt3[1] = 3.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 5.4999; pnt4[1] = 0.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 204.0, 105.58, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 204.0, 102.00, 242.250); } void mitkImageStatisticsCalculatorTestSuite::TestCase12() { /***************************** * half pixel (white) + whole pixel (white) + half pixel (black) * -> mean of 212.66 expected ******************************/ + MITK_INFO << std::endl << "Test case 12:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 9.5; pnt1[1] = 0.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 2.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 11.5; pnt3[1] = 2.5; figure2->SetControlPoint( 2, pnt3, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 212.66, 73.32, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 212.66, 59.860, 248.640); } void mitkImageStatisticsCalculatorTestSuite::TestImageMaskingEmpty() { - mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_Image, 0 ); + MITK_INFO << std::endl << "TestImageMaskingEmpty:-----------------------------------------------------------------------------------"; - this->VerifyStatistics( ComputeStatistics( m_Image, mask_image ), 0.0, 0.0, 0.0); + mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_TestImage, 0 ); + + this->VerifyStatistics( ComputeStatistics( m_TestImage, mask_image ), -21474836.480, -21474836.480, -21474836.480); // empty statisticsContainer (default values) } void mitkImageStatisticsCalculatorTestSuite::TestImageMaskingNonEmpty() { - mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_Image, 0 ); + MITK_INFO << std::endl << "TestImageMaskingNonEmpty:-----------------------------------------------------------------------------------"; + + mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_TestImage, 0 ); std::vector< itk::Index<3U> > activated_indices; itk::Index<3U> index = {{10, 8, 0}}; activated_indices.push_back( index ); index[0] = 9; index[1] = 8; index[2] = 0; activated_indices.push_back( index ); index[0] = 9; index[1] = 7; index[2] = 0; activated_indices.push_back( index ); index[0] = 10; index[1] = 7; index[2] = 0; activated_indices.push_back( index ); std::vector< itk::Index<3U> >::const_iterator indexIter = activated_indices.begin(); // activate voxel in the mask image mitk::ImagePixelWriteAccessor< unsigned char, 3> writeAccess( mask_image ); while( indexIter != activated_indices.end() ) { writeAccess.SetPixelByIndex( (*indexIter++), 1); } - this->VerifyStatistics( ComputeStatistics( m_Image, mask_image ), 127.5, 147.22, 254.5); + this->VerifyStatistics( ComputeStatistics( m_TestImage, mask_image ), 127.5, 127.5, 12.750); } void mitkImageStatisticsCalculatorTestSuite::TestRecomputeOnModifiedMask() { - mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_Image, 0 ); + MITK_INFO << std::endl << "TestRecomputeOnModifiedMask:-----------------------------------------------------------------------------------"; + mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_TestImage, 0 ); mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); - statisticsCalculator->SetImage( m_Image ); - statisticsCalculator->SetImageMask( mask_image ); - statisticsCalculator->SetMaskingModeToImage(); + statisticsCalculator->SetInputImage( m_TestImage ); + + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(mask_image); - statisticsCalculator->ComputeStatistics(); - this->VerifyStatistics( statisticsCalculator->GetStatistics(), 0.0, 0.0, 0.0); + statisticsCalculator->SetMask(imgMaskGen.GetPointer()); + + this->VerifyStatistics( statisticsCalculator->GetStatistics(), -21474836.480, -21474836.480, -21474836.480); // activate voxel in the mask image itk::Index<3U> test_index = {11, 8, 0}; mitk::ImagePixelWriteAccessor< unsigned char, 3> writeAccess( mask_image ); writeAccess.SetPixelByIndex( test_index, 1); mask_image->Modified(); - statisticsCalculator->ComputeStatistics(); - const mitk::ImageStatisticsCalculator::Statistics stat = statisticsCalculator->GetStatistics(); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stat = statisticsCalculator->GetStatistics(); this->VerifyStatistics( stat, 128.0, 0.0, 128.0); - MITK_TEST_CONDITION( stat.GetN() == 1, "Calculated mask voxel count '" << stat.GetN() << "' is equal to the desired value '" << 1 << "'" ); + MITK_TEST_CONDITION( stat->GetN() == 1, "Calculated mask voxel count '" << stat->GetN() << "' is equal to the desired value '" << 1 << "'" ); + +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DStatistics() +{ + MITK_INFO << std::endl << "Test plain Pic3D:-----------------------------------------------------------------------------------"; + long expected_N = 3211264; + double expected_mean = -365.80015345982144; + double expected_MPP = 111.80226129535752; + double expected_median = -105.16000366210938; + double expected_skewness = -0.26976612134147004; + double expected_kurtosis = 1.4655017209571437; + double expected_uniformity = 0.06087994379480554; + double expected_UPP = 0.011227934437026977; + double expected_variance = 224036.80150510342; + double expected_standarddev = 473.32525973700518; + double expected_min = -1023; + double expected_max = 1361; + double expected_RMS = 598.20276978323352; + double expected_entropy = 4.6727423654570357; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 0; + expected_minIndex[1] = 0; + expected_minIndex[2] = 0; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 139; + expected_maxIndex[1] = 182; + expected_maxIndex[2] = 43; + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DAxialPlanarFigureMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D axial pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.6719817476387417; + double expected_kurtosis = 5.8846935191205221; + double expected_MPP = 230.43933685003768; + double expected_max = 1206; + double expected_mean = 182.30282131661443; + double expected_median = 95.970001220703125; + double expected_min = -156; + long expected_N = 3190; + double expected_RMS = 301.93844376702253; + double expected_skewness = 1.6400489794326298; + double expected_standarddev = 240.69172225993557; + double expected_UPP = 0.024889790784288681; + double expected_uniformity = 0.027579917650180332; + double expected_variance = 57932.505164453964; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 156; + expected_minIndex[1] = 133; + expected_minIndex[2] = 24; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 125; + expected_maxIndex[1] = 167; + expected_maxIndex[2] = 24; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_Pic3DImage); + pfMaskGen->SetPlanarFigure(m_Pic3DPlanarFigureAxial); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, pfMaskGen.GetPointer()); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DSagittalPlanarFigureMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D sagittal pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.6051911962074286; + double expected_kurtosis = 6.5814062739142338; + double expected_MPP = 249.03202846975088; + double expected_max = 1240; + double expected_mean = 233.93602693602693; + double expected_median = 174.9849853515625; + double expected_min = -83; + long expected_N = 1188; + double expected_RMS = 332.03230188484594; + double expected_skewness = 1.7489809015501814; + double expected_standarddev = 235.62551813489128; + double expected_UPP = 0.026837539253364174; + double expected_uniformity = 0.027346982734188126; + double expected_variance = 55519.384796335973; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 128; + expected_minIndex[1] = 119; + expected_minIndex[2] = 22; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 128; + expected_maxIndex[1] = 167; + expected_maxIndex[2] = 22; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_Pic3DImage); + pfMaskGen->SetPlanarFigure(m_Pic3DPlanarFigureSagittal); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, pfMaskGen.GetPointer()); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DCoronalPlanarFigureMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D coronal pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 6.0677398647867449; + double expected_kurtosis = 1.6242929941303372; + double expected_MPP = 76.649350649350652; + double expected_max = 156; + double expected_mean = -482.14807692307693; + double expected_median = -660.07501220703125; + double expected_min = -897; + long expected_N = 520; + double expected_RMS = 595.09446729069839; + double expected_skewness = 0.51691492278851858; + double expected_standarddev = 348.81321207686312; + double expected_UPP = 0.0021560650887573964; + double expected_uniformity = 0.020295857988165685; + double expected_variance = 121670.6569193787; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 217; + expected_minIndex[1] = 127; + expected_minIndex[2] = 43; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 209; + expected_maxIndex[1] = 127; + expected_maxIndex[2] = 39; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_Pic3DImage); + pfMaskGen->SetPlanarFigure(m_Pic3DPlanarFigureCoronal); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, pfMaskGen.GetPointer()); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DImageMaskStatistics_label1() +{ + MITK_INFO << std::endl << "Test Pic3D image mask label 1 pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.695858251095868; + double expected_kurtosis = 4.2728827997815717; + double expected_MPP = 413.52408256880733; + double expected_max = 1206; + double expected_mean = 413.52408256880733; + double expected_median = 324; + double expected_min = 6; + long expected_N = 872; + double expected_RMS = 472.02024695145235; + double expected_skewness = 1.3396074364415382; + double expected_standarddev = 227.59821323493802; + double expected_UPP = 0.029758648261930806; + double expected_uniformity = 0.029758648261930806; + double expected_variance = 51800.946667736309; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 135; + expected_minIndex[1] = 158; + expected_minIndex[2] = 24; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 125; + expected_maxIndex[1] = 167; + expected_maxIndex[2] = 24; + + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(m_Pic3DImageMask); + imgMaskGen->SetInputImage(m_Pic3DImage); + imgMaskGen->SetTimeStep(0); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, imgMaskGen.GetPointer(), nullptr, 1); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DImageMaskStatistics_label2() +{ + MITK_INFO << std::endl << "Test Pic3D image mask label 2 pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 4.3685781901212764; + double expected_kurtosis = 9.7999112757587934; + double expected_MPP = -nan(""); + double expected_max = -145; + double expected_mean = -897.92833876221493; + double expected_median = -969.16499900817871; + double expected_min = -1008; + long expected_N = 307; + double expected_RMS = 913.01496468179471; + double expected_skewness = 2.6658524648889736; + double expected_standarddev = 165.29072623903585; + double expected_UPP = 0; + double expected_uniformity = 0.087544695434434425; + double expected_variance = 27321.024180627897; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 170; + expected_minIndex[1] = 60; + expected_minIndex[2] = 24; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 173; + expected_maxIndex[1] = 57; + expected_maxIndex[2] = 24; + + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(m_Pic3DImageMask); + imgMaskGen->SetInputImage(m_Pic3DImage); + imgMaskGen->SetTimeStep(0); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, imgMaskGen.GetPointer(), nullptr, 2); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DIgnorePixelValueMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D ignore zero pixels:-----------------------------------------------------------------------------------"; + + double expected_entropy = 4.671045011438645; + double expected_kurtosis = 1.4638176488404484; + double expected_MPP = 111.80226129535752; + double expected_max = 1361; + double expected_mean = -366.48547402877585; + double expected_median = -105.16000366210938; + double expected_min = -1023; + long expected_N = 3205259; + double expected_RMS = 598.76286909522139; + double expected_skewness = -0.26648854845130782; + double expected_standarddev = 473.50329537717545; + double expected_UPP = 0.011270044547276429; + double expected_uniformity = 0.061029773286547614; + double expected_variance = 224205.37073304466; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 0; + expected_minIndex[1] = 0; + expected_minIndex[2] = 0; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 139; + expected_maxIndex[1] = 182; + expected_maxIndex[2] = 43; + + mitk::IgnorePixelMaskGenerator::Pointer ignPixelValMask = mitk::IgnorePixelMaskGenerator::New(); + ignPixelValMask->SetInputImage(m_Pic3DImage); + ignPixelValMask->SetIgnoredPixelValue(0); + ignPixelValMask->SetTimeStep(0); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, ignPixelValMask.GetPointer()); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DSecondaryMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D ignore zero pixels AND Image mask 2:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.9741637167320176; + double expected_kurtosis = 3.490663358061596; + double expected_MPP = 332.43534482758622; + double expected_max = 1206; + double expected_mean = 320.63333333333333; + double expected_median = 265.06500244140625; + double expected_min = -57; + long expected_N = 720; + double expected_RMS = 433.57749531594055; + double expected_skewness = 1.1047775627624981; + double expected_standarddev = 291.86248474238687; + double expected_UPP = 0.020628858024691339; + double expected_uniformity = 0.021377314814814797; + double expected_variance = 85183.710000000006; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 116; + expected_minIndex[1] = 170; + expected_minIndex[2] = 24; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 125; + expected_maxIndex[1] = 167; + expected_maxIndex[2] = 24; + + mitk::IgnorePixelMaskGenerator::Pointer ignPixelValMask = mitk::IgnorePixelMaskGenerator::New(); + ignPixelValMask->SetInputImage(m_Pic3DImage); + ignPixelValMask->SetIgnoredPixelValue(0); + ignPixelValMask->SetTimeStep(0); + + mitk::ImageMaskGenerator::Pointer imgMaskGen2 = mitk::ImageMaskGenerator::New(); + imgMaskGen2->SetImageMask(m_Pic3DImageMask2); + imgMaskGen2->SetInputImage(m_Pic3DImage); + imgMaskGen2->SetTimeStep(0); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, imgMaskGen2.GetPointer(), ignPixelValMask.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylStatistics_time1() +{ + MITK_INFO << std::endl << "Test plain US4D timeStep1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 4.8272774900452502; + double expected_kurtosis = 6.1336513352934432; + double expected_MPP = 53.395358640738536; + double expected_max = 199; + double expected_mean = 35.771298153622375; + double expected_median = 20.894999504089355; + double expected_min = 0; + long expected_N = 3409920; + double expected_RMS = 59.244523377028408; + double expected_skewness = 1.8734292240015058; + double expected_standarddev = 47.226346233600559; + double expected_UPP = 0.12098731125004937; + double expected_uniformity = 0.12098731125004937; + double expected_variance = 2230.3277785759178; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 0; + expected_minIndex[1] = 0; + expected_minIndex[2] = 0; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 268; + expected_maxIndex[1] = 101; + expected_maxIndex[2] = 0; + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylAxialPlanarFigureMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4D axial pf timeStep1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 6.218151288002292; + double expected_kurtosis = 1.7322676370242023; + double expected_MPP = 121.11663807890223; + double expected_max = 199; + double expected_mean = 121.11663807890223; + double expected_median = 120.14999771118164; + double expected_min = 9; + long expected_N = 2332; + double expected_RMS = 134.41895158590751; + double expected_skewness = -0.1454808104597369; + double expected_standarddev = 58.30278317472294; + double expected_UPP = 0.021354765820606133; + double expected_uniformity = 0.021354765820606133; + double expected_variance = 3399.214525918756; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 129; + expected_minIndex[1] = 131; + expected_minIndex[2] = 19; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 126; + expected_maxIndex[1] = 137; + expected_maxIndex[2] = 19; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_US4DImage); + pfMaskGen->SetPlanarFigure(m_US4DPlanarFigureAxial); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, pfMaskGen.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylSagittalPlanarFigureMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4D sagittal pf timeStep1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.2003987046387508; + double expected_kurtosis = 2.7574491062430142; + double expected_MPP = 26.212534059945504; + double expected_max = 59; + double expected_mean = 26.176870748299319; + double expected_median = 26.254999160766602; + double expected_min = 0; + long expected_N = 735; + double expected_RMS = 28.084905283121476; + double expected_skewness = 0.18245181360752327; + double expected_standarddev = 10.175133541567705; + double expected_UPP = 0.032921467906890628; + double expected_uniformity = 0.032921467906890628; + double expected_variance = 103.53334258873615; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 147; + expected_minIndex[1] = 94; + expected_minIndex[2] = 21; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 147; + expected_maxIndex[1] = 77; + expected_maxIndex[2] = 24; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_US4DImage); + pfMaskGen->SetPlanarFigure(m_US4DPlanarFigureSagittal); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, pfMaskGen.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylCoronalPlanarFigureMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4D coronal pf timeStep1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.8892941136639161; + double expected_kurtosis = 4.6434920707409564; + double expected_MPP = 55.486426346239433; + double expected_max = 199; + double expected_mean = 55.118479221927501; + double expected_median = 36.815000534057617; + double expected_min = 0; + long expected_N = 2262; + double expected_RMS = 71.98149752438627; + double expected_skewness = 1.4988288344523237; + double expected_standarddev = 46.29567187238105; + double expected_UPP = 0.023286748110675673; + double expected_uniformity = 0.023286748110675673; + double expected_variance = 2143.2892341151742; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 214; + expected_minIndex[1] = 169; + expected_minIndex[2] = 10; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 99; + expected_maxIndex[1] = 169; + expected_maxIndex[2] = 17; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_US4DImage); + pfMaskGen->SetPlanarFigure(m_US4DPlanarFigureCoronal); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, pfMaskGen.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylImageMaskStatistics_time1_label_1() +{ + MITK_INFO << std::endl << "Test US4D image mask time 1 label 1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.0082903903398677; + double expected_kurtosis = 3.6266994778237809; + double expected_MPP = 169.58938547486034; + double expected_max = 199; + double expected_mean = 169.58938547486034; + double expected_median = 187.44000244140625; + double expected_min = 63; + long expected_N = 716; + double expected_RMS = 173.09843164831432; + double expected_skewness = -1.2248969838579555; + double expected_standarddev = 34.677188083311712; + double expected_UPP = 0.076601073624418703; + double expected_uniformity = 0.076601073624418703; + double expected_variance = 1202.5073733653758; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 82; + expected_minIndex[1] = 158; + expected_minIndex[2] = 19; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 126; + expected_maxIndex[1] = 140; + expected_maxIndex[2] = 19; + + mitk::ImageMaskGenerator::Pointer imgMask1 = mitk::ImageMaskGenerator::New(); + imgMask1->SetInputImage(m_US4DImage); + imgMask1->SetImageMask(m_US4DImageMask); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, imgMask1.GetPointer(), nullptr, 1); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylImageMaskStatistics_time2_label_1() +{ + MITK_INFO << std::endl << "Test US4D image mask time 2 label 1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.1857604214916506; + double expected_kurtosis = 3.0692303858330683; + double expected_MPP = 167.97194163860831; + double expected_max = 199; + double expected_mean = 167.97194163860831; + double expected_median = 184.39499664306641; + double expected_min = 72; + long expected_N = 891; + double expected_RMS = 171.67986611998634; + double expected_skewness = -1.1221651136259736; + double expected_standarddev = 35.488071983870803; + double expected_UPP = 0.063124070232188439; + double expected_uniformity = 0.063124070232188439; + double expected_variance = 1259.4032531323958; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 103; + expected_minIndex[1] = 212; + expected_minIndex[2] = 19; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 102; + expected_maxIndex[1] = 168; + expected_maxIndex[2] = 19; + + mitk::ImageMaskGenerator::Pointer imgMask1 = mitk::ImageMaskGenerator::New(); + imgMask1->SetInputImage(m_US4DImage); + imgMask1->SetImageMask(m_US4DImageMask); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 2, imgMask1.GetPointer(), nullptr, 1); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylImageMaskStatistics_time1_label_2() +{ + MITK_INFO << std::endl << "Test US4D image mask time 1 label 2:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.0822234230119001; + double expected_kurtosis = 2.4346603343623747; + double expected_MPP = 20.733626373626375; + double expected_max = 46; + double expected_mean = 20.624836029733274; + double expected_median = 20.010000228881836; + double expected_min = 0; + long expected_N = 2287; + double expected_RMS = 22.508347574573804; + double expected_skewness = 0.13837218490626488; + double expected_standarddev = 9.0134260569684965; + double expected_UPP = 0.034783970308787; + double expected_uniformity = 0.034783970308787; + double expected_variance = 81.241849284438644; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 178; + expected_minIndex[1] = 76; + expected_minIndex[2] = 19; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 198; + expected_maxIndex[1] = 90; + expected_maxIndex[2] = 19; + + mitk::ImageMaskGenerator::Pointer imgMask1 = mitk::ImageMaskGenerator::New(); + imgMask1->SetInputImage(m_US4DImage); + imgMask1->SetImageMask(m_US4DImageMask); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, imgMask1.GetPointer(), nullptr, 2); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylIgnorePixelValueMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4D ignore zero pixels:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.8609813848087962; + double expected_kurtosis = 4.7556214582883651; + double expected_MPP = 53.395358640738536; + double expected_max = 199; + double expected_mean = 53.395358640738536; + double expected_median = 35.649999618530273; + double expected_min = 1; + long expected_N = 2284417; + double expected_RMS = 72.382339046507084; + double expected_skewness = 1.588289859859108; + double expected_standarddev = 48.868585834566694; + double expected_UPP = 0.023927063695115193; + double expected_uniformity = 0.023927063695115193; + double expected_variance = 2388.1386814704128; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 187; + expected_minIndex[1] = 19; + expected_minIndex[2] = 0; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 268; + expected_maxIndex[1] = 101; + expected_maxIndex[2] = 0; + + mitk::IgnorePixelMaskGenerator::Pointer ignPixelValMask = mitk::IgnorePixelMaskGenerator::New(); + ignPixelValMask->SetInputImage(m_US4DImage); + ignPixelValMask->SetIgnoredPixelValue(0); + ignPixelValMask->SetTimeStep(1); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, ignPixelValMask.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); } -const mitk::ImageStatisticsCalculator::Statistics + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylSecondaryMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4d ignore zero pixels AND Image mask 2:-----------------------------------------------------------------------------------"; + + double expected_entropy = 4.9955858614274558; + double expected_kurtosis = 17.471042803365179; + double expected_MPP = 32.791403286978507; + double expected_max = 199; + double expected_mean = 32.791403286978507; + double expected_median = 25.75; + double expected_min = 1; + long expected_N = 17402; + double expected_RMS = 42.776697859745241; + double expected_skewness = 3.3991813038552596; + double expected_standarddev = 27.469433016621732; + double expected_UPP = 0.043040554251756687; + double expected_uniformity = 0.043040554251756687; + double expected_variance = 754.56975025466807; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 177; + expected_minIndex[1] = 27; + expected_minIndex[2] = 36; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 109; + expected_maxIndex[1] = 116; + expected_maxIndex[2] = 36; + + mitk::IgnorePixelMaskGenerator::Pointer ignPixelValMask = mitk::IgnorePixelMaskGenerator::New(); + ignPixelValMask->SetInputImage(m_US4DImage); + ignPixelValMask->SetIgnoredPixelValue(0); + + mitk::ImageMaskGenerator::Pointer imgMaskGen2 = mitk::ImageMaskGenerator::New(); + imgMaskGen2->SetImageMask(m_US4DImageMask2); + imgMaskGen2->SetInputImage(m_US4DImage); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, imgMaskGen2.GetPointer(), ignPixelValMask.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer mitkImageStatisticsCalculatorTestSuite::ComputeStatistics( mitk::Image::Pointer image, mitk::PlanarFigure::Pointer polygon ) { mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); - statisticsCalculator->SetImage( image ); - statisticsCalculator->SetMaskingModeToPlanarFigure(); - statisticsCalculator->SetPlanarFigure( polygon ); + statisticsCalculator->SetInputImage( image ); + + statisticsCalculator->SetNBinsForHistogramStatistics(10); + + mitk::PlanarFigureMaskGenerator::Pointer planFigMaskGen = mitk::PlanarFigureMaskGenerator::New(); + planFigMaskGen->SetInputImage(image); + planFigMaskGen->SetPlanarFigure(polygon); + + statisticsCalculator->SetMask(planFigMaskGen.GetPointer()); try { - statisticsCalculator->ComputeStatistics(); return statisticsCalculator->GetStatistics(); } catch( ... ) { } - return mitk::ImageStatisticsCalculator::Statistics(); + return mitk::ImageStatisticsCalculator::StatisticsContainer::New(); } -const mitk::ImageStatisticsCalculator::Statistics +const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer mitkImageStatisticsCalculatorTestSuite::ComputeStatistics(mitk::Image::Pointer image, mitk::Image::Pointer image_mask ) { mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); - statisticsCalculator->SetImage( image ); - statisticsCalculator->SetImageMask( image_mask ); - statisticsCalculator->SetMaskingModeToImage(); + statisticsCalculator->SetInputImage(image); + + statisticsCalculator->SetNBinsForHistogramStatistics(10); - statisticsCalculator->ComputeStatistics(); + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(image_mask); + statisticsCalculator->SetMask(imgMaskGen.GetPointer()); return statisticsCalculator->GetStatistics(); } +const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer +mitkImageStatisticsCalculatorTestSuite::ComputeStatisticsNew(mitk::Image::Pointer image, + int timeStep, + mitk::MaskGenerator::Pointer maskGen, + mitk::MaskGenerator::Pointer secondardMaskGen, + unsigned short label) +{ + mitk::ImageStatisticsCalculator::Pointer imgStatCalc = mitk::ImageStatisticsCalculator::New(); + imgStatCalc->SetInputImage(image); + + if (maskGen.IsNotNull()) + { + imgStatCalc->SetMask(maskGen.GetPointer()); + if (secondardMaskGen.IsNotNull()) + { + imgStatCalc->SetSecondaryMask(secondardMaskGen.GetPointer()); + } + } + + return imgStatCalc->GetStatistics(timeStep, label); +} -void mitkImageStatisticsCalculatorTestSuite::VerifyStatistics(const mitk::ImageStatisticsCalculator::Statistics& stats, +void mitkImageStatisticsCalculatorTestSuite::VerifyStatistics(mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats, double testMean, double testSD, double testMedian) { - int tmpMean = stats.GetMean() * 100; + int tmpMean = stats->GetMean() * 100; double calculatedMean = tmpMean / 100.0; MITK_TEST_CONDITION( calculatedMean == testMean, "Calculated mean grayvalue '" << calculatedMean << "' is equal to the desired value '" << testMean << "'" ); - int tmpSD = stats.GetSigma() * 100; + int tmpSD = stats->GetStd() * 100; double calculatedSD = tmpSD / 100.0; MITK_TEST_CONDITION( calculatedSD == testSD, "Calculated grayvalue sd '" << calculatedSD << "' is equal to the desired value '" << testSD <<"'" ); - int tmpMedian = stats.GetMedian() * 100; + int tmpMedian = stats->GetMedian() * 100; double calculatedMedian = tmpMedian / 100.0; MITK_TEST_CONDITION( testMedian == calculatedMedian, "Calculated median grayvalue '" << calculatedMedian << "' is equal to the desired value '" << testMedian << "'"); } +void mitkImageStatisticsCalculatorTestSuite::VerifyStatistics(mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats, + long N, + double mean, + double MPP, + double median, + double skewness, + double kurtosis, + double uniformity, + double UPP, + double variance, + double stdev, + double min, + double max, + double RMS, + double entropy, + vnl_vector minIndex, + vnl_vector maxIndex) +{ + MITK_TEST_CONDITION(std::abs(stats->GetN() - N) < mitk::eps, "calculated N: " << stats->GetN() << " expected N: " << N); + MITK_TEST_CONDITION(std::abs(stats->GetMean() - mean) < mitk::eps, "calculated mean: " << stats->GetMean() << " expected mean: " << mean); + // in one test case MPP is None because the roi has no positive pixels + if (!std::isnan(stats->GetMPP())) + { + MITK_TEST_CONDITION(std::abs(stats->GetMPP() - MPP) < mitk::eps, "calculated MPP: " << stats->GetMPP() << " expected MPP: " << MPP); + } + MITK_TEST_CONDITION(std::abs(stats->GetMedian() - median) < mitk::eps, "calculated median: " << stats->GetMedian() << " expected median: " << median); + MITK_TEST_CONDITION(std::abs(stats->GetSkewness() - skewness) < mitk::eps, "calculated skewness: " << stats->GetSkewness() << " expected skewness: " << skewness); + MITK_TEST_CONDITION(std::abs(stats->GetKurtosis() - kurtosis) < mitk::eps, "calculated kurtosis: " << stats->GetKurtosis() << " expected kurtosis: " << kurtosis); + MITK_TEST_CONDITION(std::abs(stats->GetUniformity() - uniformity) < mitk::eps, "calculated uniformity: " << stats->GetUniformity() << " expected uniformity: " << uniformity); + MITK_TEST_CONDITION(std::abs(stats->GetUPP() - UPP) < mitk::eps, "calculated UPP: " << stats->GetUPP() << " expected UPP: " << UPP); + MITK_TEST_CONDITION(std::abs(stats->GetVariance() - variance) < mitk::eps, "calculated variance: " << stats->GetVariance() << " expected variance: " << variance); + MITK_TEST_CONDITION(std::abs(stats->GetStd() - stdev) < mitk::eps, "calculated stdev: " << stats->GetStd() << " expected stdev: " << stdev); + MITK_TEST_CONDITION(std::abs(stats->GetMin() - min) < mitk::eps, "calculated min: " << stats->GetMin() << " expected min: " << min); + MITK_TEST_CONDITION(std::abs(stats->GetMax() - max) < mitk::eps, "calculated max: " << stats->GetMax() << " expected max: " << max); + MITK_TEST_CONDITION(std::abs(stats->GetRMS() - RMS) < mitk::eps, "calculated RMS: " << stats->GetRMS() << " expected RMS: " << RMS); + MITK_TEST_CONDITION(std::abs(stats->GetEntropy() - entropy) < mitk::eps, "calculated entropy: " << stats->GetEntropy() << " expected entropy: " << entropy); + for (unsigned int i = 0; i < minIndex.size(); ++i) + { + MITK_TEST_CONDITION(std::abs(stats->GetMinIndex()[i] - minIndex[i]) < mitk::eps, "minIndex [" << i << "] = " << stats->GetMinIndex()[i] << " expected: " << minIndex[i]); + } + for (unsigned int i = 0; i < maxIndex.size(); ++i) + { + MITK_TEST_CONDITION(std::abs(stats->GetMaxIndex()[i] - maxIndex[i]) < mitk::eps, "maxIndex [" << i << "] = " << stats->GetMaxIndex()[i] << " expected: " << maxIndex[i]); + } +} + void mitkImageStatisticsCalculatorTestSuite::TestUninitializedImage() { /***************************** * loading uninitialized image to datastorage ******************************/ + MITK_INFO << std::endl << "Test uninitialized image: -----------------------------------------------------------------------------------"; MITK_TEST_FOR_EXCEPTION_BEGIN(mitk::Exception) mitk::Image::Pointer image = mitk::Image::New(); mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(image); mitk::ImageStatisticsCalculator::Pointer is = mitk::ImageStatisticsCalculator::New(); - is->ComputeStatistics(); + is->GetStatistics(); MITK_TEST_FOR_EXCEPTION_END(mitk::Exception) } MITK_TEST_SUITE_REGISTRATION(mitkImageStatisticsCalculator) diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsHotspotTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsHotspotTest.cpp index baa2df1120..5372b3b769 100644 --- a/Modules/ImageStatistics/Testing/mitkImageStatisticsHotspotTest.cpp +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsHotspotTest.cpp @@ -1,632 +1,649 @@ /*=================================================================== 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 "mitkImageStatisticsCalculator.h" #include "itkMultiGaussianImageSource.h" #include "mitkTestingMacros.h" #include "mitkImageCast.h" #include #include #include #include +#include +#include + /** \section hotspotCalculationTestCases Testcases To see the different Hotspot-Testcases have a look at the \ref hotspottestdoc. Note from an intensive session of checking the test results: - itk::MultiGaussianImageSource needs a review - the test idea is ok, but the combination of XML files for parameters and MultiGaussianImageSource has serious flaws - the XML file should contain exactly the parameters that MultiGaussianImageSource requires - in contrast, now the XML file mentions index coordinates for gaussian centers while the MultiGaussianImageSource expects world coordinates - this requires a transformation (index * spacing assuming no rotation) that was actually broken until recently */ struct mitkImageStatisticsHotspotTestClass { /** \brief Test parameters for one test case. Describes all aspects of a single test case: - parameters to generate a test image - parameters of a ROI that describes where to calculate statistics - expected statistics results */ struct Parameters { public: // XML-Tag /** \brief XML-Tag "image-rows": size of x-dimension */ int m_ImageRows; /** \brief XML-Tag "image-columns": size of y-dimension */ int m_ImageColumns; /** \brief XML-Tag "image-slices": size of z-dimension */ int m_ImageSlices; /** \brief XML-Tag "numberOfGaussians": number of used gauss-functions */ int m_NumberOfGaussian; /** \brief XML-Tags "spacingX", "spacingY", "spacingZ": spacing of image in every direction */ double m_Spacing[3]; /** \brief XML-Tag "entireHotSpotInImage" */ unsigned int m_EntireHotspotInImage; // XML-Tag /** \brief XML-Tag "centerIndexX: gaussian parameter \warning This parameter READS the centerIndexX parameter from file and is THEN MISUSED to calculate some position in world coordinates, so we require double. */ std::vector m_CenterX; /** \brief XML-Tag "centerIndexY: gaussian parameter \warning This parameter READS the centerIndexX parameter from file and is THEN MISUSED to calculate some position in world coordinates, so we require double. */ std::vector m_CenterY; /** \brief XML-Tag "centerIndexZ: gaussian parameter \warning This parameter READS the centerIndexX parameter from file and is THEN MISUSED to calculate some position in world coordinates, so we require double. */ std::vector m_CenterZ; /** \brief XML-Tag "deviationX: gaussian parameter */ std::vector m_SigmaX; /** \brief XML-Tag "deviationY: gaussian parameter */ std::vector m_SigmaY; /** \brief XML-Tag "deviationZ: gaussian parameter */ std::vector m_SigmaZ; /** \brief XML-Tag "altitude: gaussian parameter */ std::vector m_Altitude; // XML-Tag /** \brief XML-Tag "numberOfLabels": number of different labels which appear in the mask */ unsigned int m_NumberOfLabels; /** \brief XML-Tag "hotspotRadiusInMM": radius of hotspot */ double m_HotspotRadiusInMM; // XML-Tag /** \brief XML-Tag "maximumSizeX": maximum position of ROI in x-dimension */ vnl_vector m_MaxIndexX; /** \brief XML-Tag "minimumSizeX": minimum position of ROI in x-dimension */ vnl_vector m_MinIndexX; /** \brief XML-Tag "maximumSizeX": maximum position of ROI in y-dimension */ vnl_vector m_MaxIndexY; /** \brief XML-Tag "minimumSizeX": minimum position of ROI in y-dimension */ vnl_vector m_MinIndexY; /** \brief XML-Tag "maximumSizeX": maximum position of ROI in z-dimension */ vnl_vector m_MaxIndexZ; /** \brief XML-Tag "minimumSizeX": minimum position of ROI in z-dimension */ vnl_vector m_MinIndexZ; /** \brief XML-Tag "label": value of label */ vnl_vector m_Label; //XML-Tag /** \brief XML-Tag "minimum": minimum inside hotspot */ vnl_vector m_HotspotMin; /** \brief XML-Tag "maximum": maximum inside hotspot */ vnl_vector m_HotspotMax; /** \brief XML-Tag "mean": mean value of hotspot */ vnl_vector m_HotspotMean; /** \brief XML-Tag "maximumIndexX": x-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMaxIndexX; /** \brief XML-Tag "maximumIndexX": y-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMaxIndexY; /** \brief XML-Tag "maximumIndexX": z-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMaxIndexZ; /** \brief XML-Tag "maximumIndexX": x-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMinIndexX; /** \brief XML-Tag "maximumIndexX": y-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMinIndexY; /** \brief XML-Tag "maximumIndexX": z-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMinIndexZ; /** \brief XML-Tag "maximumIndexX": x-coordinate of hotspot-location */ vnl_vector m_HotspotIndexX; /** \brief XML-Tag "maximumIndexX": y-coordinate of hotspot-location */ vnl_vector m_HotspotIndexY; /** \brief XML-Tag "maximumIndexX": z-coordinate of hotspot-location */ vnl_vector m_HotspotIndexZ; }; /** \brief Find/Convert integer attribute in itk::DOMNode. */ static int GetIntegerAttribute(itk::DOMNode* domNode, const std::string& tag) { assert(domNode); MITK_TEST_CONDITION_REQUIRED( domNode->HasAttribute(tag), "Tag '" << tag << "' is defined in test parameters" ); std::string attributeValue = domNode->GetAttribute(tag); int resultValue; try { //MITK_TEST_OUTPUT( << "Converting tag value '" << attributeValue << "' for tag '" << tag << "' to integer"); std::stringstream(attributeValue) >> resultValue; return resultValue; } catch(std::exception& /*e*/) { MITK_TEST_CONDITION_REQUIRED(false, "Convert tag value '" << attributeValue << "' for tag '" << tag << "' to integer"); return 0; // just to satisfy compiler } } /** \brief Find/Convert double attribute in itk::DOMNode. */ static double GetDoubleAttribute(itk::DOMNode* domNode, const std::string& tag) { assert(domNode); MITK_TEST_CONDITION_REQUIRED( domNode->HasAttribute(tag), "Tag '" << tag << "' is defined in test parameters" ); std::string attributeValue = domNode->GetAttribute(tag); double resultValue; try { //MITK_TEST_OUTPUT( << "Converting tag value '" << attributeValue << "' for tag '" << tag << "' to double"); std::stringstream(attributeValue) >> resultValue; return resultValue; } catch(std::exception& /*e*/) { MITK_TEST_CONDITION_REQUIRED(false, "Convert tag value '" << attributeValue << "' for tag '" << tag << "' to double"); return 0.0; // just to satisfy compiler } } /** \brief Read XML file describing the test parameters. Reads XML file given in first commandline parameter in order to construct a Parameters structure. The XML file should be structurs as the following example, i.e. we describe the three test aspects of Parameters in four different tags, with all the details described as tag attributes. */ /** \verbatim \endverbatim */ static Parameters ParseParameters(int argc, char* argv[]) { MITK_TEST_CONDITION_REQUIRED(argc == 2, "Test is invoked with exactly 1 parameter (XML parameters file)"); MITK_INFO << "Reading parameters from file '" << argv[1] << "'"; std::string filename = argv[1]; Parameters result; itk::DOMNodeXMLReader::Pointer xmlReader = itk::DOMNodeXMLReader::New(); xmlReader->SetFileName( filename ); try { xmlReader->Update(); itk::DOMNode::Pointer domRoot = xmlReader->GetOutput(); typedef std::vector NodeList; NodeList testimages; domRoot->GetChildren("testimage", testimages); MITK_TEST_CONDITION_REQUIRED( testimages.size() == 1, "One test image defined" ) itk::DOMNode* testimage = testimages[0]; result.m_ImageRows = GetIntegerAttribute( testimage, "image-rows" ); result.m_ImageColumns = GetIntegerAttribute( testimage, "image-columns" ); result.m_ImageSlices = GetIntegerAttribute( testimage, "image-slices" ); result.m_NumberOfGaussian = GetIntegerAttribute( testimage, "numberOfGaussians" ); result.m_Spacing[0] = GetDoubleAttribute(testimage, "spacingX"); result.m_Spacing[1] = GetDoubleAttribute(testimage, "spacingY"); result.m_Spacing[2] = GetDoubleAttribute(testimage, "spacingZ"); result.m_EntireHotspotInImage = GetIntegerAttribute( testimage, "entireHotSpotInImage" ); MITK_TEST_OUTPUT( << "Read size parameters (x,y,z): " << result.m_ImageRows << "," << result.m_ImageColumns << "," << result.m_ImageSlices); MITK_TEST_OUTPUT( << "Read spacing parameters (x,y,z): " << result.m_Spacing[0] << "," << result.m_Spacing[1] << "," << result.m_Spacing[2]); NodeList gaussians; testimage->GetChildren("gaussian", gaussians); MITK_TEST_CONDITION_REQUIRED( gaussians.size() >= 1, "At least one gaussian is defined" ) result.m_CenterX.resize(result.m_NumberOfGaussian); result.m_CenterY.resize(result.m_NumberOfGaussian); result.m_CenterZ.resize(result.m_NumberOfGaussian); result.m_SigmaX.resize(result.m_NumberOfGaussian); result.m_SigmaY.resize(result.m_NumberOfGaussian); result.m_SigmaZ.resize(result.m_NumberOfGaussian); result.m_Altitude.resize(result.m_NumberOfGaussian); for(int i = 0; i < result.m_NumberOfGaussian ; ++i) { itk::DOMNode* gaussian = gaussians[i]; result.m_CenterX[i] = GetIntegerAttribute(gaussian, "centerIndexX"); result.m_CenterY[i] = GetIntegerAttribute(gaussian, "centerIndexY"); result.m_CenterZ[i] = GetIntegerAttribute(gaussian, "centerIndexZ"); result.m_SigmaX[i] = GetDoubleAttribute(gaussian, "deviationX"); result.m_SigmaY[i] = GetDoubleAttribute(gaussian, "deviationY"); result.m_SigmaZ[i] = GetDoubleAttribute(gaussian, "deviationZ"); result.m_Altitude[i] = GetDoubleAttribute(gaussian, "altitude"); result.m_CenterX[i] = result.m_CenterX[i] * result.m_Spacing[0]; result.m_CenterY[i] = result.m_CenterY[i] * result.m_Spacing[1]; result.m_CenterZ[i] = result.m_CenterZ[i] * result.m_Spacing[2]; result.m_SigmaX[i] = result.m_SigmaX[i] * result.m_Spacing[0]; result.m_SigmaY[i] = result.m_SigmaY[i] * result.m_Spacing[1]; result.m_SigmaZ[i] = result.m_SigmaZ[i] * result.m_Spacing[2]; } NodeList segmentations; domRoot->GetChildren("segmentation", segmentations); MITK_TEST_CONDITION_REQUIRED( segmentations.size() == 1, "One segmentation defined"); itk::DOMNode* segmentation = segmentations[0]; result.m_NumberOfLabels = GetIntegerAttribute(segmentation, "numberOfLabels"); result.m_HotspotRadiusInMM = GetDoubleAttribute(segmentation, "hotspotRadiusInMM"); // read ROI parameters, fill result structure NodeList rois; segmentation->GetChildren("roi", rois); MITK_TEST_CONDITION_REQUIRED( rois.size() >= 1, "At least one ROI defined" ) result.m_MaxIndexX.set_size(result.m_NumberOfLabels); result.m_MinIndexX.set_size(result.m_NumberOfLabels); result.m_MaxIndexY.set_size(result.m_NumberOfLabels); result.m_MinIndexY.set_size(result.m_NumberOfLabels); result.m_MaxIndexZ.set_size(result.m_NumberOfLabels); result.m_MinIndexZ.set_size(result.m_NumberOfLabels); result.m_Label.set_size(result.m_NumberOfLabels); for(unsigned int i = 0; i < rois.size(); ++i) { result.m_MaxIndexX[i] = GetIntegerAttribute(rois[i], "maximumIndexX"); result.m_MinIndexX[i] = GetIntegerAttribute(rois[i], "minimumIndexX"); result.m_MaxIndexY[i] = GetIntegerAttribute(rois[i], "maximumIndexY"); result.m_MinIndexY[i] = GetIntegerAttribute(rois[i], "minimumIndexY"); result.m_MaxIndexZ[i] = GetIntegerAttribute(rois[i], "maximumIndexZ"); result.m_MinIndexZ[i] = GetIntegerAttribute(rois[i], "minimumIndexZ"); result.m_Label[i] = GetIntegerAttribute(rois[i], "label"); } // read statistic parameters, fill result structure NodeList statistics; domRoot->GetChildren("statistic", statistics); MITK_TEST_CONDITION_REQUIRED( statistics.size() >= 1 , "At least one statistic defined" ) MITK_TEST_CONDITION_REQUIRED( statistics.size() == rois.size(), "Same number of rois and corresponding statistics defined"); result.m_HotspotMin.set_size(statistics.size()); result.m_HotspotMax.set_size(statistics.size()); result.m_HotspotMean.set_size(statistics.size()); result.m_HotspotMinIndexX.set_size(statistics.size()); result.m_HotspotMinIndexY.set_size(statistics.size()); result.m_HotspotMinIndexZ.set_size(statistics.size()); result.m_HotspotMaxIndexX.set_size(statistics.size()); result.m_HotspotMaxIndexY.set_size(statistics.size()); result.m_HotspotMaxIndexZ.set_size(statistics.size()); result.m_HotspotIndexX.set_size(statistics.size()); result.m_HotspotIndexY.set_size(statistics.size()); result.m_HotspotIndexZ.set_size(statistics.size()); for(unsigned int i = 0; i < statistics.size(); ++i) { result.m_HotspotMin[i] = GetDoubleAttribute(statistics[i], "minimum"); result.m_HotspotMax[i] = GetDoubleAttribute(statistics[i], "maximum"); result.m_HotspotMean[i] = GetDoubleAttribute(statistics[i], "mean"); result.m_HotspotMinIndexX[i] = GetIntegerAttribute(statistics[i], "minimumIndexX"); result.m_HotspotMinIndexY[i] = GetIntegerAttribute(statistics[i], "minimumIndexY"); result.m_HotspotMinIndexZ[i] = GetIntegerAttribute(statistics[i], "minimumIndexZ"); result.m_HotspotMaxIndexX[i] = GetIntegerAttribute(statistics[i], "maximumIndexX"); result.m_HotspotMaxIndexY[i] = GetIntegerAttribute(statistics[i], "maximumIndexY"); result.m_HotspotMaxIndexZ[i] = GetIntegerAttribute(statistics[i], "maximumIndexZ"); result.m_HotspotIndexX[i] = GetIntegerAttribute(statistics[i], "hotspotIndexX"); result.m_HotspotIndexY[i] = GetIntegerAttribute(statistics[i], "hotspotIndexY"); result.m_HotspotIndexZ[i] = GetIntegerAttribute(statistics[i], "hotspotIndexZ"); } } catch (std::exception& e) { MITK_TEST_CONDITION_REQUIRED(false, "Reading test parameters from XML file. Error message: " << e.what()); } return result; } /** \brief Generate an image that contains a couple of 3D gaussian distributions. Uses the given parameters to produce a test image using class MultiGaussianImageSource. */ static mitk::Image::Pointer BuildTestImage(const Parameters& testParameters) { mitk::Image::Pointer result; typedef double PixelType; const int Dimension = 3; typedef itk::Image ImageType; ImageType::Pointer image = ImageType::New(); typedef itk::MultiGaussianImageSource< ImageType > MultiGaussianImageSource; MultiGaussianImageSource::Pointer gaussianGenerator = MultiGaussianImageSource::New(); ImageType::SizeValueType size[3]; size[0] = testParameters.m_ImageColumns; size[1] = testParameters.m_ImageRows; size[2] = testParameters.m_ImageSlices; itk::MultiGaussianImageSource::VectorType centerXVec, centerYVec, centerZVec, sigmaXVec, sigmaYVec, sigmaZVec, altitudeVec; for(int i = 0; i < testParameters.m_NumberOfGaussian; ++i) { centerXVec.push_back(testParameters.m_CenterX[i]); centerYVec.push_back(testParameters.m_CenterY[i]); centerZVec.push_back(testParameters.m_CenterZ[i]); sigmaXVec.push_back(testParameters.m_SigmaX[i]); sigmaYVec.push_back(testParameters.m_SigmaY[i]); sigmaZVec.push_back(testParameters.m_SigmaZ[i]); altitudeVec.push_back(testParameters.m_Altitude[i]); } ImageType::SpacingType spacing; for( int i = 0; i < Dimension; ++i ) spacing[i] = testParameters.m_Spacing[i]; gaussianGenerator->SetSize( size ); gaussianGenerator->SetSpacing( spacing ); gaussianGenerator->SetRadius(testParameters.m_HotspotRadiusInMM); gaussianGenerator->SetNumberOfGausssians(testParameters.m_NumberOfGaussian); gaussianGenerator->AddGaussian(centerXVec, centerYVec, centerZVec, sigmaXVec, sigmaYVec, sigmaZVec, altitudeVec); gaussianGenerator->Update(); image = gaussianGenerator->GetOutput(); mitk::CastToMitkImage(image, result); return result; } /** \brief Calculates hotspot statistics for given test image and ROI parameters. Uses ImageStatisticsCalculator to find a hotspot in a defined ROI within the given image. */ - static mitk::ImageStatisticsCalculator::Statistics CalculateStatistics(mitk::Image* image, const Parameters& testParameters, unsigned int label) + static mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer CalculateStatistics(mitk::Image* image, const Parameters& testParameters, unsigned int label) { - mitk::ImageStatisticsCalculator::Statistics result; + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result; const unsigned int Dimension = 3; typedef itk::Image MaskImageType; MaskImageType::Pointer mask = MaskImageType::New(); MaskImageType::SizeType size; MaskImageType::SpacingType spacing; MaskImageType::IndexType start; mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); - statisticsCalculator->SetImage(image); + statisticsCalculator->SetInputImage(image); mitk::Image::Pointer mitkMaskImage; if((testParameters.m_MaxIndexX[label] > testParameters.m_MinIndexX[label] && testParameters.m_MinIndexX[label] >= 0) && (testParameters.m_MaxIndexY[label] > testParameters.m_MinIndexY[label] && testParameters.m_MinIndexY[label] >= 0) && (testParameters.m_MaxIndexZ[label] > testParameters.m_MinIndexZ[label] && testParameters.m_MinIndexZ[label] >= 0)) { for(unsigned int i = 0; i < Dimension; ++i) { start[i] = 0; spacing[i] = testParameters.m_Spacing[i]; } size[0] = testParameters.m_ImageColumns; size[1] = testParameters.m_ImageRows; size[2] = testParameters.m_ImageSlices; MaskImageType::RegionType region; region.SetIndex(start); region.SetSize(size); mask->SetSpacing(spacing); mask->SetRegions(region); mask->Allocate(); typedef itk::ImageRegionIteratorWithIndex MaskImageIteratorType; MaskImageIteratorType maskIt(mask, region); for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { maskIt.Set(0); } for(unsigned int i = 0; i < testParameters.m_NumberOfLabels; ++i) { for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { MaskImageType::IndexType index = maskIt.GetIndex(); if((index[0] >= testParameters.m_MinIndexX[i] && index[0] <= testParameters.m_MaxIndexX[i] ) && (index[1] >= testParameters.m_MinIndexY[i] && index[1] <= testParameters.m_MaxIndexY[i] ) && (index[2] >= testParameters.m_MinIndexZ[i] && index[2] <= testParameters.m_MaxIndexZ[i] )) { maskIt.Set(testParameters.m_Label[i]); } } } - MITK_DEBUG << "Masking mode has set to image"; mitk::CastToMitkImage(mask, mitkMaskImage); - statisticsCalculator->SetImageMask(mitkMaskImage); - statisticsCalculator->SetMaskingModeToImage(); - } - else - { - MITK_DEBUG << "Masking mode has set to none"; - statisticsCalculator->SetMaskingModeToNone(); - } - - statisticsCalculator->SetHotspotRadiusInMM(testParameters.m_HotspotRadiusInMM); - statisticsCalculator->SetCalculateHotspot(true); + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(mitkMaskImage); + + mitk::HotspotMaskGenerator::Pointer hotspotMaskGen = mitk::HotspotMaskGenerator::New(); + hotspotMaskGen->SetInputImage(image); + hotspotMaskGen->SetMask(imgMaskGen.GetPointer()); + hotspotMaskGen->SetHotspotRadiusInMM(testParameters.m_HotspotRadiusInMM); + if(testParameters.m_EntireHotspotInImage == 1) + { + MITK_INFO << "Hotspot must be completly inside image"; + hotspotMaskGen->SetHotspotMustBeCompletelyInsideImage(true); + } + else + { + MITK_INFO << "Hotspot must not be completly inside image"; + hotspotMaskGen->SetHotspotMustBeCompletelyInsideImage(false); + } - if(testParameters.m_EntireHotspotInImage == 1) - { - MITK_INFO << "Hotspot must be completly inside image"; - statisticsCalculator->SetHotspotMustBeCompletlyInsideImage(true); + statisticsCalculator->SetMask(hotspotMaskGen.GetPointer()); + MITK_DEBUG << "Masking is set to hotspot+image mask"; } else { - MITK_INFO << "Hotspot must not be completly inside image"; - statisticsCalculator->SetHotspotMustBeCompletlyInsideImage(false); + mitk::HotspotMaskGenerator::Pointer hotspotMaskGen = mitk::HotspotMaskGenerator::New(); + hotspotMaskGen->SetInputImage(image); + hotspotMaskGen->SetHotspotRadiusInMM(testParameters.m_HotspotRadiusInMM); + if(testParameters.m_EntireHotspotInImage == 1) + { + MITK_INFO << "Hotspot must be completly inside image"; + hotspotMaskGen->SetHotspotMustBeCompletelyInsideImage(true); + } + else + { + MITK_INFO << "Hotspot must not be completly inside image"; + hotspotMaskGen->SetHotspotMustBeCompletelyInsideImage(false); + } + MITK_DEBUG << "Masking is set to hotspot only"; } - statisticsCalculator->ComputeStatistics(); result = statisticsCalculator->GetStatistics(0, label); return result; } static void ValidateStatisticsItem(const std::string& label, double testvalue, double reference, double tolerance) { double diff = ::fabs(reference - testvalue); MITK_TEST_CONDITION( diff < tolerance, "'" << label << "' value close enough to reference value " "(value=" << testvalue << ", reference=" << reference << ", diff=" << diff << ")" ); } static void ValidateStatisticsItem(const std::string& label, const vnl_vector& testvalue, const vnl_vector& reference) { double diffX = ::fabs(double(testvalue[0] - reference[0])); double diffY = ::fabs(double(testvalue[1] - reference[1])); double diffZ = ::fabs(double(testvalue[2] - reference[2])); std::stringstream testPosition; testPosition << testvalue[0] << "," << testvalue[1] << "," << testvalue[2]; std::stringstream referencePosition; referencePosition << reference[0] << "," << reference[1] << "," << reference[2]; MITK_TEST_CONDITION( diffX < mitk::eps && diffY < mitk::eps && diffZ < mitk::eps, "'" << label << "' close enough to reference value " << "(value=[" << testPosition.str() << "]," << " reference=[" << referencePosition.str() << "]"); } /** \brief Compares calculated against actual statistics values. Checks validness of all statistics aspects. Lets test fail if any aspect is not sufficiently equal. */ - static void ValidateStatistics(const mitk::ImageStatisticsCalculator::Statistics& statistics, const Parameters& testParameters, unsigned int label) + static void ValidateStatistics(const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer hotspotStatistics, const Parameters& testParameters, unsigned int label) { // check all expected test result against actual results double eps = 0.25; // value above the largest tested difference - ValidateStatisticsItem("Hotspot mean", statistics.GetHotspotStatistics().GetMean(), testParameters.m_HotspotMean[label], eps); - ValidateStatisticsItem("Hotspot maximum", statistics.GetHotspotStatistics().GetMax(), testParameters.m_HotspotMax[label], eps); - ValidateStatisticsItem("Hotspot minimum", statistics.GetHotspotStatistics().GetMin(), testParameters.m_HotspotMin[label], eps); + ValidateStatisticsItem("Hotspot mean", hotspotStatistics->GetMean(), testParameters.m_HotspotMean[label], eps); + ValidateStatisticsItem("Hotspot maximum", hotspotStatistics->GetMax(), testParameters.m_HotspotMax[label], eps); + ValidateStatisticsItem("Hotspot minimum", hotspotStatistics->GetMin(), testParameters.m_HotspotMin[label], eps); vnl_vector referenceHotspotCenterIndex; referenceHotspotCenterIndex.set_size(3); referenceHotspotCenterIndex[0] = testParameters.m_HotspotIndexX[label]; referenceHotspotCenterIndex[1] = testParameters.m_HotspotIndexY[label]; referenceHotspotCenterIndex[2] = testParameters.m_HotspotIndexZ[label]; - ValidateStatisticsItem("Hotspot center position", statistics.GetHotspotStatistics().GetHotspotIndex(), referenceHotspotCenterIndex); + // ValidateStatisticsItem("Hotspot center position", statistics.GetHotspotStatistics().GetHotspotIndex(), referenceHotspotCenterIndex); TODO: new image statistics calculator does not give hotspot position // TODO we do not test minimum/maximum positions within the peak/hotspot region, because // these positions are not unique, i.e. there are multiple valid minima/maxima positions. // One solution would be to modify the test cases in order to achive clear positions. // The BETTER/CORRECT solution would be to change the singular position into a set of positions / a region } }; /** \brief Verifies that hotspot statistics part of ImageStatisticsCalculator. The test reads parameters from an XML-file to generate a test-image, calculates the hotspot statistics of the image and checks if the calculated statistics are the same as the specified values of the XML-file. */ int mitkImageStatisticsHotspotTest(int argc, char* argv[]) { MITK_TEST_BEGIN("mitkImageStatisticsHotspotTest") try { mitkImageStatisticsHotspotTestClass::Parameters parameters = mitkImageStatisticsHotspotTestClass::ParseParameters(argc,argv); mitk::Image::Pointer image = mitkImageStatisticsHotspotTestClass::BuildTestImage(parameters); MITK_TEST_CONDITION_REQUIRED( image.IsNotNull(), "Generate test image" ); for(unsigned int label = 0; label < parameters.m_NumberOfLabels; ++label) { - mitk::ImageStatisticsCalculator::Statistics statistics = mitkImageStatisticsHotspotTestClass::CalculateStatistics(image, parameters, label); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer statistics = mitkImageStatisticsHotspotTestClass::CalculateStatistics(image, parameters, label); mitkImageStatisticsHotspotTestClass::ValidateStatistics(statistics, parameters, label); std::cout << std::endl; } } catch (std::exception& e) { std::cout << "Error: " << e.what() << std::endl; MITK_TEST_CONDITION_REQUIRED( false, "Exception occurred during test execution: " << e.what() ); } catch(...) { MITK_TEST_CONDITION_REQUIRED( false, "Exception occurred during test execution." ); } MITK_TEST_END() } diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsTextureAnalysisTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsTextureAnalysisTest.cpp index 1907147299..d5d888d586 100644 --- a/Modules/ImageStatistics/Testing/mitkImageStatisticsTextureAnalysisTest.cpp +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsTextureAnalysisTest.cpp @@ -1,235 +1,235 @@ /*=================================================================== 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 "mitkTestingMacros.h" #include "itkImage.h" #include "mitkExtendedLabelStatisticsImageFilter.h" #include "mitkExtendedStatisticsImageFilter.h" #include "mitkNumericConstants.h" /** \section Testing of Skewness and Kurtosis * This test class is for testing the added coefficients Skewness and Kurtosis * for the mitkExtendedLabelStatisticsImageFilter (Masked Images) and for the * mitkExtendedStatisticsImageFilter (Unmasked Images). Both filter will be tested * against two pictures. */ class mitkImageStatisticsTextureAnalysisTestClass { /** * \brief Explanation of the mitkImageStatisticsTextureAnalysisTestClass test class * * this test class produce test images and masking images with the method CreatingTestImageForDifferentLabelSize. * TestInstanceFortheMaskedStatisticsFilter and TestInstanceFortheUnmaskedStatisticsFilter are the two Instances * for the filters of masking and unmasking images. * TestofSkewnessAndKurtosisForMaskedImagesand TestofSkewnessAndKurtosisForUnmaskedImages are the correlated test * for the checking the values. */ public: typedef itk::Image< int,3 >ImageType; typedef ImageType::Pointer PointerOfImage; typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, ImageType > LabelStatisticsFilterType; typedef LabelStatisticsFilterType::Pointer labelStatisticsFilterPointer; typedef itk::ExtendedStatisticsImageFilter< ImageType > StatisticsFilterType; typedef StatisticsFilterType::Pointer StatisticsFilterPointer; ImageType::Pointer CreatingTestImageForDifferentLabelSize( int factorOfDividingThePicture, int bufferValue, int labelValue) { ImageType::Pointer image = ImageType :: New(); ImageType::IndexType start; ImageType::SizeType size; start[0] = 0; start[1] = 0; start[2] = 0; size[0] = 100; size[1] = 100; size[2] = 100; ImageType:: RegionType region; region.SetSize( size ); region.SetIndex( start ); image->SetRegions(region); image->Allocate(); image->FillBuffer(bufferValue); int i = 0; for(unsigned int r = 0; r < 50; r++) { for(unsigned int c = 0; c < factorOfDividingThePicture; c++) { for(unsigned int l = 0; l < 100; l++) { ImageType::IndexType pixelIndex; pixelIndex[0] = r; pixelIndex[1] = c; pixelIndex[2] = l; image->SetPixel(pixelIndex, labelValue); } } } return image; } LabelStatisticsFilterType::Pointer TestInstanceFortheMaskedStatisticsFilter(ImageType::Pointer image, ImageType::Pointer maskImage) { LabelStatisticsFilterType::Pointer labelStatisticsFilter; labelStatisticsFilter = LabelStatisticsFilterType::New(); labelStatisticsFilter->SetInput( image ); labelStatisticsFilter->UseHistogramsOn(); labelStatisticsFilter->SetHistogramParameters( 20, -10, 10); labelStatisticsFilter->SetLabelInput( maskImage ); labelStatisticsFilter->Update(); return labelStatisticsFilter; } StatisticsFilterType::Pointer TestInstanceFortheUnmaskedStatisticsFilter(ImageType::Pointer image ) { StatisticsFilterType::Pointer StatisticsFilter; StatisticsFilter = StatisticsFilterType::New(); StatisticsFilter->SetInput( image ); - StatisticsFilter->SetBinSize( 20 ); + StatisticsFilter->SetHistogramParameters( 20, -10, 10 ); StatisticsFilter->Update(); return StatisticsFilter; } //test for Skewness,Kurtosis and MPP for masked Images void TestofSkewnessKurtosisAndMPPForMaskedImages(LabelStatisticsFilterType::Pointer labelStatisticsFilter, double expectedSkewness, double expectedKurtosis, double expectedMPP) { // let's create an object of our class bool isSkewsnessLowerlimitCorrect = labelStatisticsFilter->GetSkewness( 1 )- expectedKurtosis+ std::pow(10,-3) <= expectedSkewness; bool isSkewsnessUpperlimitCorrect = labelStatisticsFilter->GetSkewness( 1 )+ expectedKurtosis+ std::pow(10,-3) >= expectedSkewness; MITK_TEST_CONDITION( isSkewsnessLowerlimitCorrect && isSkewsnessUpperlimitCorrect,"expectedSkewness: " << expectedSkewness << " actual Value: " << labelStatisticsFilter->GetSkewness( 1 ) ); bool isKurtosisUpperlimitCorrect = labelStatisticsFilter->GetKurtosis( 1 ) <= expectedKurtosis+ std::pow(10,-3); bool isKurtosisLowerlimitCorrect = expectedKurtosis- std::pow(10,-3) <= labelStatisticsFilter->GetKurtosis( 1 ); MITK_TEST_CONDITION( isKurtosisUpperlimitCorrect && isKurtosisLowerlimitCorrect,"expectedKurtosis: " << expectedKurtosis << " actual Value: " << labelStatisticsFilter->GetKurtosis( 1 ) ); MITK_TEST_CONDITION( ( expectedMPP - labelStatisticsFilter->GetMPP( 1 ) ) < 1, "expected MPP: " << expectedMPP << " actual Value: " << labelStatisticsFilter->GetMPP( 1 ) ); } //test for Entropy,Uniformity and UPP for masked Images void TestofEntropyUniformityAndUppForMaskedImages(LabelStatisticsFilterType::Pointer labelStatisticsFilter, double expectedEntropy, double expectedUniformity, double expectedUPP) { bool calculatedEntropyLowerLimit = labelStatisticsFilter->GetEntropy( 1 ) >= expectedEntropy - std::pow(10,-3); bool calculatedUniformityLowerLimit = labelStatisticsFilter->GetUniformity( 1 ) >= expectedUniformity - std::pow(10,-3); bool calculatedUppLowerLimit = labelStatisticsFilter->GetUPP( 1 ) >= expectedUPP - std::pow(10,-3); bool calculatedEntropyUpperLimit = labelStatisticsFilter->GetEntropy( 1 ) <= expectedEntropy + std::pow(10,-3); bool calculatedUniformityUpperLimit = labelStatisticsFilter->GetUniformity( 1 ) <= expectedUniformity + std::pow(10,-3); bool calculatedUppUpperLimit = labelStatisticsFilter->GetUPP( 1 ) <= expectedUPP + std::pow(10,-3); MITK_TEST_CONDITION( calculatedEntropyLowerLimit && calculatedEntropyUpperLimit, "expected Entropy: " << expectedEntropy << " actual Value: " << labelStatisticsFilter->GetEntropy( 1 ) ); MITK_TEST_CONDITION( calculatedUniformityLowerLimit && calculatedUniformityUpperLimit, "expected Uniformity: " << expectedUniformity << " actual Value: " << labelStatisticsFilter->GetUniformity( 1 ) ); MITK_TEST_CONDITION( calculatedUppLowerLimit && calculatedUppUpperLimit, "expected UPP: " << expectedUPP << " actual Value: " << labelStatisticsFilter->GetUPP( 1 ) ); } //test for Skewness,Kurtosis and MPP for unmasked Images void TestofSkewnessKurtosisAndMPPForUnmaskedImages(StatisticsFilterType::Pointer StatisticsFilter, double expectedSkewness, double expectedKurtosis, double expectedMPP) { // let's create an object of our class bool isSkewsnessLowerlimitCorrect = StatisticsFilter->GetSkewness()- expectedKurtosis+ std::pow(10,-3) <= expectedSkewness; bool isSkewsnessUpperlimitCorrect = StatisticsFilter->GetSkewness()+ expectedKurtosis+ std::pow(10,-3) >= expectedSkewness; MITK_TEST_CONDITION( isSkewsnessLowerlimitCorrect && isSkewsnessUpperlimitCorrect,"expectedSkewness: " << expectedSkewness << " actual Value: " << StatisticsFilter->GetSkewness() ); bool isKurtosisUpperlimitCorrect = StatisticsFilter->GetKurtosis() <= expectedKurtosis+ std::pow(10,-3); bool isKurtosisLowerlimitCorrect = expectedKurtosis- std::pow(10,-3) <= StatisticsFilter->GetKurtosis(); MITK_TEST_CONDITION( isKurtosisUpperlimitCorrect && isKurtosisLowerlimitCorrect,"expectedKurtosis: " << expectedKurtosis << " actual Value: " << StatisticsFilter->GetKurtosis() ); MITK_TEST_CONDITION( ( expectedMPP - StatisticsFilter->GetMPP() ) < mitk::eps, "expected MPP: " << expectedMPP << " actual Value: " << StatisticsFilter->GetMPP() ); } //test for Entropy,Uniformity and UPP for unmasked Images void TestofEntropyUniformityAndUppForUnmaskedImages(StatisticsFilterType::Pointer StatisticsFilter, double expectedEntropy, double expectedUniformity, double expectedUPP) { bool calculatedEntropyLowerLimit = StatisticsFilter->GetEntropy() >= expectedEntropy - std::pow(10,-3); bool calculatedUniformityLowerLimit = StatisticsFilter->GetUniformity() >= expectedUniformity - std::pow(10,-3); bool calculatedUppLowerLimit = StatisticsFilter->GetUPP() >= expectedUPP - std::pow(10,-3); bool calculatedEntropyUpperLimit = StatisticsFilter->GetEntropy() <= expectedEntropy + std::pow(10,-3); bool calculatedUniformityUpperLimit = StatisticsFilter->GetUniformity() <= expectedUniformity + std::pow(10,-3); bool calculatedUppUpperLimit = StatisticsFilter->GetUPP() <= expectedUPP + std::pow(10,-3); MITK_TEST_CONDITION( calculatedEntropyLowerLimit && calculatedEntropyUpperLimit, "expected Entropy: " << expectedEntropy << " actual Value: " << StatisticsFilter->GetEntropy() ); MITK_TEST_CONDITION( calculatedUniformityLowerLimit && calculatedUniformityUpperLimit, "expected Uniformity: " << expectedUniformity << " actual Value: " << StatisticsFilter->GetUniformity() ); MITK_TEST_CONDITION( calculatedUppLowerLimit && calculatedUppUpperLimit, "expected UPP: " << expectedUPP << " actual Value: " << StatisticsFilter->GetUPP() ); } }; int mitkImageStatisticsTextureAnalysisTest(int, char* []) { // always start with this! MITK_TEST_BEGIN("mitkImageStatisticsTextureAnalysisTest") mitkImageStatisticsTextureAnalysisTestClass testclassInstance; mitkImageStatisticsTextureAnalysisTestClass::PointerOfImage labelImage = testclassInstance.CreatingTestImageForDifferentLabelSize(100, 1, 1); mitkImageStatisticsTextureAnalysisTestClass::PointerOfImage image = testclassInstance.CreatingTestImageForDifferentLabelSize(100, 3, 2); mitkImageStatisticsTextureAnalysisTestClass::PointerOfImage image2 = testclassInstance.CreatingTestImageForDifferentLabelSize(50, 3, 2); //test for masked images mitkImageStatisticsTextureAnalysisTestClass::labelStatisticsFilterPointer mitkLabelFilter= testclassInstance.TestInstanceFortheMaskedStatisticsFilter( image,labelImage); testclassInstance.TestofSkewnessKurtosisAndMPPForMaskedImages(mitkLabelFilter, 0, 0.999998, 2.5); testclassInstance.TestofEntropyUniformityAndUppForMaskedImages(mitkLabelFilter, 1, 0.5, 0.5); mitkImageStatisticsTextureAnalysisTestClass::labelStatisticsFilterPointer mitkLabelFilter2= testclassInstance.TestInstanceFortheMaskedStatisticsFilter( image2,labelImage); testclassInstance.TestofSkewnessKurtosisAndMPPForMaskedImages(mitkLabelFilter2, -1.1547, 2.33333, 2.75); testclassInstance.TestofEntropyUniformityAndUppForMaskedImages(mitkLabelFilter2, 0.811278, 0.625, 0.625); //test for unmasked images mitkImageStatisticsTextureAnalysisTestClass::StatisticsFilterPointer mitkFilter= testclassInstance.TestInstanceFortheUnmaskedStatisticsFilter( image); testclassInstance.TestofSkewnessKurtosisAndMPPForUnmaskedImages(mitkFilter, 0, 0.999998, 2.5); testclassInstance.TestofEntropyUniformityAndUppForUnmaskedImages(mitkFilter, 1, 0.5, 0.5); mitkImageStatisticsTextureAnalysisTestClass::StatisticsFilterPointer mitkFilter2= testclassInstance.TestInstanceFortheUnmaskedStatisticsFilter( image2); testclassInstance.TestofSkewnessKurtosisAndMPPForUnmaskedImages(mitkFilter2, -1.1547, 2.33333, 2.75); testclassInstance.TestofEntropyUniformityAndUppForUnmaskedImages( mitkFilter2, 0.811278, 0.625, 0.625); MITK_TEST_END() } diff --git a/Modules/ImageStatistics/files.cmake b/Modules/ImageStatistics/files.cmake index 29d3c3547a..5904c1fdfb 100644 --- a/Modules/ImageStatistics/files.cmake +++ b/Modules/ImageStatistics/files.cmake @@ -1,14 +1,33 @@ set(CPP_FILES mitkImageStatisticsCalculator.cpp mitkPointSetStatisticsCalculator.cpp mitkPointSetDifferenceStatisticsCalculator.cpp mitkIntensityProfile.cpp + mitkHotspotMaskGenerator.cpp + mitkMaskGenerator.cpp + mitkPlanarFigureMaskGenerator.cpp + mitkMultiLabelMaskGenerator.cpp + mitkImageMaskGenerator.cpp + mitkHistogramStatisticsCalculator.cpp + mitkMaskUtilities.cpp + mitkIgnorePixelMaskGenerator.cpp ) set(H_FILES mitkImageStatisticsCalculator.h mitkPointSetDifferenceStatisticsCalculator.h mitkPointSetStatisticsCalculator.h mitkExtendedStatisticsImageFilter.h mitkExtendedLabelStatisticsImageFilter.h + mitkHotspotMaskGenerator.h + mitkMaskGenerator.h + mitkPlanarFigureMaskGenerator.h + mitkMultiLabelMaskGenerator.h + mitkImageMaskGenerator.h + mitkHistogramStatisticsCalculator.h + mitkMaskUtilities.h + mitkitkMaskImageFilter.h + mitkIgnorePixelMaskGenerator.h + mitkMinMaxImageFilterWithIndex.h + mitkMinMaxLabelmageFilterWithIndex.h ) diff --git a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h b/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h index ae449d14c8..789fd1d134 100644 --- a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h +++ b/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h @@ -1,130 +1,350 @@ /*=================================================================== 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 __mitkExtendedLabelStatisticsImageFilter_h -#define __mitkExtendedLabelStatisticsImageFilter_h +#ifndef __mitkExtendedLabelStatisticsImageFilter +#define __mitkExtendedLabelStatisticsImageFilter #include "itkLabelStatisticsImageFilter.h" namespace itk { /** * \class ExtendedLabelStatisticsImageFilter * \brief Extension of the itkLabelStatisticsImageFilter that also calculates the Skewness,Kurtosis,Entropy,Uniformity. * * This class inherits from the itkLabelStatisticsImageFilter and - * uses its results for the calculation of six additional coefficients: - * the Skewness,Kurtosis,Uniformity,UPP,MPP,Entropy + * uses its results for the calculation of seven additional coefficients: + * the Skewness, Kurtosis, Uniformity, UPP, MPP, Entropy and Median * - * As these coefficient are based on the mean and the sigma which are both calculated - * by the LabelStatisticsImageFilter, the method AfterThreadedGenerateData() is overwritten - * and calls ComputeSkewnessKurtosisAndMPP() and ComputeEntropyUniformityAndUPP after the AfterThreadedGenerateData() - * while the second coefficient Method is only called when the the method useHistogram is on!!! - * implementation of the superclass is called. * */ template< class TInputImage, class TLabelImage > class ExtendedLabelStatisticsImageFilter : public LabelStatisticsImageFilter< TInputImage, TLabelImage > { public: typedef ExtendedLabelStatisticsImageFilter Self; typedef LabelStatisticsImageFilter < TInputImage, TLabelImage > Superclass; typedef SmartPointer< Self > Pointer; typedef SmartPointer< const Self > ConstPointer; typedef typename Superclass::LabelPixelType LabelPixelType; typedef typename Superclass::RealType RealType; typedef typename Superclass::PixelType PixelType; typedef typename Superclass::MapIterator MapIterator; + typedef typename Superclass::BoundingBoxType BoundingBoxType; + typedef typename Superclass::RegionType RegionType; typedef itk::Statistics::Histogram HistogramType; itkFactorylessNewMacro( Self ); itkCloneMacro( Self ); itkTypeMacro(ExtendedLabelStatisticsImageFilter, LabelStatisticsImageFilter); - /** - * \brief Internal class which stores the calculated coefficients Skewness,Kurtosis,Uniformity,UPP,MPP,Entropy. - */ - class CoefficientsClass + + /** \class LabelStatistics + * \brief Statistics stored per label + * \ingroup ITKImageStatistics + */ + class LabelStatistics { - public: + public: + + // default constructor + LabelStatistics() + { + // initialized to the default values + m_Count = NumericTraits< IdentifierType >::ZeroValue(); + m_PositivePixelCount = NumericTraits< IdentifierType >::ZeroValue(); + m_Sum = NumericTraits< RealType >::ZeroValue(); + m_SumOfPositivePixels = NumericTraits< RealType >::ZeroValue(); + + m_SumOfSquares = NumericTraits< RealType >::ZeroValue(); + m_SumOfCubes = NumericTraits< RealType >::ZeroValue(); + m_SumOfQuadruples = NumericTraits< RealType >::ZeroValue(); + + // Set such that the first pixel encountered can be compared + m_Minimum = NumericTraits< RealType >::max(); + m_Maximum = NumericTraits< RealType >::NonpositiveMin(); + + // Default these to zero + m_Mean = NumericTraits< RealType >::ZeroValue(); + m_Sigma = NumericTraits< RealType >::ZeroValue(); + m_Variance = NumericTraits< RealType >::ZeroValue(); + m_MPP = NumericTraits< RealType >::ZeroValue(); + m_Median = NumericTraits< RealType >::ZeroValue(); + m_Uniformity = NumericTraits< RealType >::ZeroValue(); + m_UPP = NumericTraits< RealType >::ZeroValue(); + m_Entropy = NumericTraits< RealType >::ZeroValue(); + m_Skewness = NumericTraits< RealType >::ZeroValue(); + m_Kurtosis = NumericTraits< RealType >::ZeroValue(); + + unsigned int imageDimension = itkGetStaticConstMacro(ImageDimension); + m_BoundingBox.resize(imageDimension * 2); + for ( unsigned int i = 0; i < imageDimension * 2; i += 2 ) + { + m_BoundingBox[i] = NumericTraits< IndexValueType >::max(); + m_BoundingBox[i + 1] = NumericTraits< IndexValueType >::NonpositiveMin(); + } + m_Histogram = ITK_NULLPTR; + } + + // constructor with histogram enabled + LabelStatistics(int size, RealType lowerBound, RealType upperBound) + { + // initialized to the default values + m_Count = NumericTraits< IdentifierType >::ZeroValue(); + m_PositivePixelCount = NumericTraits< IdentifierType >::ZeroValue(); + m_Sum = NumericTraits< RealType >::ZeroValue(); + m_SumOfPositivePixels = NumericTraits< RealType >::ZeroValue(); + + m_SumOfSquares = NumericTraits< RealType >::ZeroValue(); + m_SumOfCubes = NumericTraits< RealType >::ZeroValue(); + m_SumOfQuadruples = NumericTraits< RealType >::ZeroValue(); - CoefficientsClass() + // Set such that the first pixel encountered can be compared + m_Minimum = NumericTraits< RealType >::max(); + m_Maximum = NumericTraits< RealType >::NonpositiveMin(); + + // Default these to zero + m_Mean = NumericTraits< RealType >::ZeroValue(); + m_Sigma = NumericTraits< RealType >::ZeroValue(); + m_Variance = NumericTraits< RealType >::ZeroValue(); + m_MPP = NumericTraits< RealType >::ZeroValue(); + m_Median = NumericTraits< RealType >::ZeroValue(); + m_Uniformity = NumericTraits< RealType >::ZeroValue(); + m_UPP = NumericTraits< RealType >::ZeroValue(); + m_Entropy = NumericTraits< RealType >::ZeroValue(); + m_Skewness = NumericTraits< RealType >::ZeroValue(); + m_Kurtosis = NumericTraits< RealType >::ZeroValue(); + + + unsigned int imageDimension = itkGetStaticConstMacro(ImageDimension); + m_BoundingBox.resize(imageDimension * 2); + for ( unsigned int i = 0; i < imageDimension * 2; i += 2 ) + { + m_BoundingBox[i] = NumericTraits< IndexValueType >::max(); + m_BoundingBox[i + 1] = NumericTraits< IndexValueType >::NonpositiveMin(); + } + + // Histogram + m_Histogram = HistogramType::New(); + typename HistogramType::SizeType hsize; + typename HistogramType::MeasurementVectorType lb; + typename HistogramType::MeasurementVectorType ub; + hsize.SetSize(1); + lb.SetSize(1); + ub.SetSize(1); + m_Histogram->SetMeasurementVectorSize(1); + hsize[0] = size; + lb[0] = lowerBound; + ub[0] = upperBound; + m_Histogram->Initialize(hsize, lb, ub); + } + + // need copy constructor because of smart pointer to histogram + LabelStatistics(const LabelStatistics & l) { - m_Kurtosis = 0.0; - m_Skewness = 0.0; - m_Entropy = -1.0; - m_Uniformity = 0.0; - m_MPP = 0.0; - m_UPP = 0.0; - }; - - ~CoefficientsClass(){}; - - /* the new member coefficients*/ - RealType m_Kurtosis; - RealType m_Skewness; - RealType m_Entropy; - RealType m_Uniformity; - RealType m_MPP; - RealType m_UPP; + m_Count = l.m_Count; + m_Minimum = l.m_Minimum; + m_Maximum = l.m_Maximum; + m_Mean = l.m_Mean; + m_Sum = l.m_Sum; + m_SumOfSquares = l.m_SumOfSquares; + m_Sigma = l.m_Sigma; + m_Variance = l.m_Variance; + m_MPP = l.m_MPP; + m_Median = l.m_Median; + m_Uniformity = l.m_Uniformity; + m_UPP = l.m_UPP; + m_Entropy = l.m_Entropy; + m_Skewness = l.m_Skewness; + m_Kurtosis = l.m_Kurtosis; + m_BoundingBox = l.m_BoundingBox; + m_Histogram = l.m_Histogram; + m_SumOfPositivePixels = l.m_SumOfPositivePixels; + m_PositivePixelCount = l.m_PositivePixelCount; + m_SumOfCubes = l.m_SumOfCubes; + m_SumOfQuadruples = l.m_SumOfQuadruples; + } + + // added for completeness + LabelStatistics &operator= (const LabelStatistics& l) + { + if(this != &l) + { + m_Count = l.m_Count; + m_Minimum = l.m_Minimum; + m_Maximum = l.m_Maximum; + m_Mean = l.m_Mean; + m_Sum = l.m_Sum; + m_SumOfSquares = l.m_SumOfSquares; + m_Sigma = l.m_Sigma; + m_Variance = l.m_Variance; + m_MPP = l.m_MPP; + m_Median = l.m_Median; + m_Uniformity = l.m_Uniformity; + m_UPP = l.m_UPP; + m_Entropy = l.m_Entropy; + m_Skewness = l.m_Skewness; + m_Kurtosis = l.m_Kurtosis; + m_BoundingBox = l.m_BoundingBox; + m_Histogram = l.m_Histogram; + m_SumOfPositivePixels = l.m_SumOfPositivePixels; + m_PositivePixelCount = l.m_PositivePixelCount; + m_SumOfCubes = l.m_SumOfCubes; + m_SumOfQuadruples = l.m_SumOfQuadruples; + } + return *this; + } + + IdentifierType m_Count; + RealType m_Minimum; + RealType m_Maximum; + RealType m_Mean; + RealType m_Sum; + RealType m_SumOfSquares; + RealType m_Sigma; + RealType m_Variance; + RealType m_MPP; + RealType m_Median; + RealType m_Uniformity; + RealType m_UPP; + RealType m_Entropy; + RealType m_Skewness; + RealType m_Kurtosis; + IdentifierType m_PositivePixelCount; + RealType m_SumOfPositivePixels; + RealType m_SumOfCubes; + RealType m_SumOfQuadruples; + typename Superclass::BoundingBoxType m_BoundingBox; + typename HistogramType::Pointer m_Histogram; }; + /** Type of the map used to store data per label */ + typedef itksys::hash_map< LabelPixelType, LabelStatistics > MapType; + typedef typename itksys::hash_map< LabelPixelType, LabelStatistics >::const_iterator StatisticsMapConstIterator; + typedef typename itksys::hash_map< LabelPixelType, LabelStatistics >::iterator StatisticsMapIterator; + typedef IdentifierType MapSizeType; + + /** Type of the container used to store valid label values */ + typedef std::vector ValidLabelValuesContainerType; + + /** Return the computed Minimum for a label. */ + RealType GetMinimum(LabelPixelType label) const; + + /** Return the computed Maximum for a label. */ + RealType GetMaximum(LabelPixelType label) const; - /*getter method for the new coefficients*/ + /** Return the computed Mean for a label. */ + RealType GetMean(LabelPixelType label) const; + + /** Return the computed Standard Deviation for a label. */ + RealType GetSigma(LabelPixelType label) const; + + /** Return the computed Variance for a label. */ + RealType GetVariance(LabelPixelType label) const; + + /** Return the computed bounding box for a label. */ + BoundingBoxType GetBoundingBox(LabelPixelType label) const; + + /** Return the computed region. */ + RegionType GetRegion(LabelPixelType label) const; + + /** Return the compute Sum for a label. */ + RealType GetSum(LabelPixelType label) const; + + /** Return the number of pixels for a label. */ + MapSizeType GetCount(LabelPixelType label) const; + + /** Return the histogram for a label */ + HistogramType::Pointer GetHistogram(LabelPixelType label) const; + + /*getter method for the new statistics*/ RealType GetSkewness(LabelPixelType label) const; RealType GetKurtosis(LabelPixelType label) const; RealType GetUniformity( LabelPixelType label) const; + RealType GetMedian( LabelPixelType label) const; RealType GetEntropy( LabelPixelType label) const; RealType GetMPP( LabelPixelType label) const; RealType GetUPP( LabelPixelType label) const; - std::list< int> GetRelevantLabels() const; bool GetMaskingNonEmpty() const; - protected: + std::list GetRelevantLabels() const; - typedef std::map< LabelPixelType, CoefficientsClass > CoefficientsMap; - typedef typename CoefficientsMap::const_iterator CoefficientsMapConstIterator; - ExtendedLabelStatisticsImageFilter(); + /** specify global Histogram parameters. If the histogram parameters are set with this function, the same min and max value are used for all histograms. */ + void SetHistogramParameters(const int numBins, RealType lowerBound, + RealType upperBound); - virtual ~ExtendedLabelStatisticsImageFilter(){}; + /** specify Histogram parameters for each label individually. Labels in the label image that are not represented in the std::maps here will receive global parameters (if available) */ + void SetHistogramParametersForLabels(std::map numBins, std::map lowerBound, + std::map upperBound); + + protected: + ExtendedLabelStatisticsImageFilter(): + m_GlobalHistogramParametersSet(false), + m_LabelHistogramParametersSet(false), + m_PreferGlobalHistogramParameters(false) + { + m_NumBins.set_size(1); + } + + virtual ~ExtendedLabelStatisticsImageFilter(){} - /** - * \brief ComputeSkewnessKurtosisAndMPP(),ComputeEntropyUniformityAndUPP() will be called after superclass - * both methods are seprated because one is build up on the pixel values and one is build up a step after on a - * histogram - */ - void ComputeSkewnessKurtosisAndMPP(); void AfterThreadedGenerateData(); - void ComputeEntropyUniformityAndUPP(); - void CalculateSettingsForLabels(); + + /** Initialize some accumulators before the threads run. */ + void BeforeThreadedGenerateData(); + + /** Multi-thread version GenerateData. */ + void ThreadedGenerateData(const typename TInputImage::RegionType & + outputRegionForThread, + ThreadIdType threadId); + + /** Does the specified label exist? Can only be called after a call + * a call to Update(). */ + bool HasLabel(LabelPixelType label) const + { + return m_LabelStatistics.find(label) != m_LabelStatistics.end(); + } private: + std::vector< MapType > m_LabelStatisticsPerThread; + MapType m_LabelStatistics; + ValidLabelValuesContainerType m_ValidLabelValues; + + bool m_GlobalHistogramParametersSet; + + typename HistogramType::SizeType m_NumBins; + + RealType m_LowerBound; + RealType m_UpperBound; - CoefficientsMap m_LabelStatisticsCoefficients; - std::list< int> m_RelevantLabels; bool m_MaskNonEmpty; + bool m_LabelHistogramParametersSet; + std::map m_LabelMin, m_LabelMax; + std::map m_LabelNBins; + bool m_PreferGlobalHistogramParameters; + }; // end of class } // end namespace itk #ifndef ITK_MANUAL_INSTANTIATION #include "mitkExtendedLabelStatisticsImageFilter.hxx" #endif #endif diff --git a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx b/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx index 42865e425c..57e4dda221 100644 --- a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx +++ b/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx @@ -1,324 +1,765 @@ /*=================================================================== 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 _mitkExtendedLabelStatisticsImageFilter_hxx #define _mitkExtendedLabelStatisticsImageFilter_hxx #include "mitkExtendedLabelStatisticsImageFilter.h" #include "itkImageRegionConstIteratorWithIndex.h" #include "itkImageRegionConstIterator.h" #include #include #include "mitkNumericConstants.h" #include "mitkLogMacros.h" +#include namespace itk { - template< class TInputImage , class TLabelImage> - ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > - ::ExtendedLabelStatisticsImageFilter() - : LabelStatisticsImageFilter< TInputImage, TLabelImage >() + template< class TInputImage, class TLabelImage > + bool + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetMaskingNonEmpty() const { - CalculateSettingsForLabels(); + return m_MaskNonEmpty; } - - template< class TInputImage, class TLabelImage > - std::list - ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > - ::GetRelevantLabels() const + template< typename TInputImage, typename TLabelImage > + void + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::SetHistogramParameters(const int numBins, RealType lowerBound, RealType upperBound) { - return m_RelevantLabels; + m_NumBins[0] = numBins; + m_LowerBound = lowerBound; + m_UpperBound = upperBound; + m_GlobalHistogramParametersSet = true; + m_PreferGlobalHistogramParameters = true; + this->Modified(); } + template< typename TInputImage, typename TLabelImage > + void + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::SetHistogramParametersForLabels(std::map numBins, std::map lowerBound, + std::map upperBound) + { + m_LabelMin = lowerBound; + m_LabelMax = upperBound; + m_LabelNBins = numBins; + m_LabelHistogramParametersSet = true; + m_PreferGlobalHistogramParameters = false; + this->Modified(); + } template< class TInputImage, class TLabelImage > - bool + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > - ::GetMaskingNonEmpty() const + ::GetUniformity(LabelPixelType label) const { - return m_MaskNonEmpty; + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::Zero; + } + else + { + return ( *mapIt ).second.m_Uniformity; + } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > - ::GetUniformity(LabelPixelType label) const + ::GetMedian(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { - return ( *mapIt ).second.m_Uniformity; + return ( *mapIt ).second.m_Median; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetEntropy(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Entropy; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetUPP(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_UPP; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetMPP(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_MPP; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetKurtosis(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Kurtosis; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetSkewness(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Skewness; } } - template< class TInputImage, class TLabelImage > - void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: - CalculateSettingsForLabels() + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetMinimum(LabelPixelType label) const { - LabelPixelType i; - m_MaskNonEmpty = false; - for ( i = 1; i < 4096; ++i ) - { - if ( this->HasLabel( i ) ) + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { - m_RelevantLabels.push_back( i ); - m_MaskNonEmpty = true; - m_LabelStatisticsCoefficients.insert( std::make_pair(i, CoefficientsClass()) ); + // label does not exist, return a default value + return NumericTraits< PixelType >::max(); + } + else + { + return ( *mapIt ).second.m_Minimum; } - } } + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetMaximum(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; - template< class TInputImage, class TLabelImage > - void - ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: - ComputeEntropyUniformityAndUPP() + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::NonpositiveMin(); + } + else + { + return ( *mapIt ).second.m_Maximum; + } + } + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetMean(LabelPixelType label) const { - double baseChange = std::log10(2); - RealType partialProbability( 0.0 ); - RealType uniformity( 0.0 ); - RealType entropy( 0.0 ); - RealType upp( 0.0 ); - - LabelPixelType i; - if ( m_MaskNonEmpty ) - { - typename std::list< int >::const_iterator it; - for ( it = m_RelevantLabels.cbegin(), i = 0; - it != m_RelevantLabels.cend(); - ++it, ++i ) + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { - HistogramType::Pointer histogramForEntropy = this->GetHistogram(*it); - for (int i = 0; i < histogramForEntropy->Size(); i++) - { - partialProbability = histogramForEntropy->GetFrequency(i,0) / double ( histogramForEntropy->GetTotalFrequency() ) ; + // label does not exist, return a default value + return NumericTraits< PixelType >::ZeroValue(); + } + else + { + return ( *mapIt ).second.m_Mean; + } + } - if( partialProbability != 0) - { - entropy -= partialProbability *( std::log10(partialProbability) / std::log10(2) ) ; - uniformity += std::pow(partialProbability,2); + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetSum(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::ZeroValue(); + } + else + { + return ( *mapIt ).second.m_Sum; + } + } + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetSigma(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; - if(histogramForEntropy->GetMeasurement(i,0) > 0) - { - upp += std::pow(partialProbability,2); - } - } + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::ZeroValue(); + } + else + { + return ( *mapIt ).second.m_Sigma; + } + } + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetVariance(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::ZeroValue(); + } + else + { + return ( *mapIt ).second.m_Variance; + } + } + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::BoundingBoxType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetBoundingBox(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + typename Superclass::BoundingBoxType emptyBox; + // label does not exist, return a default value + return emptyBox; + } + else + { + return ( *mapIt ).second.m_BoundingBox; + } + } + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RegionType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetRegion(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + + if ( mapIt == m_LabelStatistics.end() ) + { + typename Superclass::RegionType emptyRegion; + // label does not exist, return a default value + return emptyRegion; + } + else + { + typename Superclass::BoundingBoxType bbox = this->GetBoundingBox(label); + typename Superclass::IndexType index; + typename Superclass::SizeType size; + + unsigned int dimension = bbox.size() / 2; + + for ( unsigned int i = 0; i < dimension; i++ ) + { + index[i] = bbox[2 * i]; + size[i] = bbox[2 * i + 1] - bbox[2 * i] + 1; } - m_LabelStatisticsCoefficients[*it].m_Entropy = entropy; - m_LabelStatisticsCoefficients[*it].m_Uniformity = uniformity; - m_LabelStatisticsCoefficients[*it].m_UPP = upp; + typename Superclass::RegionType region; + region.SetSize(size); + region.SetIndex(index); + + return region; } - } } - template< class TInputImage, class TLabelImage > + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::MapSizeType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetCount(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return 0; + } + else + { + return ( *mapIt ).second.m_Count; + } + } + + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::HistogramType::Pointer + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetHistogram(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return ITK_NULLPTR; + } + else + { + // this will be zero if histograms have not been enabled + return ( *mapIt ).second.m_Histogram; + } + } + + + + template< typename TInputImage, typename TLabelImage > void - ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: - ComputeSkewnessKurtosisAndMPP() + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::BeforeThreadedGenerateData() { - typename TLabelImage::RegionType Subregion; + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + // Resize the thread temporaries + m_LabelStatisticsPerThread.resize(numberOfThreads); - RealType baseOfSkewnessAndCurtosis( 0.0 ); - RealType kurtosis( 0.0 ); - RealType skewness( 0.0 ); - RealType mpp( 0.0 ); - RealType currentPixel( 0.0 ); + // Initialize the temporaries + for ( ThreadIdType i = 0; i < numberOfThreads; ++i ) + { + m_LabelStatisticsPerThread[i].clear(); + } - std::list< LabelPixelType> relevantLabels; - LabelPixelType i; - if ( m_MaskNonEmpty ) + // Initialize the final map + m_LabelStatistics.clear(); + } + + template< typename TInputImage, typename TLabelImage > + std::list + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetRelevantLabels() const + { + std::list< int> relevantLabels; + for (int i = 0; i < 4096; ++i ) { - typename std::list< int >::const_iterator it; - for ( it = m_RelevantLabels.cbegin(), i = 0; - it != m_RelevantLabels.cend(); - ++it ) + if ( this->HasLabel( i ) ) { - RealType sigma = this->GetSigma( *it ); - RealType mean = this->GetMean( *it ); - Subregion = Superclass::GetRegion(*it); + relevantLabels.push_back( i ); + } + } + return relevantLabels; + } - int count( this->GetCount(*it) ); - if ( count == 0 || sigma < mitk::eps) - { - throw std::logic_error( "Empty segmentation" ); - } + template< typename TInputImage, typename TLabelImage > + void + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::ThreadedGenerateData(const typename TInputImage::RegionType & outputRegionForThread, + ThreadIdType threadId) + { - if ( fabs( sigma ) < mitk::sqrteps ) - { - throw std::logic_error( "Sigma == 0" ); - } + typename HistogramType::IndexType histogramIndex(1); + typename HistogramType::MeasurementVectorType histogramMeasurement(1); + + const SizeValueType size0 = outputRegionForThread.GetSize(0); + if( size0 == 0) + { + return; + } + + ImageLinearConstIteratorWithIndex< TInputImage > it (this->GetInput(), + outputRegionForThread); + ImageScanlineConstIterator< TLabelImage > labelIt (this->GetLabelInput(), + outputRegionForThread); - ImageRegionConstIteratorWithIndex< TInputImage > it1 (this->GetInput(), - Subregion); - ImageRegionConstIterator< TLabelImage > labelIt (this->GetLabelInput(), - Subregion); + StatisticsMapIterator mapIt; - for (it1.GoToBegin(); !it1.IsAtEnd(); ++it1, ++labelIt) + // support progress methods/callbacks + const size_t numberOfLinesToProcess = outputRegionForThread.GetNumberOfPixels() / size0; + ProgressReporter progress( this, threadId, numberOfLinesToProcess ); + + typedef typename MapType::value_type MapValueType; + + // do the work + while ( !it.IsAtEnd() ) + { + while ( !it.IsAtEndOfLine() ) { - if (labelIt.Get() == *it) + const RealType & value = static_cast< RealType >( it.Get() ); + + const LabelPixelType & label = labelIt.Get(); + + // is the label already in this thread? + mapIt = m_LabelStatisticsPerThread[threadId].find(label); + if ( mapIt == m_LabelStatisticsPerThread[threadId].end() ) { - currentPixel = it1.Get(); - baseOfSkewnessAndCurtosis = (currentPixel -mean) / sigma; - kurtosis += std::pow( baseOfSkewnessAndCurtosis, 4.0 ); - skewness += std::pow( baseOfSkewnessAndCurtosis, 3.0 ); + // if global histogram parameters are set and preferred then use them + if ( m_PreferGlobalHistogramParameters && m_GlobalHistogramParametersSet ) + { + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics(m_NumBins[0], m_LowerBound, + m_UpperBound) ) ).first; + } + // if we have label histogram parameters then use them. If we encounter a label that has no parameters then use global settings if available + else if(!m_PreferGlobalHistogramParameters && m_LabelHistogramParametersSet) + { + typename std::map::iterator lbIt, ubIt; + typename std::map::iterator nbIt; + + lbIt = m_LabelMin.find(label); + ubIt = m_LabelMax.find(label); + nbIt = m_LabelNBins.find(label); - if(currentPixel > 0) + // if any of the parameters is lacking for the current label but global histogram params are available, use the global parameters + if ((lbIt == m_LabelMin.end() || ubIt == m_LabelMax.end() || nbIt == m_LabelNBins.end()) && m_GlobalHistogramParametersSet) { - mpp+= currentPixel; + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics(m_NumBins[0], m_LowerBound, + m_UpperBound) ) ).first; } + // if any of the parameters is lacking for the current label and global histogram params are not available, dont use histograms for this label + else if ((lbIt == m_LabelMin.end() || ubIt == m_LabelMax.end() || nbIt == m_LabelNBins.end()) && !m_GlobalHistogramParametersSet) + { + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics() ) ).first; + } + // label histogram parameters are available, use them! + else + { + PixelType lowerBound, upperBound; + unsigned int nBins; + lowerBound = (*lbIt).second; + upperBound = (*ubIt).second; + nBins = (*nbIt).second; + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics(nBins, lowerBound, upperBound) ) ).first; + } + } + // neither global nor label specific histogram parameters are set -> don't use histograms + else + { + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics() ) ).first; + } + } + + typename MapType::mapped_type &labelStats = ( *mapIt ).second; + // update the values for this label and this thread + if ( value < labelStats.m_Minimum ) + { + labelStats.m_Minimum = value; + } + if ( value > labelStats.m_Maximum ) + { + labelStats.m_Maximum = value; } + + // bounding box is min,max pairs + for ( unsigned int i = 0; i < ( 2 * TInputImage::ImageDimension ); i += 2 ) + { + const typename TInputImage::IndexType & index = it.GetIndex(); + if ( labelStats.m_BoundingBox[i] > index[i / 2] ) + { + labelStats.m_BoundingBox[i] = index[i / 2]; + } + if ( labelStats.m_BoundingBox[i + 1] < index[i / 2] ) + { + labelStats.m_BoundingBox[i + 1] = index[i / 2]; + } + } + + labelStats.m_Sum += value; + labelStats.m_SumOfSquares += ( value * value ); + labelStats.m_Count++; + labelStats.m_SumOfCubes += std::pow(value, 3.); + labelStats.m_SumOfQuadruples += std::pow(value, 4.); + + if (value > 0) + { + labelStats.m_PositivePixelCount++; + labelStats.m_SumOfPositivePixels += value; + } + + // if enabled, update the histogram for this label + if ( labelStats.m_Histogram.IsNotNull() ) + { + histogramMeasurement[0] = value; + labelStats.m_Histogram->GetIndex(histogramMeasurement, histogramIndex); + labelStats.m_Histogram->IncreaseFrequencyOfIndex(histogramIndex, 1); + } + else + { + int x = 0; } - m_LabelStatisticsCoefficients[*it].m_Skewness = RealType(skewness/count); - m_LabelStatisticsCoefficients[*it].m_Kurtosis = RealType(kurtosis/count); - m_LabelStatisticsCoefficients[*it].m_MPP = RealType(mpp/count); + + + ++labelIt; + ++it; + } + labelIt.NextLine(); + it.NextLine(); + progress.CompletedPixel(); } - } - } + } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: AfterThreadedGenerateData() { - Superclass::AfterThreadedGenerateData(); + StatisticsMapIterator mapIt; + StatisticsMapConstIterator threadIt; + ThreadIdType i; + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + // Run through the map for each thread and accumulate the count, + // sum, and sumofsquares + for ( i = 0; i < numberOfThreads; i++ ) + { + // iterate over the map for this thread + for ( threadIt = m_LabelStatisticsPerThread[i].begin(); + threadIt != m_LabelStatisticsPerThread[i].end(); + ++threadIt ) + { + // does this label exist in the cumulative structure yet? + mapIt = m_LabelStatistics.find( ( *threadIt ).first ); + if ( mapIt == m_LabelStatistics.end() ) + { + // create a new entry + typedef typename MapType::value_type MapValueType; + if ( m_GlobalHistogramParametersSet || m_LabelHistogramParametersSet ) + { +// mapIt = m_LabelStatistics.insert( MapValueType( ( *threadIt ).first, +// LabelStatistics(m_NumBins[0], m_LowerBound, +// m_UpperBound) ) ).first; + mapIt = m_LabelStatistics.insert( MapValueType( *threadIt ) ).first; + continue; + } + else + { + mapIt = m_LabelStatistics.insert( MapValueType( ( *threadIt ).first, + LabelStatistics() ) ).first; + } + } - CalculateSettingsForLabels(); - ComputeSkewnessKurtosisAndMPP(); + typename MapType::mapped_type &labelStats = ( *mapIt ).second; - if(this->GetUseHistograms()) - { - ComputeEntropyUniformityAndUPP(); - } - else - { - MITK_WARN << "Cannot compute coefficients UPP,Entropy,Uniformity because of missing histogram"; - } + // accumulate the information from this thread + labelStats.m_Count += ( *threadIt ).second.m_Count; + labelStats.m_Sum += ( *threadIt ).second.m_Sum; + labelStats.m_SumOfSquares += ( *threadIt ).second.m_SumOfSquares; + labelStats.m_SumOfPositivePixels += ( *threadIt ).second.m_SumOfPositivePixels; + labelStats.m_PositivePixelCount += ( *threadIt ).second.m_PositivePixelCount; + labelStats.m_SumOfCubes += ( *threadIt ).second.m_SumOfCubes; + labelStats.m_SumOfQuadruples += ( *threadIt ).second.m_SumOfQuadruples; + + if ( labelStats.m_Minimum > ( *threadIt ).second.m_Minimum ) + { + labelStats.m_Minimum = ( *threadIt ).second.m_Minimum; + } + if ( labelStats.m_Maximum < ( *threadIt ).second.m_Maximum ) + { + labelStats.m_Maximum = ( *threadIt ).second.m_Maximum; + } + + //bounding box is min,max pairs + int dimension = labelStats.m_BoundingBox.size() / 2; + for ( int ii = 0; ii < ( dimension * 2 ); ii += 2 ) + { + if ( labelStats.m_BoundingBox[ii] > ( *threadIt ).second.m_BoundingBox[ii] ) + { + labelStats.m_BoundingBox[ii] = ( *threadIt ).second.m_BoundingBox[ii]; + } + if ( labelStats.m_BoundingBox[ii + 1] < ( *threadIt ).second.m_BoundingBox[ii + 1] ) + { + labelStats.m_BoundingBox[ii + 1] = ( *threadIt ).second.m_BoundingBox[ii + 1]; + } + } + + // if enabled, update the histogram for this label + if ( m_GlobalHistogramParametersSet || m_LabelHistogramParametersSet ) + { + typename HistogramType::IndexType index; + index.SetSize(1); + for ( unsigned int bin = 0; bin < labelStats.m_Histogram->Size(); bin++ ) + { + index[0] = bin; + labelStats.m_Histogram->IncreaseFrequency( bin, ( *threadIt ).second.m_Histogram->GetFrequency(bin) ); + } + } + } // end of thread map iterator loop + } // end of thread loop + + // compute the remainder of the statistics + for ( mapIt = m_LabelStatistics.begin(); + mapIt != m_LabelStatistics.end(); + ++mapIt ) + { + typename MapType::mapped_type &labelStats = ( *mapIt ).second; + + // mean + labelStats.m_Mean = labelStats.m_Sum + / static_cast< RealType >( labelStats.m_Count ); + + // MPP + labelStats.m_MPP = labelStats.m_SumOfPositivePixels + / static_cast< RealType >( labelStats.m_PositivePixelCount ); + + // variance + if ( labelStats.m_Count > 0 ) + { + // unbiased estimate of variance + LabelStatistics & ls = mapIt->second; + const RealType sumSquared = ls.m_Sum * ls.m_Sum; + const RealType count = static_cast< RealType >( ls.m_Count ); + + ls.m_Variance = ( ls.m_SumOfSquares - sumSquared / count ) / ( count ); + + RealType secondMoment = ls.m_SumOfSquares / count; + RealType thirdMoment = ls.m_SumOfCubes / count; + RealType fourthMoment = ls.m_SumOfQuadruples / count; + + ls.m_Skewness = (thirdMoment - 3. * secondMoment * ls.m_Mean + 2. * std::pow(ls.m_Mean, 3.)) / std::pow(secondMoment - std::pow(ls.m_Mean, 2.), 1.5); // see http://www.boost.org/doc/libs/1_51_0/doc/html/boost/accumulators/impl/skewness_impl.html + ls.m_Kurtosis = (fourthMoment - 4. * thirdMoment * ls.m_Mean + 6. * secondMoment * std::pow(ls.m_Mean, 2.) - 3. * std::pow(ls.m_Mean, 4.)) / std::pow(secondMoment - std::pow(ls.m_Mean, 2.), 2.); // see http://www.boost.org/doc/libs/1_51_0/doc/html/boost/accumulators/impl/kurtosis_impl.html, dropped -3 + } + else + { + labelStats.m_Variance = NumericTraits< RealType >::ZeroValue(); + labelStats.m_Skewness = NumericTraits< RealType >::ZeroValue(); + labelStats.m_Kurtosis = NumericTraits< RealType >::ZeroValue(); + } + + // sigma + labelStats.m_Sigma = std::sqrt( labelStats.m_Variance ); + + // histogram statistics + if (labelStats.m_Histogram.IsNotNull()) + { + mitk::HistogramStatisticsCalculator histStatCalc; + histStatCalc.SetHistogram(labelStats.m_Histogram); + histStatCalc.CalculateStatistics(); + labelStats.m_Median = histStatCalc.GetMedian(); + labelStats.m_Entropy = histStatCalc.GetEntropy(); + labelStats.m_Uniformity = histStatCalc.GetUniformity(); + labelStats.m_UPP = histStatCalc.GetUPP(); + } + + } + + { + //Now update the cached vector of valid labels. + m_ValidLabelValues.resize(0); + m_ValidLabelValues.reserve(m_LabelStatistics.size()); + for ( mapIt = m_LabelStatistics.begin(); + mapIt != m_LabelStatistics.end(); + ++mapIt ) + { + m_ValidLabelValues.push_back(mapIt->first); + } + } } } // end namespace itk #endif diff --git a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h b/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h index ea0927d30c..912bec98dd 100644 --- a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h +++ b/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h @@ -1,205 +1,210 @@ /*=================================================================== 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 __mitkExtendedStatisticsImageFilter_h -#define __mitkExtendedStatisticsImageFilter_h +#ifndef __mitkExtendedStatisticsImageFilter +#define __mitkExtendedStatisticsImageFilter #include "itkStatisticsImageFilter.h" -#include "itkScalarImageToHistogramGenerator.h" #include #include namespace itk { /** * \class ExtendedStatisticsImageFilter * \brief Extension of the itkStatisticsImageFilter that also calculates the Skewness and Kurtosis. * * This class inherits from the itkStatisticsImageFilter and * uses its results for the calculation of other additional coefficients: * the Skewness and Kurtosis. * - * As these coefficient are based on the mean and the sigma which are both calculated - * by the StatisticsImageFilter, the method AfterThreadedGenerateData() is overwritten - * and calls ComputeSkewnessKurtosisAndMPP() and ComputeEntropyUniformityAndUPP - * after the AfterThreadedGenerateData() - * implementation of the superclass is called. - * * As the StatisticsImageFilter stores the statistics in the outputs 1 to 6 by the - * StatisticsImageFilter, the skewness, kurtosis,MPP,UPP,Uniformity and Entropy are stored in the outputs - * 7 to 13 by this filter. + * StatisticsImageFilter, the skewness, kurtosis, MPP, UPP, Uniformity, Entropy and Median are stored in the outputs + * 7 to 14 by this filter. */ template< class TInputImage > class ExtendedStatisticsImageFilter : public StatisticsImageFilter< TInputImage > { public: /** Standard Self typedef */ typedef ExtendedStatisticsImageFilter Self; typedef StatisticsImageFilter< TInputImage > Superclass; typedef SmartPointer< Self > Pointer; typedef SmartPointer< const Self > ConstPointer; typedef typename Superclass::RealType RealType; typedef typename Superclass::RealObjectType RealObjectType; typedef typename Superclass::PixelType PixelType; + /** Histogram-related typedefs */ + typedef itk::Statistics::Histogram< RealType > HistogramType; + typedef typename HistogramType::Pointer HistogramPointer; + itkFactorylessNewMacro( Self ); itkCloneMacro( Self ); itkTypeMacro( ExtendedStatisticsImageFilter, StatisticsImageFilter ); - - typedef itk::Statistics::ScalarImageToHistogramGenerator< TInputImage > - HistogramGeneratorType; - /** * \brief Return the computed Skewness. */ double GetSkewness() const { return this->GetSkewnessOutput()->Get(); } /** * \brief Return the computed Median */ double GetMedian() const { return this->GetMedianOutput()->Get(); } /** * \brief Return the computed Kurtosis. */ double GetKurtosis() const { return this->GetKurtosisOutput()->Get(); } /* \brief Return the computed MPP. */ double GetMPP() const { return this->GetMPPOutput()->Get(); } /** * \brief Return the computed Uniformity. */ double GetUniformity() const { return this->GetUniformityOutput()->Get(); } /** *\brief Return the computed Entropy. */ double GetEntropy() const { return this->GetEntropyOutput()->Get(); } /** * \brief Return the computed UPP. */ double GetUPP() const { return this->GetUPPOutput()->Get(); } /** * \brief Return the computed Histogram. */ - const typename HistogramGeneratorType::HistogramType* + const typename HistogramType::Pointer GetHistogram() { - return m_HistogramGenerator->GetOutput(); + if (m_HistogramCalculated) + { + return m_Histogram; + } + else + { + return ITK_NULLPTR; + } } - /** - * \brief Set the Binsize for the Histogram. - */ - void SetBinSize(int size); + + /** specify Histogram parameters */ + void SetHistogramParameters(const int numBins, RealType lowerBound, + RealType upperBound); protected: ExtendedStatisticsImageFilter(); virtual ~ExtendedStatisticsImageFilter(){}; + void BeforeThreadedGenerateData(); + + /** Multi-thread version GenerateData. */ + void ThreadedGenerateData(const typename StatisticsImageFilter::RegionType & + outputRegionForThread, + ThreadIdType threadId); + /** * brief Calls AfterThreadedGenerateData() of the superclass and the main methods */ void AfterThreadedGenerateData(); - void CalculateHistogram(); - - /** - * \brief Compute Entropy,uniformity,MPP,UPP, Median. - * - * The Entropy,uniformity,MPP, Median and UPP will be calculated with the Sigma, Histogram and Mean Value of the - * itkStatisticsImageFilter which comes out of the threadedGenerateData(). - */ - void ComputeSkewnessKurtosisAndMPP(); - void ComputeEntropyUniformityMedianAndUPP(); - - /** - * \brief Histogram. - * - * new members for setting and calculating the hisotgram for those coefficients which depends on this - */ - typename HistogramGeneratorType::Pointer m_HistogramGenerator; - int m_BinSize; - bool m_HistogramCalculated; RealObjectType* GetSkewnessOutput(); const RealObjectType* GetSkewnessOutput() const; RealObjectType* GetKurtosisOutput(); const RealObjectType* GetKurtosisOutput() const; RealObjectType* GetMPPOutput(); const RealObjectType* GetMPPOutput() const; RealObjectType* GetEntropyOutput(); const RealObjectType* GetEntropyOutput() const; RealObjectType* GetUniformityOutput(); const RealObjectType* GetUniformityOutput() const; RealObjectType* GetUPPOutput(); const RealObjectType* GetUPPOutput() const; RealObjectType* GetMedianOutput(); const RealObjectType* GetMedianOutput() const; virtual DataObject::Pointer MakeOutput( ProcessObject::DataObjectPointerArraySizeType idx ); +private: + Array< RealType > m_ThreadSum; + Array< RealType > m_SumOfSquares; + Array< RealType > m_SumOfCubes; + Array< RealType > m_SumOfQuadruples; + Array< SizeValueType > m_Count; + Array< SizeValueType > m_PositivePixelCount; + Array< RealType > m_ThreadSumOfPositivePixels; + Array< PixelType > m_ThreadMin; + Array< PixelType > m_ThreadMax; + std::vector< HistogramPointer > m_HistogramPerThread; + HistogramPointer m_Histogram; + bool m_UseHistogram; + bool m_HistogramCalculated; + RealType m_LowerBound, m_UpperBound; + int m_NumBins; + }; // end of class } // end namespace itk #ifndef ITK_MANUAL_INSTANTIATION #include "mitkExtendedStatisticsImageFilter.hxx" #endif #endif diff --git a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx b/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx index 004cf1a90b..37085f67f2 100644 --- a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx +++ b/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx @@ -1,344 +1,477 @@ /*=================================================================== 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 __mitkExtendedStatisticsImageFilter_hxx #define __mitkExtendedStatisticsImageFilter_hxx #include "mitkExtendedStatisticsImageFilter.h" +#include namespace itk { template< class TInputImage > ExtendedStatisticsImageFilter< TInputImage >::ExtendedStatisticsImageFilter() - : StatisticsImageFilter< TInputImage >() + : StatisticsImageFilter< TInputImage >(), + m_ThreadSum(1), + m_SumOfSquares(1), + m_SumOfCubes(1), + m_SumOfQuadruples(1), + m_Count(1), + m_ThreadMin(1), + m_ThreadMax(1), + m_PositivePixelCount(1), + m_ThreadSumOfPositivePixels(1), + m_UseHistogram(false), + m_HistogramCalculated(false) { /* * add the Skewness,Kurtosis,Entropy,Uniformity,MPP, UPP, Median to the other statistical calculated Values * of the mitkStatisticsImageFilter as the 7th to the 13th Output */ for ( int i = 7; i < 14; ++i ) { typename RealObjectType::Pointer output = static_cast< RealObjectType * >( this->MakeOutput(i).GetPointer() ); this->ProcessObject::SetNthOutput( i, output.GetPointer() ); } this->GetSkewnessOutput()->Set( 0.0 ); this->GetKurtosisOutput()->Set( 0.0 ); this->GetEntropyOutput()->Set( -1.0 ); this->GetUniformityOutput()->Set( 0.0 ); this->GetUPPOutput()->Set( 0.0 ); this->GetMPPOutput()->Set( 0.0 ); this->GetMedianOutput()->Set( 0.0 ); - this->m_HistogramCalculated = false; - this->m_HistogramGenerator = HistogramGeneratorType::New(); + m_Histogram = ITK_NULLPTR; + m_HistogramPerThread.resize(0); } template< class TInputImage > DataObject::Pointer ExtendedStatisticsImageFilter< TInputImage >::MakeOutput( ProcessObject::DataObjectPointerArraySizeType output) { switch ( output ) { case 7: case 8: case 9: case 10: case 11: case 12: { return RealObjectType::New().GetPointer(); break; } case 13: { return RealObjectType::New().GetPointer(); break; } default: { // might as well make an image return Superclass::MakeOutput( output ); break; } } } - - template< class TInputImage > - void ExtendedStatisticsImageFilter< TInputImage > - ::SetBinSize( int size ) - { - m_BinSize = size; - } - template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetSkewnessOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(7) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetSkewnessOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(7) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetKurtosisOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(8) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetKurtosisOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(8) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUniformityOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(9) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUniformityOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(9) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetEntropyOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(10) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetEntropyOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(10) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUPPOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(11) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUPPOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(11) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMPPOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(12) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMPPOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(12) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMedianOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(13) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMedianOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(13) ); } - - template< class TInputImage > + template< typename TInputImage > void - ExtendedStatisticsImageFilter< TInputImage > - ::AfterThreadedGenerateData() + ExtendedStatisticsImageFilter< TInputImage > + ::BeforeThreadedGenerateData() { - Superclass::AfterThreadedGenerateData(); + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); - ComputeSkewnessKurtosisAndMPP(); - - CalculateHistogram(); - - if(m_HistogramCalculated == true) - { - ComputeEntropyUniformityMedianAndUPP(); - } - else + if (m_UseHistogram) { - MITK_WARN << "Cannot compute coefficients UPP,Entropy,Uniformity because of missing histogram"; + // Histogram + m_Histogram = HistogramType::New(); + typename HistogramType::SizeType hsize; + typename HistogramType::MeasurementVectorType lb; + typename HistogramType::MeasurementVectorType ub; + hsize.SetSize(1); + lb.SetSize(1); + ub.SetSize(1); + m_Histogram->SetMeasurementVectorSize(1); + hsize[0] = m_NumBins; + lb[0] = m_LowerBound; + ub[0] = m_UpperBound; + m_Histogram->Initialize(hsize, lb, ub); + + m_HistogramPerThread.resize(numberOfThreads); + + for (unsigned int i = 0; i < numberOfThreads; i++) + { + typename HistogramType::Pointer hist = HistogramType::New(); + typename HistogramType::SizeType hsize; + typename HistogramType::MeasurementVectorType lb; + typename HistogramType::MeasurementVectorType ub; + hsize.SetSize(1); + lb.SetSize(1); + ub.SetSize(1); + hist->SetMeasurementVectorSize(1); + hsize[0] = m_NumBins; + lb[0] = m_LowerBound; + ub[0] = m_UpperBound; + hist->Initialize(hsize, lb, ub); + m_HistogramPerThread[i] = hist; + } } + + // Resize the thread temporaries + m_Count.SetSize(numberOfThreads); + m_SumOfSquares.SetSize(numberOfThreads); + m_SumOfCubes.SetSize(numberOfThreads); + m_SumOfQuadruples.SetSize(numberOfThreads); + m_ThreadSum.SetSize(numberOfThreads); + m_ThreadMin.SetSize(numberOfThreads); + m_ThreadMax.SetSize(numberOfThreads); + m_PositivePixelCount.SetSize(numberOfThreads); + m_ThreadSumOfPositivePixels.SetSize(numberOfThreads); + + // Initialize the temporaries + m_Count.Fill(NumericTraits< SizeValueType >::Zero); + m_ThreadSum.Fill(NumericTraits< RealType >::Zero); + m_SumOfSquares.Fill(NumericTraits< RealType >::Zero); + m_SumOfCubes.Fill(NumericTraits< RealType >::Zero); + m_SumOfQuadruples.Fill(NumericTraits< RealType >::Zero); + m_ThreadMin.Fill( NumericTraits< PixelType >::max() ); + m_ThreadMax.Fill( NumericTraits< PixelType >::NonpositiveMin() ); + m_PositivePixelCount.Fill(NumericTraits< SizeValueType >::Zero); + m_ThreadSumOfPositivePixels.Fill(NumericTraits< SizeValueType >::Zero); } - template< class TInputImage > + + template< typename TInputImage > void - ExtendedStatisticsImageFilter< TInputImage > - ::ComputeSkewnessKurtosisAndMPP() + ExtendedStatisticsImageFilter< TInputImage > + ::ThreadedGenerateData(const typename StatisticsImageFilter::RegionType & + outputRegionForThread, + ThreadIdType threadId) { - RealType mean = this->GetMean(); - RealType sigma = this->GetSigma(); - RealType baseOfSkewnessAndKurtosis; - RealType kurtosis(0.0); - RealType skewness(0.0); - RealType mpp(0.0); - RealType currentPixel(0.0); - - if ( sigma < mitk::eps ) - { - throw std::logic_error( "Empty segmentation" ); - } - - - ImageRegionConstIterator< TInputImage > it (this->GetInput(), this->GetInput()->GetLargestPossibleRegion() ); - - int counter = 0; - for (it.GoToBegin(); !it.IsAtEnd(); ++it) - { - currentPixel = it.Get(); - - baseOfSkewnessAndKurtosis = (currentPixel - mean) / sigma ; - kurtosis += std::pow( baseOfSkewnessAndKurtosis, 4.0 ); - skewness += std::pow( baseOfSkewnessAndKurtosis, 3.0 ); - - if(currentPixel > 0) + const SizeValueType size0 = outputRegionForThread.GetSize(0); + if( size0 == 0) { - mpp+= currentPixel; + return; } - counter++; - } - - if ( counter == 0 ) - { - throw std::logic_error( "Empty segmentation" ); - } - - kurtosis = kurtosis / counter; - skewness = skewness / counter; - mpp = mpp/counter; + RealType realValue; + PixelType value; + + typename HistogramType::IndexType histogramIndex(1); + typename HistogramType::MeasurementVectorType histogramMeasurement(1); + + RealType sum = NumericTraits< RealType >::ZeroValue(); + RealType sumOfPositivePixels = NumericTraits< RealType >::ZeroValue(); + RealType sumOfSquares = NumericTraits< RealType >::ZeroValue(); + RealType sumOfCubes = NumericTraits< RealType >::ZeroValue(); + RealType sumOfQuadruples = NumericTraits< RealType >::ZeroValue(); + SizeValueType count = NumericTraits< SizeValueType >::ZeroValue(); + SizeValueType countOfPositivePixels = NumericTraits< SizeValueType >::ZeroValue(); + PixelType min = NumericTraits< PixelType >::max(); + PixelType max = NumericTraits< PixelType >::NonpositiveMin(); + + ImageScanlineConstIterator< TInputImage > it (this->GetInput(), outputRegionForThread); + + // support progress methods/callbacks + const size_t numberOfLinesToProcess = outputRegionForThread.GetNumberOfPixels() / size0; + ProgressReporter progress( this, threadId, numberOfLinesToProcess ); + + // do the work + while ( !it.IsAtEnd() ) + { + while ( !it.IsAtEndOfLine() ) + { + value = it.Get(); + realValue = static_cast< RealType >( value ); - this->GetKurtosisOutput()->Set( kurtosis ); - this->GetSkewnessOutput()->Set( skewness ); - this->GetMPPOutput()->Set( mpp ); - } + if (m_UseHistogram) + { + histogramMeasurement[0] = value; + m_HistogramPerThread[threadId]->GetIndex(histogramMeasurement, histogramIndex); + m_HistogramPerThread[threadId]->IncreaseFrequencyOfIndex(histogramIndex, 1); + } + if ( value < min ) + { + min = value; + } + if ( value > max ) + { + max = value; + } + if (value > 0) + { + sumOfPositivePixels += realValue; + ++countOfPositivePixels; + } - template< class TInputImage > - void - ExtendedStatisticsImageFilter< TInputImage > - ::CalculateHistogram() - { - m_HistogramGenerator->SetInput( this->GetInput() ); - m_HistogramGenerator->SetMarginalScale( 100 ); - m_HistogramGenerator->SetNumberOfBins( m_BinSize ); - m_HistogramGenerator->SetHistogramMin( this->GetMinimum() ); - m_HistogramGenerator->SetHistogramMax( this->GetMaximum() ); - m_HistogramGenerator->Compute(); + sum += realValue; + sumOfSquares += ( realValue * realValue ); + sumOfCubes += std::pow(realValue, 3.); + sumOfQuadruples += std::pow(realValue, 4.); + ++count; + ++it; + } + it.NextLine(); + progress.CompletedPixel(); + } - m_HistogramCalculated = true; + m_ThreadSum[threadId] = sum; + m_SumOfSquares[threadId] = sumOfSquares; + m_Count[threadId] = count; + m_ThreadMin[threadId] = min; + m_ThreadMax[threadId] = max; + m_ThreadSumOfPositivePixels[threadId] = sumOfPositivePixels; + m_PositivePixelCount[threadId] = countOfPositivePixels; + m_SumOfCubes[threadId] = sumOfCubes; + m_SumOfQuadruples[threadId] = sumOfQuadruples; } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > - ::ComputeEntropyUniformityMedianAndUPP() + ::AfterThreadedGenerateData() { - double baseChange = std::log10(2); - RealType partialProbability( 0.0 ); - RealType uniformity( 0.0 ); - RealType entropy( 0.0 ); - RealType upp( 0.0 ); - RealType median( 0.0 ); - - const typename HistogramGeneratorType::HistogramType* histogramForEntropy = GetHistogram(); - - double cumulativeProbability = 0.0; - bool medianFound = false; - - for (int i = 0; i < histogramForEntropy->Size(); i++) - { - partialProbability = histogramForEntropy->GetFrequency(i,0) / double ( histogramForEntropy->GetTotalFrequency() ) ; - cumulativeProbability += double ( partialProbability ); - - if( partialProbability != 0) + ThreadIdType i; + SizeValueType count = 0; + SizeValueType countOfPositivePixels = 0; + RealType sumOfSquares = 0; + RealType sumOfCubes = 0; + RealType sumOfQuadruples = 0; + RealType sumOfPositivePixels = 0; + + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + PixelType minimum; + PixelType maximum; + RealType mean; + RealType meanOfPositivePixels; + RealType sigma; + RealType variance; + RealType skewness; + RealType kurtosis; + RealType sum; + + sum = sumOfSquares = sumOfCubes = sumOfQuadruples = NumericTraits< RealType >::ZeroValue(); + count = countOfPositivePixels = 0; + + // Find the min/max over all threads and accumulate count, sum and + // sum of squares + minimum = NumericTraits< PixelType >::max(); + maximum = NumericTraits< PixelType >::NonpositiveMin(); + for ( i = 0; i < numberOfThreads; i++ ) { - entropy -= partialProbability *( std::log10(partialProbability) / std::log10(2) ) ; - uniformity += std::pow(partialProbability,2); - - if(histogramForEntropy->GetMeasurement(i,0) > 0) + count += m_Count[i]; + sum += m_ThreadSum[i]; + sumOfSquares += m_SumOfSquares[i]; + sumOfCubes += m_SumOfCubes[i]; + sumOfQuadruples += m_SumOfQuadruples[i]; + sumOfPositivePixels += m_ThreadSumOfPositivePixels[i]; + countOfPositivePixels += m_PositivePixelCount[i]; + + if ( m_ThreadMin[i] < minimum ) { - upp += std::pow(partialProbability,2); + minimum = m_ThreadMin[i]; + } + if ( m_ThreadMax[i] > maximum ) + { + maximum = m_ThreadMax[i]; + } + // if enabled, update the histogram for this label + if ( m_UseHistogram ) + { + typename HistogramType::IndexType index; + index.SetSize(1); + for ( unsigned int bin = 0; bin < m_NumBins; bin++ ) + { + index[0] = bin; + m_Histogram->IncreaseFrequency( bin, m_HistogramPerThread[i]->GetFrequency(bin) ); + } } } - - if( cumulativeProbability >= 0.5 && !medianFound ) - { - RealType binMin = histogramForEntropy->GetBinMin( 0, i ); - RealType binMax = histogramForEntropy->GetBinMax( 0, i ); - median = ( binMin + binMax ) / 2.0; - medianFound = true; - } + // compute statistics + mean = sum / static_cast< RealType >( count ); + meanOfPositivePixels = sumOfPositivePixels / static_cast< RealType >( countOfPositivePixels ); + + // unbiased estimate + variance = ( sumOfSquares - ( sum * sum / static_cast< RealType >( count ) ) ) + / ( static_cast< RealType >( count ) ); + RealType secondMoment, thirdMoment, fourthMoment; + secondMoment = sumOfSquares / static_cast< RealType >( count ); + thirdMoment = sumOfCubes / static_cast< RealType >( count ); + fourthMoment = sumOfQuadruples / static_cast< RealType >( count ); + + skewness = (thirdMoment - 3. * secondMoment * mean + 2. * std::pow(mean, 3.)) / std::pow(secondMoment - std::pow(mean, 2.), 1.5); // see http://www.boost.org/doc/libs/1_51_0/doc/html/boost/accumulators/impl/skewness_impl.html + kurtosis = (fourthMoment - 4. * thirdMoment * mean + 6. * secondMoment * std::pow(mean, 2.) - 3. * std::pow(mean, 4.)) / std::pow(secondMoment - std::pow(mean, 2.), 2.); // see http://www.boost.org/doc/libs/1_46_1/doc/html/boost/accumulators/impl/kurtosis_impl.html, dropped -3 + sigma = std::sqrt(variance); + + // Set the outputs + this->GetMinimumOutput()->Set(minimum); + this->GetMaximumOutput()->Set(maximum); + this->GetMeanOutput()->Set(mean); + this->GetSigmaOutput()->Set(sigma); + this->GetVarianceOutput()->Set(variance); + this->GetSumOutput()->Set(sum); + this->GetKurtosisOutput()->Set(kurtosis); + this->GetSkewnessOutput()->Set(skewness); + this->GetMPPOutput()->Set(meanOfPositivePixels); + + if (m_UseHistogram) + { + mitk::HistogramStatisticsCalculator histStatCalc; + histStatCalc.SetHistogram(m_Histogram); + histStatCalc.CalculateStatistics(); + this->GetUniformityOutput()->Set(histStatCalc.GetUniformity()); + this->GetUPPOutput()->Set(histStatCalc.GetUPP()); + this->GetEntropyOutput()->Set(histStatCalc.GetEntropy()); + this->GetMedianOutput()->Set(histStatCalc.GetMedian()); } - this->GetEntropyOutput()->Set( entropy ); - this->GetUniformityOutput()->Set( uniformity ); - this->GetUPPOutput()->Set( upp ); - this->GetMedianOutput()->Set( median ); + m_HistogramCalculated = true; + } + template< typename TInputImage > + void + ExtendedStatisticsImageFilter< TInputImage > + ::SetHistogramParameters(const int numBins, RealType lowerBound, RealType upperBound) + { + m_NumBins = numBins; + m_LowerBound = lowerBound; + m_UpperBound = upperBound; + m_UseHistogram = true; } + } #endif diff --git a/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.cpp b/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.cpp new file mode 100644 index 0000000000..b7af6a479d --- /dev/null +++ b/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.cpp @@ -0,0 +1,108 @@ +#include +#include + +namespace mitk { + +HistogramStatisticsCalculator::HistogramStatisticsCalculator(): + m_StatisticsCalculated(false) +{ + +} + +void HistogramStatisticsCalculator::SetHistogram(HistogramType::Pointer histogram) +{ + if (m_Histogram != histogram) + { + m_Histogram = histogram; + m_StatisticsCalculated = false; + } +} + +HistogramStatisticsCalculator::MeasurementType HistogramStatisticsCalculator::GetEntropy() +{ + if (!m_StatisticsCalculated) + { + MITK_WARN("Statistics have not yet been calculated, running calculation now..."); + CalculateStatistics(); + } + return m_Entropy; +} + +HistogramStatisticsCalculator::MeasurementType HistogramStatisticsCalculator::GetMedian() +{ + if (!m_StatisticsCalculated) + { + MITK_WARN("Statistics have not yet been calculated, running calculation now..."); + CalculateStatistics(); + } + return m_Median; +} + +HistogramStatisticsCalculator::MeasurementType HistogramStatisticsCalculator::GetUniformity() +{ + if (!m_StatisticsCalculated) + { + MITK_WARN("Statistics have not yet been calculated, running calculation now..."); + CalculateStatistics(); + } + return m_Uniformity; +} + +HistogramStatisticsCalculator::MeasurementType HistogramStatisticsCalculator::GetUPP() +{ + if (!m_StatisticsCalculated) + { + MITK_WARN("Statistics have not yet been calculated, running calculation now..."); + CalculateStatistics(); + } + return m_UPP; +} + +void HistogramStatisticsCalculator::CalculateStatistics() +{ + if (m_Histogram.IsNull()) + { + throw std::runtime_error("Histogram not set in HistogramStatisticsCalculator::CalculateStatistics()"); + } + + unsigned int nBins = m_Histogram->GetSize()[0]; + m_Uniformity = 0; + m_Entropy = 0; + m_UPP = 0; + m_Median = 0; + + MeasurementType cumulativeProbability = 0.0; + MeasurementType partialProbability; + bool medianFound(false); + + for (unsigned int i = 0; i < nBins; i++) + { + partialProbability = m_Histogram->GetFrequency(i, 0) / double( m_Histogram->GetTotalFrequency() ); + cumulativeProbability += partialProbability; + + if (partialProbability != 0) + { + m_Entropy -= partialProbability * (std::log2( partialProbability )); + m_Uniformity += std::pow(partialProbability, 2); + + if (m_Histogram->GetMeasurement(i, 0) > 0) + { + m_UPP += std::pow(partialProbability, 2); + } + + } + + if (cumulativeProbability >= 0.5 && !medianFound) + { + MeasurementType binMin = m_Histogram->GetBinMin(0, i); + MeasurementType binMax = m_Histogram->GetBinMax(0, i); + m_Median = (binMax + binMin) / 2.0; + medianFound = true; + } + + } + m_StatisticsCalculated = true; +} + +} + diff --git a/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.h b/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.h new file mode 100644 index 0000000000..a7e3e3d450 --- /dev/null +++ b/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.h @@ -0,0 +1,49 @@ +#ifndef MITKHISTOGRAMSTATISTICSCALCULATOR +#define MITKHISTOGRAMSTATISTICSCALCULATOR + +#include +#include +#include + + +namespace mitk +{ +/** + * @brief Computes basic histogram statistics such as Uniformity, UPP (Uniformity of positive entries), Entropy and Median (approximation) + */ + class MITKIMAGESTATISTICS_EXPORT HistogramStatisticsCalculator + { + public: + typedef double MeasurementType; + typedef itk::Statistics::Histogram HistogramType; + + HistogramStatisticsCalculator(); + + /** + * @brief SetHistogram requires a itk::Statistics::Histogram + */ + void SetHistogram(HistogramType::Pointer histogram); + + MeasurementType GetUPP(); + + MeasurementType GetUniformity(); + + MeasurementType GetEntropy(); + + MeasurementType GetMedian(); + + /** + * @brief calculate statistics + */ + void CalculateStatistics(); + + protected: + + private: + HistogramType::Pointer m_Histogram; + MeasurementType m_Uniformity, m_UPP, m_Entropy, m_Median; + bool m_StatisticsCalculated; + }; +} + +#endif diff --git a/Modules/ImageStatistics/mitkHotspotMaskGenerator.cpp b/Modules/ImageStatistics/mitkHotspotMaskGenerator.cpp new file mode 100644 index 0000000000..da07560989 --- /dev/null +++ b/Modules/ImageStatistics/mitkHotspotMaskGenerator.cpp @@ -0,0 +1,595 @@ +#include +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include + +namespace mitk +{ + HotspotMaskGenerator::HotspotMaskGenerator(): + m_HotspotRadiusinMM(6.2035049089940), // radius of a 1cm3 sphere in mm + m_HotspotMustBeCompletelyInsideImage(true), + m_Label(1) + { + m_TimeStep = 0; + m_InternalMask = mitk::Image::New(); + m_InternalMaskUpdateTime = 0; + } + + void HotspotMaskGenerator::SetInputImage(mitk::Image::Pointer inputImage) + { + if (inputImage != m_inputImage) + { + m_inputImage = inputImage; + m_ConvolutionImageMaxIndex.set_size(inputImage->GetDimension()); + m_ConvolutionImageMinIndex.set_size(inputImage->GetDimension()); + this->Modified(); + } + } + + void HotspotMaskGenerator::SetMask(MaskGenerator::Pointer mask) + { + if (mask != m_Mask) + { + m_Mask = mask; + this->Modified(); + } + } + + HotspotMaskGenerator::~HotspotMaskGenerator() + { + } + + void HotspotMaskGenerator::SetHotspotRadiusInMM(double radiusInMillimeter) + { + if(radiusInMillimeter != m_HotspotRadiusinMM) + { + m_HotspotRadiusinMM = radiusInMillimeter; + this->Modified(); + } + } + + const double& HotspotMaskGenerator::GetHotspotRadiusinMM() const + { + return m_HotspotRadiusinMM; + } + + bool HotspotMaskGenerator::GetHotspotMustBeCompletelyInsideImage() const + { + return m_HotspotMustBeCompletelyInsideImage; + } + + void HotspotMaskGenerator::SetHotspotMustBeCompletelyInsideImage(bool mustBeCompletelyInImage) + { + if (m_HotspotMustBeCompletelyInsideImage != mustBeCompletelyInImage) + { + m_HotspotMustBeCompletelyInsideImage = mustBeCompletelyInImage; + this->Modified(); + } + } + + + mitk::Image::Pointer HotspotMaskGenerator::GetMask() + { + if (IsUpdateRequired()) + { + if ( m_inputImage.IsNull() ) + { + throw std::runtime_error( "Error: image empty!" ); + } + + if ( m_TimeStep >= m_inputImage->GetTimeSteps() ) + { + throw std::runtime_error( "Error: invalid time step!" ); + } + + mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); + imageTimeSelector->SetInput( m_inputImage ); + imageTimeSelector->SetTimeNr( m_TimeStep ); + imageTimeSelector->UpdateLargestPossibleRegion(); + mitk::Image::Pointer timeSliceImage = imageTimeSelector->GetOutput(); + + m_internalImage = timeSliceImage; + m_internalMask2D = nullptr; // is this correct when this variable holds a smart pointer? + m_internalMask3D = nullptr; + + if ( m_Mask != nullptr ) + { + m_Mask->SetTimeStep(m_TimeStep); + mitk::Image::Pointer timeSliceMask = m_Mask->GetMask(); + + if ( m_internalImage->GetDimension() == 3 ) + { + CastToItkImage(timeSliceMask, m_internalMask3D); + AccessFixedDimensionByItk_2(m_internalImage, CalculateHotspotMask, 3, m_internalMask3D, m_Label); + } + else if ( m_internalImage->GetDimension() == 2 ) + { + CastToItkImage(timeSliceMask, m_internalMask2D); + AccessFixedDimensionByItk_2(m_internalImage, CalculateHotspotMask, 2, m_internalMask2D, m_Label); + } + else + { + throw std::runtime_error( "Error: invalid image dimension" ); + } + } + else + { + + if ( m_internalImage->GetDimension() == 3 ) + { + AccessFixedDimensionByItk_2(m_internalImage, CalculateHotspotMask, 3, m_internalMask3D, m_Label); + } + else if ( m_internalImage->GetDimension() == 2 ) + { + AccessFixedDimensionByItk_2(m_internalImage, CalculateHotspotMask, 2, m_internalMask2D, m_Label); + } + else + { + throw std::runtime_error( "Error: invalid image dimension" ); + } + } + this->Modified(); + } + + m_InternalMaskUpdateTime = m_InternalMask->GetMTime(); + return m_InternalMask; + } + + void HotspotMaskGenerator::SetLabel(unsigned short label) + { + if (label != m_Label) + { + m_Label = label; + this->Modified(); + } + } + + vnl_vector HotspotMaskGenerator::GetConvolutionImageMinIndex() + { + this->GetMask(); // make sure we are up to date + return m_ConvolutionImageMinIndex; + } + + vnl_vector HotspotMaskGenerator::GetHotspotIndex() + { + this->GetMask(); // make sure we are up to date + return m_ConvolutionImageMaxIndex; + } + + template + HotspotMaskGenerator::ImageExtrema + HotspotMaskGenerator::CalculateExtremaWorld( const itk::Image* inputImage, + typename itk::Image::Pointer maskImage, + double neccessaryDistanceToImageBorderInMM, + unsigned int label ) + { + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef itk::Image< unsigned short, VImageDimension > MaskImageType; + + typedef itk::ImageRegionConstIteratorWithIndex MaskImageIteratorType; + typedef itk::ImageRegionConstIteratorWithIndex InputImageIndexIteratorType; + + typename ImageType::SpacingType spacing = inputImage->GetSpacing(); + + ImageExtrema minMax; + minMax.Defined = false; + minMax.MaxIndex.set_size(VImageDimension); + minMax.MaxIndex.set_size(VImageDimension); + + typename ImageType::RegionType allowedExtremaRegion = inputImage->GetLargestPossibleRegion(); + + bool keepDistanceToImageBorders( neccessaryDistanceToImageBorderInMM > 0 ); + if (keepDistanceToImageBorders) + { + long distanceInPixels[VImageDimension]; + for(unsigned short dimension = 0; dimension < VImageDimension; ++dimension) + { + // To confirm that the whole hotspot is inside the image we have to keep a specific distance to the image-borders, which is as long as + // the radius. To get the amount of indices we divide the radius by spacing and add 0.5 because voxels are center based: + // For example with a radius of 2.2 and a spacing of 1 two indices are enough because 2.2 / 1 + 0.5 = 2.7 => 2. + // But with a radius of 2.7 we need 3 indices because 2.7 / 1 + 0.5 = 3.2 => 3 + distanceInPixels[dimension] = int( neccessaryDistanceToImageBorderInMM / spacing[dimension] + 0.5); + } + + allowedExtremaRegion.ShrinkByRadius(distanceInPixels); + } + + InputImageIndexIteratorType imageIndexIt(inputImage, allowedExtremaRegion); + + float maxValue = itk::NumericTraits::min(); + float minValue = itk::NumericTraits::max(); + + typename ImageType::IndexType maxIndex; + typename ImageType::IndexType minIndex; + + for(unsigned short i = 0; i < VImageDimension; ++i) + { + maxIndex[i] = 0; + minIndex[i] = 0; + } + + if (maskImage != nullptr) + { + MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); + typename ImageType::IndexType imageIndex; + typename ImageType::PointType worldPosition; + typename ImageType::IndexType maskIndex; + + for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) + { + imageIndex = maskIndex = maskIt.GetIndex(); + + if(maskIt.Get() == label) + { + if( allowedExtremaRegion.IsInside(imageIndex) ) + { + imageIndexIt.SetIndex( imageIndex ); + double value = imageIndexIt.Get(); + minMax.Defined = true; + + //Calculate minimum, maximum and corresponding index-values + if( value > maxValue ) + { + maxIndex = imageIndexIt.GetIndex(); + maxValue = value; + } + + if(value < minValue ) + { + minIndex = imageIndexIt.GetIndex(); + minValue = value; + } + } + } + } + } + else + { + for(imageIndexIt.GoToBegin(); !imageIndexIt.IsAtEnd(); ++imageIndexIt) + { + double value = imageIndexIt.Get(); + minMax.Defined = true; + + //Calculate minimum, maximum and corresponding index-values + if( value > maxValue ) + { + maxIndex = imageIndexIt.GetIndex(); + maxValue = value; + } + + if(value < minValue ) + { + minIndex = imageIndexIt.GetIndex(); + minValue = value; + } + } + } + + minMax.MaxIndex.set_size(VImageDimension); + minMax.MinIndex.set_size(VImageDimension); + + for(unsigned int i = 0; i < minMax.MaxIndex.size(); ++i) + { + minMax.MaxIndex[i] = maxIndex[i]; + } + + for(unsigned int i = 0; i < minMax.MinIndex.size(); ++i) + { + minMax.MinIndex[i] = minIndex[i]; + } + + minMax.Max = maxValue; + minMax.Min = minValue; + + return minMax; + } + + template + itk::Size + HotspotMaskGenerator::CalculateConvolutionKernelSize( double spacing[VImageDimension], + double radiusInMM ) + { + typedef itk::Image< float, VImageDimension > KernelImageType; + typedef typename KernelImageType::SizeType SizeType; + SizeType maskSize; + + for(unsigned int i = 0; i < VImageDimension; ++i) + { + maskSize[i] = static_cast( 2 * radiusInMM / spacing[i]); + + // We always want an uneven size to have a clear center point in the convolution mask + if(maskSize[i] % 2 == 0 ) + { + ++maskSize[i]; + } + } + return maskSize; + } + + template + itk::SmartPointer< itk::Image > + HotspotMaskGenerator::GenerateHotspotSearchConvolutionKernel(double mmPerPixel[VImageDimension], + double radiusInMM ) + { + std::stringstream ss; + for (unsigned int i = 0; i < VImageDimension; ++i) + { + ss << mmPerPixel[i]; + if (i < VImageDimension -1) + ss << ","; + } + MITK_DEBUG << "Update convolution kernel for spacing (" << ss.str() << ") and radius " << radiusInMM << "mm"; + + + double radiusInMMSquared = radiusInMM * radiusInMM; + typedef itk::Image< float, VImageDimension > KernelImageType; + typename KernelImageType::Pointer convolutionKernel = KernelImageType::New(); + + // Calculate size and allocate mask image + typedef typename KernelImageType::SizeType SizeType; + SizeType maskSize = this->CalculateConvolutionKernelSize(mmPerPixel, radiusInMM); + + mitk::Point3D convolutionMaskCenterIndex; + convolutionMaskCenterIndex.Fill(0.0); + for(unsigned int i = 0; i < VImageDimension; ++i) + { + convolutionMaskCenterIndex[i] = 0.5 * (double)(maskSize[i]-1); + } + + typedef typename KernelImageType::IndexType IndexType; + IndexType maskIndex; + maskIndex.Fill(0); + + typedef typename KernelImageType::RegionType RegionType; + RegionType maskRegion; + maskRegion.SetSize(maskSize); + maskRegion.SetIndex(maskIndex); + + convolutionKernel->SetRegions(maskRegion); + convolutionKernel->SetSpacing(mmPerPixel); + convolutionKernel->Allocate(); + + // Fill mask image values by subsampling the image grid + typedef itk::ImageRegionIteratorWithIndex MaskIteratorType; + MaskIteratorType maskIt(convolutionKernel,maskRegion); + + int numberOfSubVoxelsPerDimension = 2; // per dimension! + int numberOfSubVoxels = ::pow( static_cast(numberOfSubVoxelsPerDimension), static_cast(VImageDimension) ); + double subVoxelSizeInPixels = 1.0 / (double)numberOfSubVoxelsPerDimension; + double valueOfOneSubVoxel = 1.0 / (double)numberOfSubVoxels; + double maskValue = 0.0; + mitk::Point3D subVoxelIndexPosition; + double distanceSquared = 0.0; + + typedef itk::ContinuousIndex ContinuousIndexType; + for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) + { + ContinuousIndexType indexPoint(maskIt.GetIndex()); + mitk::Point3D voxelPosition; + for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) + { + voxelPosition[dimension] = indexPoint[dimension]; + } + + maskValue = 0.0; + mitk::Vector3D subVoxelOffset; subVoxelOffset.Fill(0.0); + // iterate sub-voxels by iterating all possible offsets + for (subVoxelOffset[0] = -0.5 + subVoxelSizeInPixels / 2.0; + subVoxelOffset[0] < +0.5; + subVoxelOffset[0] += subVoxelSizeInPixels) + { + for (subVoxelOffset[1] = -0.5 + subVoxelSizeInPixels / 2.0; + subVoxelOffset[1] < +0.5; + subVoxelOffset[1] += subVoxelSizeInPixels) + { + for (subVoxelOffset[2] = -0.5 + subVoxelSizeInPixels / 2.0; + subVoxelOffset[2] < +0.5; + subVoxelOffset[2] += subVoxelSizeInPixels) + { + subVoxelIndexPosition = voxelPosition + subVoxelOffset; // this COULD be integrated into the for-loops if neccessary (add voxelPosition to initializer and end condition) + distanceSquared = + (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] * (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] + + (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] * (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] + + (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2] * (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2]; + + if (distanceSquared <= radiusInMMSquared) + { + maskValue += valueOfOneSubVoxel; + } + } + } + } + maskIt.Set( maskValue ); + } + + return convolutionKernel; + } + + template + itk::SmartPointer > + HotspotMaskGenerator::GenerateConvolutionImage( const itk::Image* inputImage ) + { + double mmPerPixel[VImageDimension]; + for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) + { + mmPerPixel[dimension] = inputImage->GetSpacing()[dimension]; + } + + // update convolution kernel + typedef itk::Image< float, VImageDimension > KernelImageType; + typename KernelImageType::Pointer convolutionKernel = this->GenerateHotspotSearchConvolutionKernel(mmPerPixel, m_HotspotRadiusinMM); + + // update convolution image + typedef itk::Image< TPixel, VImageDimension > InputImageType; + typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; + typedef itk::FFTConvolutionImageFilter ConvolutionFilterType; + + typename ConvolutionFilterType::Pointer convolutionFilter = ConvolutionFilterType::New(); + typedef itk::ConstantBoundaryCondition BoundaryConditionType; + BoundaryConditionType boundaryCondition; + boundaryCondition.SetConstant(0.0); + + if (m_HotspotMustBeCompletelyInsideImage) + { + // overwrite default boundary condition + convolutionFilter->SetBoundaryCondition(&boundaryCondition); + } + + convolutionFilter->SetInput(inputImage); + convolutionFilter->SetKernelImage(convolutionKernel); + convolutionFilter->SetNormalize(true); + MITK_DEBUG << "Update Convolution image for hotspot search"; + convolutionFilter->UpdateLargestPossibleRegion(); + + typename ConvolutionImageType::Pointer convolutionImage = convolutionFilter->GetOutput(); + convolutionImage->SetSpacing( inputImage->GetSpacing() ); // only workaround because convolution filter seems to ignore spacing of input image + + return convolutionImage; + } + + template < typename TPixel, unsigned int VImageDimension> + void + HotspotMaskGenerator::FillHotspotMaskPixels( itk::Image* maskImage, + itk::Point sphereCenter, + double sphereRadiusInMM ) + { + typedef itk::Image< TPixel, VImageDimension > MaskImageType; + typedef itk::ImageRegionIteratorWithIndex MaskImageIteratorType; + + MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); + + typename MaskImageType::IndexType maskIndex; + typename MaskImageType::PointType worldPosition; + + // this is not very smart. I would rather use a 0 initialized mask (not the case here -> blame CalculateHotspotMask) and find the region where I need to iterate over, then iterate only over the small region + for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) + { + maskIndex = maskIt.GetIndex(); + maskImage->TransformIndexToPhysicalPoint(maskIndex, worldPosition); + maskIt.Set( worldPosition.EuclideanDistanceTo(sphereCenter) <= sphereRadiusInMM ? 1 : 0 ); + } + } + + template + void + HotspotMaskGenerator::CalculateHotspotMask(itk::Image* inputImage, + typename itk::Image::Pointer maskImage, + unsigned int label) + { + typedef itk::Image< TPixel, VImageDimension > InputImageType; + typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; + typedef itk::Image< float, VImageDimension > KernelImageType; + typedef itk::Image< unsigned short, VImageDimension > MaskImageType; + + typename ConvolutionImageType::Pointer convolutionImage = this->GenerateConvolutionImage(inputImage); + + if (convolutionImage.IsNull()) + { + MITK_ERROR << "Empty convolution image in CalculateHotspotStatistics(). We should never reach this state (logic error)."; + throw std::logic_error("Empty convolution image in CalculateHotspotStatistics()"); + } + + // if mask image is not defined, create an image of the same size as inputImage and fill it with 1's + // there is maybe a better way to do this!? + if (maskImage == nullptr) + { + maskImage = MaskImageType::New(); + typename MaskImageType::RegionType maskRegion = inputImage->GetLargestPossibleRegion(); + typename MaskImageType::SpacingType maskSpacing = inputImage->GetSpacing(); + typename MaskImageType::PointType maskOrigin = inputImage->GetOrigin(); + typename MaskImageType::DirectionType maskDirection = inputImage->GetDirection(); + maskImage->SetRegions(maskRegion); + maskImage->Allocate(); + maskImage->SetOrigin(maskOrigin); + maskImage->SetSpacing(maskSpacing); + maskImage->SetDirection(maskDirection); + + maskImage->FillBuffer(1); + + label = 1; + } + + // find maximum in convolution image, given the current mask + double requiredDistanceToBorder = m_HotspotMustBeCompletelyInsideImage ? m_HotspotRadiusinMM : -1.0; + ImageExtrema convolutionImageInformation = CalculateExtremaWorld(convolutionImage.GetPointer(), maskImage, requiredDistanceToBorder, label); + + bool isHotspotDefined = convolutionImageInformation.Defined; + + if (!isHotspotDefined) + { + MITK_ERROR << "No origin of hotspot-sphere was calculated!"; + m_InternalMask = nullptr; + } + else + { + // create a binary mask around the "hotspot" region, fill the shape of a sphere around our hotspot center +// typename DuplicatorType::Pointer copyMachine = DuplicatorType::New(); +// copyMachine->SetInputImage(inputImage); +// copyMachine->Update(); + +// typename CastFilterType::Pointer caster = CastFilterType::New(); +// caster->SetInput( copyMachine->GetOutput() ); +// caster->Update(); + typename MaskImageType::Pointer hotspotMaskITK = MaskImageType::New(); + hotspotMaskITK->SetOrigin(inputImage->GetOrigin()); + hotspotMaskITK->SetSpacing(inputImage->GetSpacing()); + hotspotMaskITK->SetLargestPossibleRegion(inputImage->GetLargestPossibleRegion()); + hotspotMaskITK->SetBufferedRegion(inputImage->GetBufferedRegion()); + hotspotMaskITK->SetDirection(inputImage->GetDirection()); + hotspotMaskITK->SetNumberOfComponentsPerPixel(inputImage->GetNumberOfComponentsPerPixel()); + hotspotMaskITK->Allocate(); + hotspotMaskITK->FillBuffer(1); + + typedef typename InputImageType::IndexType IndexType; + IndexType maskCenterIndex; + for (unsigned int d =0; d< VImageDimension;++d) + { + maskCenterIndex[d]=convolutionImageInformation.MaxIndex[d]; + } + + typename ConvolutionImageType::PointType maskCenter; + inputImage->TransformIndexToPhysicalPoint(maskCenterIndex,maskCenter); + + FillHotspotMaskPixels(hotspotMaskITK.GetPointer(), maskCenter, m_HotspotRadiusinMM); + + //obtain mitk::Image::Pointer from itk::Image + mitk::Image::Pointer hotspotMaskAsMITKImage = mitk::GrabItkImageMemory(hotspotMaskITK); + + m_InternalMask = hotspotMaskAsMITKImage; + m_ConvolutionImageMaxIndex = convolutionImageInformation.MaxIndex; + m_ConvolutionImageMinIndex = convolutionImageInformation.MinIndex; + } + } + + bool HotspotMaskGenerator::IsUpdateRequired() const + { + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); + unsigned long maskGeneratorTimeStamp = m_Mask->GetMTime(); + unsigned long inputImageTimeStamp = m_inputImage->GetMTime(); + + if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed + { + return true; + } + + if (m_InternalMaskUpdateTime < maskGeneratorTimeStamp || m_InternalMaskUpdateTime < inputImageTimeStamp) // mask image has changed outside of this class + { + return true; + } + + if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class + { + return true; + } + + return false; + } +} diff --git a/Modules/ImageStatistics/mitkHotspotMaskGenerator.h b/Modules/ImageStatistics/mitkHotspotMaskGenerator.h new file mode 100644 index 0000000000..909fb6b1a6 --- /dev/null +++ b/Modules/ImageStatistics/mitkHotspotMaskGenerator.h @@ -0,0 +1,164 @@ +#ifndef MITKHOTSPOTCALCULATOR_H +#define MITKHOTSPOTCALCULATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace mitk +{ +/** + * @brief The HotspotMaskGenerator class is used when a hotspot has to be found in an image. A hotspot is + * the region of the image where the mean intensity is maximal (=brightest spot). It is usually used in PET scans. + * The identification of the hotspot is done as follows: First a cubic (or circular, if image is 2d) + * mask of predefined size is generated. This mask is then convolved with the input image (in fourier domain). + * The maximum value of the convolved image then corresponds to the hotspot. + * If a maskGenerator is set, only the pixels of the convolved image where the corresponding mask is == @a label + * are searched for the maximum value. + */ + class MITKIMAGESTATISTICS_EXPORT HotspotMaskGenerator: public MaskGenerator + { + public: + /** Standard Self typedef */ + typedef HotspotMaskGenerator Self; + typedef MaskGenerator Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(HotspotMaskGenerator, MaskGenerator) + + /** + @brief Set the input image. Required for this class + */ + void SetInputImage(mitk::Image::Pointer inputImage); + + /** + @brief Set a mask (can be nullptr if no mask is desired) + */ + void SetMask(MaskGenerator::Pointer mask); + + /** + @brief Set the radius of the hotspot (in MM) + */ + void SetHotspotRadiusInMM(double radiusInMillimeter); + + const double& GetHotspotRadiusinMM() const; + + /** + @brief Define whether the hotspot must be completely inside the image. Default is true + */ + void SetHotspotMustBeCompletelyInsideImage(bool hotspotCompletelyInsideImage); + + bool GetHotspotMustBeCompletelyInsideImage() const; + + /** + @brief If a maskGenerator is set, this detemines which mask value is used + */ + void SetLabel(unsigned short label); + + /** + @brief Computes and returns the hotspot mask. The hotspot mask has the same size as the input image. The hopspot has value 1, the remaining pixels are set to 0 + */ + mitk::Image::Pointer GetMask(); + + /** + @brief Returns the image index where the hotspot is located + */ + vnl_vector GetHotspotIndex(); + + /** + @brief Returns the index where the convolution image is minimal (darkest spot in image) + */ + vnl_vector GetConvolutionImageMinIndex(); + + protected: + HotspotMaskGenerator(); + + ~HotspotMaskGenerator(); + + class ImageExtrema + { + public: + bool Defined; + double Max; + double Min; + vnl_vector MaxIndex; + vnl_vector MinIndex; + + ImageExtrema() + :Defined(false) + ,Max(itk::NumericTraits::min()) + ,Min(itk::NumericTraits::max()) + { + } + }; + + private: + /** \brief Returns size of convolution kernel depending on spacing and radius. */ + template + itk::Size + CalculateConvolutionKernelSize(double spacing[VImageDimension], double radiusInMM); + + /** \brief Generates image of kernel which is needed for convolution. */ + template + itk::SmartPointer< itk::Image > + GenerateHotspotSearchConvolutionKernel(double spacing[VImageDimension], double radiusInMM); + + /** \brief Convolves image with spherical kernel image. Used for hotspot calculation. */ + template + itk::SmartPointer< itk::Image > + GenerateConvolutionImage( const itk::Image* inputImage ); + + + /** \brief Fills pixels of the spherical hotspot mask. */ + template < typename TPixel, unsigned int VImageDimension> + void + FillHotspotMaskPixels( itk::Image* maskImage, + itk::Point sphereCenter, + double sphereRadiusInMM); + + + /** \brief */ + template + void + CalculateHotspotMask(itk::Image* inputImage, + typename itk::Image::Pointer maskImage, + unsigned int label); + + + template + ImageExtrema CalculateExtremaWorld( const itk::Image* inputImage, + typename itk::Image::Pointer maskImage, + double neccessaryDistanceToImageBorderInMM, + unsigned int label); + + bool IsUpdateRequired() const; + + HotspotMaskGenerator(const HotspotMaskGenerator &); + HotspotMaskGenerator & operator=(const HotspotMaskGenerator &); + + MaskGenerator::Pointer m_Mask; + mitk::Image::Pointer m_internalImage; + itk::Image::Pointer m_internalMask2D; + itk::Image::Pointer m_internalMask3D; + double m_HotspotRadiusinMM; + bool m_HotspotMustBeCompletelyInsideImage; + bool m_HotspotParamsChanged; + unsigned short m_Label; + vnl_vector m_ConvolutionImageMinIndex, m_ConvolutionImageMaxIndex; + unsigned long m_InternalMaskUpdateTime; + }; +} +#endif // MITKHOTSPOTCALCULATOR + + diff --git a/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.cpp b/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.cpp new file mode 100644 index 0000000000..ba068667dd --- /dev/null +++ b/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include + +namespace mitk +{ +void IgnorePixelMaskGenerator::SetIgnoredPixelValue(RealType pixelValue) +{ + if (pixelValue != m_IgnoredPixelValue) + { + m_IgnoredPixelValue = pixelValue; + this->Modified(); + } +} + +mitk::Image::Pointer IgnorePixelMaskGenerator::GetMask() +{ + if (IsUpdateRequired()) + { + if (m_inputImage.IsNull()) + { + MITK_ERROR << "Image not set!"; + } + + if (m_IgnoredPixelValue == std::numeric_limits::min()) + { + MITK_ERROR << "IgnotePixelValue not set!"; + } + + if (m_TimeStep > (m_inputImage->GetTimeSteps() - 1)) + { + MITK_ERROR << "Invalid time step: " << m_TimeStep << ". The image has " << m_inputImage->GetTimeSteps() << " timeSteps!"; + } + + // extractimage time slice + ImageTimeSelector::Pointer imgTimeSel = ImageTimeSelector::New(); + imgTimeSel->SetInput(m_inputImage); + imgTimeSel->SetTimeNr(m_TimeStep); + imgTimeSel->UpdateLargestPossibleRegion(); + + mitk::Image::Pointer timeSliceImage = imgTimeSel->GetOutput(); + + // update m_InternalMask + AccessByItk(timeSliceImage, InternalCalculateMask); + m_InternalMask->SetGeometry(timeSliceImage->GetGeometry()); + + this->Modified(); + } + m_InternalMaskUpdateTime = m_InternalMask->GetMTime(); + return m_InternalMask; +} + +template +void IgnorePixelMaskGenerator::InternalCalculateMask(typename itk::Image* image) +{ + typedef itk::Image ImageType; + typedef itk::Image MaskType; + + typename MaskType::Pointer mask = MaskType::New(); + mask->SetOrigin(image->GetOrigin()); + mask->SetSpacing(image->GetSpacing()); + mask->SetLargestPossibleRegion(image->GetLargestPossibleRegion()); + mask->SetBufferedRegion(image->GetBufferedRegion()); + mask->SetDirection(image->GetDirection()); + mask->SetNumberOfComponentsPerPixel(image->GetNumberOfComponentsPerPixel()); + mask->Allocate(); + mask->FillBuffer(1); + + // iterate over image and mask and set mask=1 if image=m_IgnorePixelValue + itk::ImageRegionConstIterator imageIterator(image, image->GetLargestPossibleRegion()); + itk::ImageRegionIterator maskIterator(mask, mask->GetLargestPossibleRegion()); + + + for (imageIterator.GoToBegin(); !imageIterator.IsAtEnd(); ++imageIterator, ++maskIterator) + { + if (imageIterator.Value() == static_cast(m_IgnoredPixelValue)) + { + maskIterator.Set(0); + } + } + + m_InternalMask = GrabItkImageMemory(mask); +} + +bool IgnorePixelMaskGenerator::IsUpdateRequired() const +{ + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); + unsigned long inputImageTimeStamp = m_inputImage->GetMTime(); + + if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed + { + return true; + } + + if (m_InternalMaskUpdateTime < inputImageTimeStamp) // mask image has changed outside of this class + { + return true; + } + + if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class + { + return true; + } + + return false; +} + +} // end namespace diff --git a/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.h b/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.h new file mode 100644 index 0000000000..d8eecde982 --- /dev/null +++ b/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.h @@ -0,0 +1,67 @@ +#ifndef MITKIGNOREPIXELMASKGEN_ +#define MITKIGNOREPIXELMASKGEN_ + +#include +#include +#include +#include +#include + + +namespace mitk +{ +/** + * @brief The IgnorePixelMaskGenerator class is used to generate a mask that is zero for specific pixel values in the input image. This class requires an input image. + */ +class MITKIMAGESTATISTICS_EXPORT IgnorePixelMaskGenerator: public MaskGenerator +{ +public: + /** Standard Self typedef */ + typedef IgnorePixelMaskGenerator Self; + typedef MaskGenerator Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + typedef double RealType; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(IgnorePixelMaskGenerator, MaskGenerator) + + /** + * @brief The mask will be 0 there inputImage==pixelValue and 1 otherwise + */ + void SetIgnoredPixelValue(RealType pixelValue); + + /** + * @brief Computes and returns the mask + */ + mitk::Image::Pointer GetMask(); + +protected: + IgnorePixelMaskGenerator(): + m_IgnoredPixelValue(std::numeric_limits::min()) + { + m_TimeStep = 0; + m_InternalMaskUpdateTime = 0; + m_InternalMask = mitk::Image::New(); + } + + ~IgnorePixelMaskGenerator(){} + + template + void InternalCalculateMask(typename itk::Image* image); + +private: + bool IsUpdateRequired() const; + + RealType m_IgnoredPixelValue; + unsigned long m_InternalMaskUpdateTime; + + +}; + +} + +#endif diff --git a/Modules/ImageStatistics/mitkImageMaskGenerator.cpp b/Modules/ImageStatistics/mitkImageMaskGenerator.cpp new file mode 100644 index 0000000000..a96916aebb --- /dev/null +++ b/Modules/ImageStatistics/mitkImageMaskGenerator.cpp @@ -0,0 +1,82 @@ + + +#include +#include +#include + +namespace mitk { + +void ImageMaskGenerator::SetImageMask(Image::Pointer maskImage) +{ + if (m_internalMaskImage != maskImage) + { + m_internalMaskImage = maskImage; + this->Modified(); + } +} + +mitk::Image::Pointer ImageMaskGenerator::GetMask() +{ + if (m_internalMaskImage.IsNull()) + { + MITK_ERROR << "Mask Image is nullptr"; + } + + if (this->IsUpdateRequired()) + { + unsigned int timeStepForExtraction; + + if (m_TimeStep >= m_internalMaskImage->GetTimeSteps()) + { + MITK_WARN << "Warning: time step > number of time steps in mask image, using last time step"; + timeStepForExtraction = m_internalMaskImage->GetTimeSteps() - 1; + } + else + { + timeStepForExtraction = m_TimeStep; + } + ImageTimeSelector::Pointer imageTimeSelector = ImageTimeSelector::New(); + imageTimeSelector->SetInput(m_internalMaskImage); + imageTimeSelector->SetTimeNr(timeStepForExtraction); + imageTimeSelector->UpdateLargestPossibleRegion(); + + m_InternalMask = mitk::Image::New(); + m_InternalMask = imageTimeSelector->GetOutput(); + this->Modified(); + } + + m_InternalMaskUpdateTime = m_InternalMask->GetMTime(); + return m_InternalMask; +} + +bool ImageMaskGenerator::IsUpdateRequired() const +{ + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); + unsigned long maskImageTimeStamp = m_internalMaskImage->GetMTime(); + + if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed + { + return true; + } + + if (m_InternalMaskUpdateTime < maskImageTimeStamp) // mask image has changed outside of this class + { + return true; + } + + if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class + { + return true; + } + + return false; +} + +} + + + + + + diff --git a/Modules/ImageStatistics/mitkImageMaskGenerator.h b/Modules/ImageStatistics/mitkImageMaskGenerator.h new file mode 100644 index 0000000000..0d6d12c826 --- /dev/null +++ b/Modules/ImageStatistics/mitkImageMaskGenerator.h @@ -0,0 +1,48 @@ +#ifndef mitkBinaryMaskGenerator +#define mitkBinaryMaskGenerator + +#include +#include +#include +#include +#include + +namespace mitk +{ +class MITKIMAGESTATISTICS_EXPORT ImageMaskGenerator: public MaskGenerator +{ +public: + /** Standard Self typedef */ + typedef ImageMaskGenerator Self; + typedef MaskGenerator Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(BinaryImageMaskGenerator, MaskGenerator) + + mitk::Image::Pointer GetMask(); + + void SetImageMask(mitk::Image::Pointer maskImage); + +protected: + ImageMaskGenerator():Superclass(){ + m_InternalMaskUpdateTime = 0; + m_InternalMask = mitk::Image::New(); + } + +private: + bool IsUpdateRequired() const; + + mitk::Image::Pointer m_internalMaskImage; + unsigned long m_InternalMaskUpdateTime; + +}; + + +} + +#endif diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp b/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp index b88432606f..4551c82546 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp @@ -1,2249 +1,642 @@ -/*=================================================================== -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 "mitkImageStatisticsCalculator.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkExtractImageFilter.h" -#include "mitkImageTimeSelector.h" -#include "mitkITKImageImport.h" - -#include -#include - -#include +#include +#include -#include +#include +#include +#include #include +#include +#include #include -#include -#include +#include #include -#include -#include +#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "itkImage.h" -//#define DEBUG_HOTSPOTSEARCH -#define _USE_MATH_DEFINES -#include - -#include "vtkLassoStencilSource.h" +#include +#include "itkImageFileWriter.h" namespace mitk { - ImageStatisticsCalculator::ImageStatisticsCalculator() - : m_MaskingMode( MASKING_MODE_NONE ), - m_MaskingModeChanged( false ), - m_IgnorePixelValue(0.0), - m_DoIgnorePixelValue(false), - m_IgnorePixelValueChanged(false), - m_PlanarFigureAxis (0), - m_PlanarFigureSlice (0), - m_PlanarFigureCoordinate0 (0), - m_PlanarFigureCoordinate1 (0), - m_HistogramBinSize(1.0), - m_UseDefaultBinSize(true), - m_UseBinSizeBasedOnVOIRegion(false), - m_HotspotRadiusInMM(6.2035049089940), // radius of a 1cm3 sphere in mm - m_CalculateHotspot(false), - m_HotspotRadiusInMMChanged(false), - m_HotspotMustBeCompletelyInsideImage(true) - { - m_EmptyHistogram = HistogramType::New(); - m_EmptyHistogram->SetMeasurementVectorSize(1); - HistogramType::SizeType histogramSize(1); - histogramSize.Fill( 256 ); - m_EmptyHistogram->Initialize( histogramSize ); - - m_EmptyStatistics.Reset(); - } - - ImageStatisticsCalculator::~ImageStatisticsCalculator() - { - } - - - void ImageStatisticsCalculator::SetUseDefaultBinSize(bool useDefault) - { - m_UseDefaultBinSize = useDefault; - } - - - - - ImageStatisticsCalculator::Statistics::Statistics(bool withHotspotStatistics) - :m_HotspotStatistics(withHotspotStatistics ? new Statistics(false) : nullptr) - { - Reset(); - } - - ImageStatisticsCalculator::Statistics::Statistics(const Statistics& other) - :m_HotspotStatistics( nullptr) - { - this->SetLabel( other.GetLabel() ); - this->SetN( other.GetN() ); - this->SetMin( other.GetMin() ); - this->SetMax( other.GetMax() ); - this->SetMedian( other.GetMedian() ); - this->SetMean( other.GetMean() ); - this->SetVariance( other.GetVariance() ); - this->SetKurtosis( other.GetKurtosis() ); - this->SetSkewness( other.GetSkewness() ); - this->SetUniformity( other.GetUniformity() ); - this->SetEntropy( other.GetEntropy() ); - this->SetUPP( other.GetUPP() ); - this->SetMPP( other.GetMPP() ); - this->SetSigma( other.GetSigma() ); - this->SetRMS( other.GetRMS() ); - this->SetMaxIndex( other.GetMaxIndex() ); - this->SetMinIndex( other.GetMinIndex() ); - this->SetHotspotIndex( other.GetHotspotIndex() ); - - if (other.m_HotspotStatistics) - { - this->m_HotspotStatistics = new Statistics(false); - *this->m_HotspotStatistics = *other.m_HotspotStatistics; - } - } - - bool ImageStatisticsCalculator::Statistics::HasHotspotStatistics() const - { - return m_HotspotStatistics != nullptr; - } - - void ImageStatisticsCalculator::Statistics::SetHasHotspotStatistics(bool hasHotspotStatistics) - { - m_HasHotspotStatistics = hasHotspotStatistics; - } - - - ImageStatisticsCalculator::Statistics::~Statistics() - { - delete m_HotspotStatistics; - } - - double ImageStatisticsCalculator::Statistics::GetVariance() const - { - return this->Variance; - } - - void ImageStatisticsCalculator::Statistics::SetVariance( const double value ) - { - if( this->Variance != value ) - { - if( value < 0.0 ) - { - this->Variance = 0.0; // if given value is negative set variance to 0.0 - } - else - { - this->Variance = value; - } - } - } - - double ImageStatisticsCalculator::Statistics::GetSigma() const - { - return this->Sigma; - } - - void ImageStatisticsCalculator::Statistics::SetSigma( const double value ) - { - if( this->Sigma != value ) - { - // for some compiler the value != value works to check for NaN but not for all - // but we can always be sure that the standard deviation is a positive value - if( value != value || value < 0.0 ) - { - // if standard deviation is NaN we just assume 0.0 - this->Sigma = 0.0; - } - else - { - this->Sigma = value; - } - } - } - - void ImageStatisticsCalculator::Statistics::Reset(unsigned int dimension) - { - SetLabel(0); - SetN( 0 ); - SetMin( 0.0 ); - SetMax( 0.0 ); - SetMedian( 0.0 ); - SetVariance( 0.0 ); - SetMean( 0.0 ); - SetSigma( 0.0 ); - SetRMS( 0.0 ); - - SetSkewness( 0.0 ); - SetKurtosis( 0.0 ); - SetUniformity( 0.0 ); - SetEntropy( 0.0 ); - SetMPP( 0.0 ); - SetUPP( 0.0 ); - - vnl_vector zero; - zero.set_size(dimension); - for(unsigned int i = 0; i < dimension; ++i) - { - zero[i] = 0; - } - - SetMaxIndex(zero); - SetMinIndex(zero); - SetHotspotIndex(zero); - - if (m_HotspotStatistics != nullptr) - { - m_HotspotStatistics->Reset(dimension); - } - } - - const ImageStatisticsCalculator::Statistics& - ImageStatisticsCalculator::Statistics::GetHotspotStatistics() const - { - if (m_HotspotStatistics) - { - return *m_HotspotStatistics; - } - else - { - throw std::logic_error("Object has no hostspot statistics, see HasHotspotStatistics()"); - } - } - - ImageStatisticsCalculator::Statistics& - ImageStatisticsCalculator::Statistics::GetHotspotStatistics() - { - if (m_HotspotStatistics) - { - return *m_HotspotStatistics; - } - else - { - throw std::logic_error("Object has no hostspot statistics, see HasHotspotStatistics()"); - } - } - - ImageStatisticsCalculator::Statistics& - ImageStatisticsCalculator::Statistics::operator=(ImageStatisticsCalculator::Statistics const& other) - { - if (this == &other) - return *this; - - this->SetLabel( other.GetLabel() ); - this->SetN( other.GetN() ); - this->SetMin( other.GetMin() ); - this->SetMax( other.GetMax() ); - this->SetMean( other.GetMean() ); - this->SetMedian( other.GetMedian() ); - this->SetVariance( other.GetVariance() ); - this->SetSigma( other.GetSigma() ); - this->SetRMS( other.GetRMS() ); - this->SetMinIndex( other.GetMinIndex() ); - this->SetMaxIndex( other.GetMaxIndex() ); - this->SetHotspotIndex( other.GetHotspotIndex() ); - this->SetSkewness( other.GetSkewness() ); - this->SetKurtosis( other.GetKurtosis() ); - this->SetUniformity( other.GetUniformity() ); - this->SetEntropy( other.GetEntropy() ); - this->SetUPP( other.GetUPP() ); - this->SetMPP( other.GetMPP() ); - - - delete this->m_HotspotStatistics; - this->m_HotspotStatistics = nullptr; - - if (other.m_HotspotStatistics) - { - this->m_HotspotStatistics = new Statistics(false); - *this->m_HotspotStatistics = *other.m_HotspotStatistics; - } - - return *this; - - } - - void ImageStatisticsCalculator::SetImage( const mitk::Image *image ) - { - if ( m_Image != image ) - { - m_Image = image; - this->Modified(); - unsigned int numberOfTimeSteps = image->GetTimeSteps(); - - // Initialize vectors to time-size of this image - m_ImageHistogramVector.resize( numberOfTimeSteps ); - m_MaskedImageHistogramVector.resize( numberOfTimeSteps ); - m_PlanarFigureHistogramVector.resize( numberOfTimeSteps ); - - m_ImageStatisticsVector.resize( numberOfTimeSteps ); - m_MaskedImageStatisticsVector.resize( numberOfTimeSteps ); - m_PlanarFigureStatisticsVector.resize( numberOfTimeSteps ); - - m_ImageStatisticsTimeStampVector.resize( numberOfTimeSteps ); - m_MaskedImageStatisticsTimeStampVector.resize( numberOfTimeSteps ); - m_PlanarFigureStatisticsTimeStampVector.resize( numberOfTimeSteps ); - - m_ImageStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); - m_MaskedImageStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); - m_PlanarFigureStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); - - for ( unsigned int t = 0; t < image->GetTimeSteps(); ++t ) - { - m_ImageStatisticsTimeStampVector[t].Modified(); - m_ImageStatisticsCalculationTriggerVector[t] = true; - } - } - } - - - void ImageStatisticsCalculator::SetImageMask( const mitk::Image *imageMask ) - { - if ( m_Image.IsNull() ) - { - itkExceptionMacro( << "Image needs to be set first!" ); - } - - if ( m_ImageMask != imageMask ) - { - m_ImageMask = imageMask; - this->Modified(); - - for ( unsigned int t = 0; t < m_Image->GetTimeSteps(); ++t ) - { - m_MaskedImageStatisticsTimeStampVector[t].Modified(); - m_MaskedImageStatisticsCalculationTriggerVector[t] = true; - } - } - } - - void ImageStatisticsCalculator::SetPlanarFigure( mitk::PlanarFigure *planarFigure ) - { - if ( m_Image.IsNull() ) + void ImageStatisticsCalculator::SetInputImage(mitk::Image::Pointer image) { - itkExceptionMacro( << "Image needs to be set first!" ); + if (image != m_Image) + { + m_Image = image; + m_StatisticsByTimeStep.resize(m_Image->GetTimeSteps()); + m_StatisticsUpdateTimePerTimeStep.resize(m_Image->GetTimeSteps()); + std::fill(m_StatisticsUpdateTimePerTimeStep.begin(), m_StatisticsUpdateTimePerTimeStep.end(), 0); + this->Modified(); + } } - if ( m_PlanarFigure != planarFigure ) + void ImageStatisticsCalculator::SetMask(mitk::MaskGenerator::Pointer mask) { - m_PlanarFigure = planarFigure; - this->Modified(); - - for ( unsigned int t = 0; t < m_Image->GetTimeSteps(); ++t ) - { - m_PlanarFigureStatisticsTimeStampVector[t].Modified(); - m_PlanarFigureStatisticsCalculationTriggerVector[t] = true; - } - } - } + if (mask != m_MaskGenerator) + { + m_MaskGenerator = mask; + this->Modified(); + } - void ImageStatisticsCalculator::SetMaskingMode( unsigned int mode ) - { - if ( m_MaskingMode != mode ) - { - m_MaskingMode = mode; - m_MaskingModeChanged = true; - this->Modified(); } - } - - void ImageStatisticsCalculator::SetMaskingModeToNone() - { - if ( m_MaskingMode != MASKING_MODE_NONE ) + void ImageStatisticsCalculator::SetSecondaryMask(mitk::MaskGenerator::Pointer mask) { - m_MaskingMode = MASKING_MODE_NONE; - m_MaskingModeChanged = true; - this->Modified(); - } - } + if (mask != m_SecondaryMaskGenerator) + { + m_SecondaryMaskGenerator = mask; + this->Modified(); + } - void ImageStatisticsCalculator::SetMaskingModeToImage() - { - if ( m_MaskingMode != MASKING_MODE_IMAGE ) - { - m_MaskingMode = MASKING_MODE_IMAGE; - m_MaskingModeChanged = true; - this->Modified(); } - } - void ImageStatisticsCalculator::SetMaskingModeToPlanarFigure() - { - if ( m_MaskingMode != MASKING_MODE_PLANARFIGURE ) + void ImageStatisticsCalculator::SetNBinsForHistogramStatistics(unsigned int nBins) { - m_MaskingMode = MASKING_MODE_PLANARFIGURE; - m_MaskingModeChanged = true; - this->Modified(); - } - } - - void ImageStatisticsCalculator::SetIgnorePixelValue(double value) - { - if ( m_IgnorePixelValue != value ) - { - m_IgnorePixelValue = value; - if(m_DoIgnorePixelValue) - { - m_IgnorePixelValueChanged = true; - } - this->Modified(); + if (nBins != m_nBinsForHistogramStatistics) + { + m_nBinsForHistogramStatistics = nBins; + this->Modified(); + this->m_UseBinSizeOverNBins = false; + } + if (m_UseBinSizeOverNBins) + { + this->Modified(); + this->m_UseBinSizeOverNBins = false; + } } - } - - double ImageStatisticsCalculator::GetIgnorePixelValue() - { - return m_IgnorePixelValue; - } - void ImageStatisticsCalculator::SetDoIgnorePixelValue(bool value) - { - if ( m_DoIgnorePixelValue != value ) + unsigned int ImageStatisticsCalculator::GetNBinsForHistogramStatistics() const { - m_DoIgnorePixelValue = value; - m_IgnorePixelValueChanged = true; - this->Modified(); + return m_nBinsForHistogramStatistics; } - } - - bool ImageStatisticsCalculator::GetDoIgnorePixelValue() - { - return m_DoIgnorePixelValue; - } - - void ImageStatisticsCalculator::SetHistogramBinSize(double size) - { - this->m_HistogramBinSize = size; - } - double ImageStatisticsCalculator::GetHistogramBinSize() - { - return this->m_HistogramBinSize; - } - - void ImageStatisticsCalculator::SetHotspotRadiusInMM(double value) - { - if ( m_HotspotRadiusInMM != value ) - { - m_HotspotRadiusInMM = value; - if(m_CalculateHotspot) - { - m_HotspotRadiusInMMChanged = true; - //MITK_INFO <<"Hotspot radius changed, new convolution required"; - } - this->Modified(); - } - } - double ImageStatisticsCalculator::GetHotspotRadiusInMM() - { - return m_HotspotRadiusInMM; - } - - void ImageStatisticsCalculator::SetCalculateHotspot(bool on) - { - if ( m_CalculateHotspot != on ) + void ImageStatisticsCalculator::SetBinSizeForHistogramStatistics(double binSize) { - m_CalculateHotspot = on; - m_HotspotRadiusInMMChanged = true; - //MITK_INFO <<"Hotspot calculation changed, new convolution required"; - this->Modified(); + if (binSize != m_binSizeForHistogramStatistics) + { + m_binSizeForHistogramStatistics = binSize; + this->Modified(); + this->m_UseBinSizeOverNBins = true; + } + if (!m_UseBinSizeOverNBins) + { + this->Modified(); + this->m_UseBinSizeOverNBins = true; + } } - } - bool ImageStatisticsCalculator::IsHotspotCalculated() - { - return m_CalculateHotspot; - } - - void ImageStatisticsCalculator::SetHotspotMustBeCompletlyInsideImage(bool hotspotMustBeCompletelyInsideImage, bool warn) - { - m_HotspotMustBeCompletelyInsideImage = hotspotMustBeCompletelyInsideImage; - if (!m_HotspotMustBeCompletelyInsideImage && warn) + double ImageStatisticsCalculator::GetBinSizeForHistogramStatistics() const { - MITK_WARN << "Hotspot calculation will extrapolate pixels at image borders. Be aware of the consequences for the hotspot location."; + return m_binSizeForHistogramStatistics; } - } - - bool ImageStatisticsCalculator::GetHotspotMustBeCompletlyInsideImage() const - { - return m_HotspotMustBeCompletelyInsideImage; - } - - - /* Implementation of the min max values for setting the range of the histogram */ - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::GetMinAndMaxValue( double &min, - double &max, int &counter, double &sigma, - const itk::Image< TPixel, VImageDimension > *InputImage, - itk::Image< unsigned short, VImageDimension > *MaskedImage ) - { - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - typedef itk::Image< TPixel, VImageDimension > ImageType; - - typedef itk::ImageRegionConstIteratorWithIndex Imageie; - typedef itk::ImageRegionConstIteratorWithIndex Imageie2; - - Imageie2 labelIterator2( MaskedImage, MaskedImage->GetRequestedRegion() ); - Imageie labelIterator3( InputImage, InputImage->GetRequestedRegion() ); - - max = 0; - min = 0; - counter = 0; - sigma = 0; - double SumOfSquares = 0; - double sumSquared = 0; - double actualPielValue = 0; - int counterOfPixelsInROI = 0; - - - for( labelIterator2.GoToBegin(); !labelIterator2.IsAtEnd(); ++labelIterator2, ++labelIterator3) + ImageStatisticsCalculator::StatisticsContainer::Pointer ImageStatisticsCalculator::GetStatistics(unsigned int timeStep, unsigned int label) { - if( labelIterator2.Value()== 1.0) - { - counter++; - - counterOfPixelsInROI++; - actualPielValue = labelIterator3.Value(); - sumSquared = sumSquared + actualPielValue; - SumOfSquares = SumOfSquares + std::pow(actualPielValue,2); - - if(counterOfPixelsInROI == 1) + if (timeStep >= m_StatisticsByTimeStep.size()) { - max = actualPielValue; - min = actualPielValue; + mitkThrow() << "invalid timeStep in ImageStatisticsCalculator_v2::GetStatistics"; } - if(actualPielValue >= max) + if (m_Image.IsNull()) { - max = actualPielValue; + mitkThrow() << "no image"; } - else if(actualPielValue <= min) + + if (!m_Image->IsInitialized()) { - min = actualPielValue; + mitkThrow() << "Image not initialized!"; } - } - } - - if (counter > 1) - { - sigma = ( SumOfSquares - std::pow( sumSquared, 2) / counter ) / ( counter-1 ); - } - else - { - sigma = 0; - } - - } - - - bool ImageStatisticsCalculator::ComputeStatistics( unsigned int timeStep ) - { - - if (m_Image.IsNull() ) - { - mitkThrow() << "Image not set!"; - } - - if (!m_Image->IsInitialized()) - { - mitkThrow() << "Image not initialized!"; - } - - if ( m_Image->GetReferenceCount() == 1 ) - { - // Image no longer valid; we are the only ones to still hold a reference on it - return false; - } - - if ( timeStep >= m_Image->GetTimeSteps() ) - { - throw std::runtime_error( "Error: invalid time step!" ); - } - - // If a mask was set but we are the only ones to still hold a reference on - // it, delete it. - if ( m_ImageMask.IsNotNull() && (m_ImageMask->GetReferenceCount() == 1) ) - { - m_ImageMask = nullptr; - } - - // Check if statistics is already up-to-date - unsigned long imageMTime = m_ImageStatisticsTimeStampVector[timeStep].GetMTime(); - unsigned long maskedImageMTime = m_MaskedImageStatisticsTimeStampVector[timeStep].GetMTime(); - unsigned long planarFigureMTime = m_PlanarFigureStatisticsTimeStampVector[timeStep].GetMTime(); - - bool imageStatisticsCalculationTrigger = m_ImageStatisticsCalculationTriggerVector[timeStep]; - bool maskedImageStatisticsCalculationTrigger = m_MaskedImageStatisticsCalculationTriggerVector[timeStep]; - bool planarFigureStatisticsCalculationTrigger = m_PlanarFigureStatisticsCalculationTriggerVector[timeStep]; - - if ( !m_IgnorePixelValueChanged - && !m_HotspotRadiusInMMChanged - && ((m_MaskingMode != MASKING_MODE_NONE) || (imageMTime > m_Image->GetMTime() && !imageStatisticsCalculationTrigger)) - && ((m_MaskingMode != MASKING_MODE_IMAGE) || (maskedImageMTime > m_ImageMask->GetMTime() && !maskedImageStatisticsCalculationTrigger)) - && ((m_MaskingMode != MASKING_MODE_PLANARFIGURE) || (planarFigureMTime > m_PlanarFigure->GetMTime() && !planarFigureStatisticsCalculationTrigger)) ) - { - // Statistics is up to date! - if ( m_MaskingModeChanged ) - { - m_MaskingModeChanged = false; - } - else - { - return false; - } - } - - // Reset state changed flag - m_MaskingModeChanged = false; - m_IgnorePixelValueChanged = false; - - // Depending on masking mode, extract and/or generate the required image - // and mask data from the user input - this->ExtractImageAndMask( timeStep ); - - - StatisticsContainer *statisticsContainer; - HistogramContainer *histogramContainer; - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - if(!m_DoIgnorePixelValue) - { - statisticsContainer = &m_ImageStatisticsVector[timeStep]; - histogramContainer = &m_ImageHistogramVector[timeStep]; - - m_ImageStatisticsTimeStampVector[timeStep].Modified(); - m_ImageStatisticsCalculationTriggerVector[timeStep] = false; - } - else - { - statisticsContainer = &m_MaskedImageStatisticsVector[timeStep]; - histogramContainer = &m_MaskedImageHistogramVector[timeStep]; - - m_MaskedImageStatisticsTimeStampVector[timeStep].Modified(); - m_MaskedImageStatisticsCalculationTriggerVector[timeStep] = false; - } - break; - - case MASKING_MODE_IMAGE: - statisticsContainer = &m_MaskedImageStatisticsVector[timeStep]; - histogramContainer = &m_MaskedImageHistogramVector[timeStep]; - - m_MaskedImageStatisticsTimeStampVector[timeStep].Modified(); - m_MaskedImageStatisticsCalculationTriggerVector[timeStep] = false; - break; - - case MASKING_MODE_PLANARFIGURE: - statisticsContainer = &m_PlanarFigureStatisticsVector[timeStep]; - histogramContainer = &m_PlanarFigureHistogramVector[timeStep]; - - m_PlanarFigureStatisticsTimeStampVector[timeStep].Modified(); - m_PlanarFigureStatisticsCalculationTriggerVector[timeStep] = false; - break; - } - - // Calculate statistics and histogram(s) - if ( m_InternalImage->GetDimension() == 3 ) - { - if ( m_MaskingMode == MASKING_MODE_NONE && !m_DoIgnorePixelValue ) - { - AccessFixedDimensionByItk_2( - m_InternalImage, - InternalCalculateStatisticsUnmasked, - 3, - statisticsContainer, - histogramContainer ); - } - else - { - AccessFixedDimensionByItk_3( - m_InternalImage, - InternalCalculateStatisticsMasked, - 3, - m_InternalImageMask3D.GetPointer(), - statisticsContainer, - histogramContainer ); - } - } - else if ( m_InternalImage->GetDimension() == 2 ) - { - if ( m_MaskingMode == MASKING_MODE_NONE && !m_DoIgnorePixelValue ) - { - AccessFixedDimensionByItk_2( - m_InternalImage, - InternalCalculateStatisticsUnmasked, - 2, - statisticsContainer, - histogramContainer ); - } - else - { - AccessFixedDimensionByItk_3( - m_InternalImage, - InternalCalculateStatisticsMasked, - 2, - m_InternalImageMask2D.GetPointer(), - statisticsContainer, - histogramContainer ); - } - } - else - { - MITK_ERROR << "ImageStatistics: Image dimension not supported!"; - } - - - // Release unused image smart pointers to free memory - m_InternalImage = mitk::Image::ConstPointer(); - m_InternalImageMask3D = MaskImage3DType::Pointer(); - m_InternalImageMask2D = MaskImage2DType::Pointer(); - - - - return true; - } - - - ImageStatisticsCalculator::BinFrequencyType - ImageStatisticsCalculator::GetBinsAndFreuqencyForHistograms( unsigned int timeStep , unsigned int label ) const - { - const HistogramType *binsAndFrequencyToCalculate = this->GetHistogram(0); - - // ToDo: map should be created on stack not on heap - std::map returnedHistogramMap; - - unsigned int size = binsAndFrequencyToCalculate->Size(); - for( unsigned int bin=0; bin < size; ++bin ) - { - double frequency = binsAndFrequencyToCalculate->GetFrequency( bin, 0 ); - //if( frequency > mitk::eps ) - { - returnedHistogramMap.insert( std::pair(binsAndFrequencyToCalculate->GetMeasurement( bin, 0 ), binsAndFrequencyToCalculate->GetFrequency( bin, 0 ) ) ); - } - } - - return returnedHistogramMap; - } - - const ImageStatisticsCalculator::HistogramType * - ImageStatisticsCalculator::GetHistogram( unsigned int timeStep, unsigned int label ) const - { - if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) - { - return nullptr; - } - - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - { - if(m_DoIgnorePixelValue) - return m_MaskedImageHistogramVector[timeStep][label]; - - return m_ImageHistogramVector[timeStep][label]; - } + if (IsUpdateRequired(timeStep)) + { + if (m_MaskGenerator.IsNotNull()) + { + m_MaskGenerator->SetTimeStep(timeStep); + m_InternalMask = m_MaskGenerator->GetMask(); + if (m_MaskGenerator->GetReferenceImage().IsNotNull()) + { + m_InternalImageForStatistics = m_MaskGenerator->GetReferenceImage(); + } + else + { + m_InternalImageForStatistics = m_Image; + } + } + else + { + m_InternalImageForStatistics = m_Image; + } - case MASKING_MODE_IMAGE: - return m_MaskedImageHistogramVector[timeStep][label]; + if (m_SecondaryMaskGenerator.IsNotNull()) + { + m_SecondaryMaskGenerator->SetTimeStep(timeStep); + m_SecondaryMask = m_SecondaryMaskGenerator->GetMask(); + } - case MASKING_MODE_PLANARFIGURE: - return m_PlanarFigureHistogramVector[timeStep][label]; - } - } + ImageTimeSelector::Pointer imgTimeSel = ImageTimeSelector::New(); + imgTimeSel->SetInput(m_InternalImageForStatistics); + imgTimeSel->SetTimeNr(timeStep); + imgTimeSel->UpdateLargestPossibleRegion(); + m_ImageTimeSlice = imgTimeSel->GetOutput(); - const ImageStatisticsCalculator::HistogramContainer & - ImageStatisticsCalculator::GetHistogramVector( unsigned int timeStep ) const - { - if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) - { - return m_EmptyHistogramContainer; - } - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - { - if(m_DoIgnorePixelValue) - return m_MaskedImageHistogramVector[timeStep]; + // Calculate statistics with/without mask + if (m_MaskGenerator.IsNull() && m_SecondaryMaskGenerator.IsNull()) + { + // 1) calculate statistics unmasked: + AccessByItk_1(m_ImageTimeSlice, InternalCalculateStatisticsUnmasked, timeStep) - return m_ImageHistogramVector[timeStep]; - } + } + else + { + // 2) calculate statistics masked + AccessByItk_1(m_ImageTimeSlice, InternalCalculateStatisticsMasked, timeStep) + } - case MASKING_MODE_IMAGE: - return m_MaskedImageHistogramVector[timeStep]; - case MASKING_MODE_PLANARFIGURE: - return m_PlanarFigureHistogramVector[timeStep]; - } - } + this->Modified(); + } + m_StatisticsUpdateTimePerTimeStep[timeStep] = m_StatisticsByTimeStep[timeStep][m_StatisticsByTimeStep[timeStep].size()-1]->GetMTime(); - const ImageStatisticsCalculator::Statistics & - ImageStatisticsCalculator::GetStatistics( unsigned int timeStep, unsigned int label ) const - { - if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) - { - return m_EmptyStatistics; - } + for (std::vector::iterator it = m_StatisticsByTimeStep[timeStep].begin(); it != m_StatisticsByTimeStep[timeStep].end(); ++it) + { + StatisticsContainer::Pointer statCont = *it; + if (statCont->GetLabel() == label) + { + return statCont->Clone(); + } + } - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - { - if(m_DoIgnorePixelValue) - return m_MaskedImageStatisticsVector[timeStep][label]; - - return m_ImageStatisticsVector[timeStep][label]; - } - case MASKING_MODE_IMAGE: - return m_MaskedImageStatisticsVector[timeStep][label]; - - case MASKING_MODE_PLANARFIGURE: - return m_PlanarFigureStatisticsVector[timeStep][label]; + // these lines will ony be executed if the requested label could not be found! + MITK_WARN << "Invalid label: " << label << " in time step: " << timeStep; + return StatisticsContainer::New(); } - } - const ImageStatisticsCalculator::StatisticsContainer & - ImageStatisticsCalculator::GetStatisticsVector( unsigned int timeStep ) const - { - if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) + template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsUnmasked( + typename itk::Image< TPixel, VImageDimension >* image, unsigned int timeStep) { - return m_EmptyStatisticsContainer; - } + typedef typename itk::Image< TPixel, VImageDimension > ImageType; + typedef typename itk::ExtendedStatisticsImageFilter ImageStatisticsFilterType; + typedef typename itk::MinMaxImageFilterWithIndex MinMaxFilterType; - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - { - if(m_DoIgnorePixelValue) - return m_MaskedImageStatisticsVector[timeStep]; - - return m_ImageStatisticsVector[timeStep]; - } - case MASKING_MODE_IMAGE: - return m_MaskedImageStatisticsVector[timeStep]; - - case MASKING_MODE_PLANARFIGURE: - return m_PlanarFigureStatisticsVector[timeStep]; - } - } + StatisticsContainer::Pointer statisticsResult = StatisticsContainer::New(); + typename ImageStatisticsFilterType::Pointer statisticsFilter = ImageStatisticsFilterType::New(); + statisticsFilter->SetInput(image); + statisticsFilter->SetCoordinateTolerance(0.001); + statisticsFilter->SetDirectionTolerance(0.001); + // TODO: this is single threaded. Implement our own image filter that does this multi threaded +// typename itk::MinimumMaximumImageCalculator::Pointer imgMinMaxFilter = itk::MinimumMaximumImageCalculator::New(); +// imgMinMaxFilter->SetImage(image); +// imgMinMaxFilter->Compute(); + vnl_vector minIndex, maxIndex; - void ImageStatisticsCalculator::ExtractImageAndMask( unsigned int timeStep ) - { - if ( m_Image.IsNull() ) - { - throw std::runtime_error( "Error: image empty!" ); - } + typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); + minMaxFilter->SetInput(image); + minMaxFilter->UpdateLargestPossibleRegion(); + typename ImageType::PixelType minval = minMaxFilter->GetMin(); + typename ImageType::PixelType maxval = minMaxFilter->GetMax(); - if ( timeStep >= m_Image->GetTimeSteps() ) - { - throw std::runtime_error( "Error: invalid time step!" ); - } + typename ImageType::IndexType tmpMinIndex = minMaxFilter->GetMinIndex(); + typename ImageType::IndexType tmpMaxIndex = minMaxFilter->GetMaxIndex(); - ImageTimeSelector::Pointer imageTimeSelector = ImageTimeSelector::New(); - imageTimeSelector->SetInput( m_Image ); - imageTimeSelector->SetTimeNr( timeStep ); - imageTimeSelector->UpdateLargestPossibleRegion(); - mitk::Image *timeSliceImage = imageTimeSelector->GetOutput(); +// typename ImageType::IndexType tmpMinIndex = imgMinMaxFilter->GetIndexOfMinimum(); +// typename ImageType::IndexType tmpMaxIndex = imgMinMaxFilter->GetIndexOfMaximum(); + minIndex.set_size(tmpMaxIndex.GetIndexDimension()); + maxIndex.set_size(tmpMaxIndex.GetIndexDimension()); - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - { - m_InternalImage = timeSliceImage; - m_InternalImageMask2D = nullptr; - m_InternalImageMask3D = nullptr; - if(m_DoIgnorePixelValue) + for (unsigned int i=0; i < tmpMaxIndex.GetIndexDimension(); i++) { - if( m_InternalImage->GetDimension() == 3 ) - { - if(itk::ImageIOBase::USHORT != timeSliceImage->GetPixelType().GetComponentType()) - CastToItkImage( timeSliceImage, m_InternalImageMask3D ); - else - CastToItkImage( timeSliceImage->Clone(), m_InternalImageMask3D ); - m_InternalImageMask3D->FillBuffer(1); - } - if( m_InternalImage->GetDimension() == 2 ) - { - if(itk::ImageIOBase::USHORT != timeSliceImage->GetPixelType().GetComponentType()) - CastToItkImage( timeSliceImage, m_InternalImageMask2D ); - else - CastToItkImage( timeSliceImage->Clone(), m_InternalImageMask2D ); - m_InternalImageMask2D->FillBuffer(1); - } + minIndex[i] = tmpMinIndex[i]; + maxIndex[i] = tmpMaxIndex[i]; } - break; - } - case MASKING_MODE_IMAGE: - { - if ( m_ImageMask.IsNotNull() && (m_ImageMask->GetReferenceCount() > 1) ) + statisticsResult->SetMinIndex(minIndex); + statisticsResult->SetMaxIndex(maxIndex); + + //convert m_binSize in m_nBins if necessary + unsigned int nBinsForHistogram; + if (m_UseBinSizeOverNBins) { - if ( timeStep >= m_ImageMask->GetTimeSteps() ) - { - // Use the last mask time step in case the current time step is bigger than the total - // number of mask time steps. - // It makes more sense setting this to the last mask time step than to 0. - // For instance if you have a mask with 2 time steps and an image with 5: - // If time step 0 is selected, the mask will use time step 0. - // If time step 1 is selected, the mask will use time step 1. - // If time step 2+ is selected, the mask will use time step 1. - // If you have a mask with only one time step instead, this will always default to 0. - timeStep = m_ImageMask->GetTimeSteps() - 1; - } - - ImageTimeSelector::Pointer maskedImageTimeSelector = ImageTimeSelector::New(); - maskedImageTimeSelector->SetInput( m_ImageMask ); - maskedImageTimeSelector->SetTimeNr( timeStep ); - maskedImageTimeSelector->UpdateLargestPossibleRegion(); - mitk::Image *timeSliceMaskedImage = maskedImageTimeSelector->GetOutput(); - - m_InternalImage = timeSliceImage; - CastToItkImage( timeSliceMaskedImage, m_InternalImageMask3D ); + nBinsForHistogram = std::max(static_cast(std::ceil(maxval - minval)) / m_binSizeForHistogramStatistics, 10.); // do not allow less than 10 bins } else { - throw std::runtime_error( "Error: image mask empty!" ); + nBinsForHistogram = m_nBinsForHistogramStatistics; } - break; - } - case MASKING_MODE_PLANARFIGURE: - { - m_InternalImageMask2D = nullptr; - if ( m_PlanarFigure.IsNull() ) - { - throw std::runtime_error( "Error: planar figure empty!" ); - } - if ( !m_PlanarFigure->IsClosed() ) - { - throw std::runtime_error( "Masking not possible for non-closed figures" ); - } + statisticsFilter->SetHistogramParameters(nBinsForHistogram, minval, maxval); - const BaseGeometry *imageGeometry = timeSliceImage->GetGeometry(); - if ( imageGeometry == nullptr ) + try { - throw std::runtime_error( "Image geometry invalid!" ); + statisticsFilter->Update(); } - - const PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); - if ( planarFigurePlaneGeometry == nullptr ) + catch (const itk::ExceptionObject& e) { - throw std::runtime_error( "Planar-Figure not yet initialized!" ); + mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } - const PlaneGeometry *planarFigureGeometry = - dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); - if ( planarFigureGeometry == nullptr ) + // no mask, therefore just one label = the whole image + m_StatisticsByTimeStep[timeStep].resize(1); + statisticsResult->SetLabel(1); + statisticsResult->SetN(image->GetLargestPossibleRegion().GetNumberOfPixels()); + statisticsResult->SetMean(statisticsFilter->GetMean()); + statisticsResult->SetMin(statisticsFilter->GetMinimum()); + statisticsResult->SetMax(statisticsFilter->GetMaximum()); + statisticsResult->SetVariance(statisticsFilter->GetVariance()); + statisticsResult->SetStd(statisticsFilter->GetSigma()); + statisticsResult->SetSkewness(statisticsFilter->GetSkewness()); + statisticsResult->SetKurtosis(statisticsFilter->GetKurtosis()); + statisticsResult->SetRMS(std::sqrt(std::pow(statisticsFilter->GetMean(), 2.) + statisticsFilter->GetVariance())); // variance = sigma^2 + statisticsResult->SetMPP(statisticsFilter->GetMPP()); + + statisticsResult->SetEntropy(statisticsFilter->GetEntropy()); + statisticsResult->SetMedian(statisticsFilter->GetMedian()); + statisticsResult->SetUniformity(statisticsFilter->GetUniformity()); + statisticsResult->SetUPP(statisticsFilter->GetUPP()); + statisticsResult->SetHistogram(statisticsFilter->GetHistogram()); + + m_StatisticsByTimeStep[timeStep][0] = statisticsResult; + } + + + template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsMasked( + typename itk::Image< TPixel, VImageDimension >* image, + unsigned int timeStep) + { + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef itk::Image< MaskPixelType, VImageDimension > MaskType; + typedef typename MaskType::PixelType LabelPixelType; + typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, MaskType > ImageStatisticsFilterType; + typedef MaskUtilities< TPixel, VImageDimension > MaskUtilType; + typedef typename itk::MinMaxLabelImageFilterWithIndex MinMaxLabelFilterType; + typedef typename ImageType::PixelType InputImgPixelType; + + // workaround: if m_SecondaryMaskGenerator ist not null but m_MaskGenerator is! (this is the case if we request a 'ignore zuero valued pixels' + // mask in the gui but do not define a primary mask) + bool swapMasks = false; + if (m_SecondaryMask.IsNotNull() && m_InternalMask.IsNull()) { - throw std::runtime_error( "Non-planar planar figures not supported!" ); + m_InternalMask = m_SecondaryMask; + m_SecondaryMask = nullptr; + swapMasks = true; } - // Find principal direction of PlanarFigure in input image - unsigned int axis; - if ( !this->GetPrincipalAxis( imageGeometry, - planarFigureGeometry->GetNormal(), axis ) ) - { - throw std::runtime_error( "Non-aligned planar figures not supported!" ); + // maskImage has to have the same dimension as image + typename MaskType::Pointer maskImage = MaskType::New(); + try { + // try to access the pixel values directly (no copying or casting). Only works if mask pixels are of pixelType unsigned short + maskImage = ImageToItkImage< MaskPixelType, VImageDimension >(m_InternalMask); } - m_PlanarFigureAxis = axis; + catch (itk::ExceptionObject & e) - // Find slice number corresponding to PlanarFigure in input image - MaskImage3DType::IndexType index; - imageGeometry->WorldToIndex( planarFigureGeometry->GetOrigin(), index ); - - unsigned int slice = index[axis]; - m_PlanarFigureSlice = slice; - - - // Extract slice with given position and direction from image - unsigned int dimension = timeSliceImage->GetDimension(); - - if (dimension != 2) { - ExtractImageFilter::Pointer imageExtractor = ExtractImageFilter::New(); - imageExtractor->SetInput( timeSliceImage ); - imageExtractor->SetSliceDimension( axis ); - imageExtractor->SetSliceIndex( slice ); - imageExtractor->Update(); - m_InternalImage = imageExtractor->GetOutput(); + // if the pixel type of the mask is not short, then we have to make a copy of m_InternalMask (and cast the values) + CastToItkImage(m_InternalMask, maskImage); } - else + + // if we have a secondary mask (say a ignoreZeroPixelMask) we need to combine the masks (corresponds to AND) + if (m_SecondaryMask.IsNotNull()) { - m_InternalImage = timeSliceImage; + // dirty workaround for a bug when pf mask + any other mask is used in conjunction. We need a proper fix for this (Fabian Isensee is responsible and probably working on it!) + if (m_InternalMask->GetDimension() == 2 && (m_SecondaryMask->GetDimension() == 3 || m_SecondaryMask->GetDimension() == 4)) + { + mitk::Image::Pointer old_img = m_SecondaryMaskGenerator->GetReferenceImage(); + m_SecondaryMaskGenerator->SetInputImage(m_MaskGenerator->GetReferenceImage()); + m_SecondaryMask = m_SecondaryMaskGenerator->GetMask(); + m_SecondaryMaskGenerator->SetInputImage(old_img); + } + typename MaskType::Pointer secondaryMaskImage = MaskType::New(); + secondaryMaskImage = ImageToItkImage< MaskPixelType, VImageDimension >(m_SecondaryMask); + + // secondary mask should be a ignore zero value pixel mask derived from image. it has to be cropped to the mask region (which may be planar or simply smaller) + typename MaskUtilities::Pointer secondaryMaskMaskUtil = MaskUtilities::New(); + secondaryMaskMaskUtil->SetImage(secondaryMaskImage.GetPointer()); + secondaryMaskMaskUtil->SetMask(maskImage.GetPointer()); + typename MaskType::Pointer adaptedSecondaryMaskImage = secondaryMaskMaskUtil->ExtractMaskImageRegion(); + + typename itk::MaskImageFilter2::Pointer maskFilter = itk::MaskImageFilter2::New(); + maskFilter->SetInput1(maskImage); + maskFilter->SetInput2(adaptedSecondaryMaskImage); + maskFilter->SetMaskingValue(1); // all pixels of maskImage where secondaryMaskImage==1 will be kept, all the others are set to 0 + maskFilter->UpdateLargestPossibleRegion(); + maskImage = maskFilter->GetOutput(); } - // Compute mask from PlanarFigure - AccessFixedDimensionByItk_1( - m_InternalImage, - InternalCalculateMaskFromPlanarFigure, - 2, axis ); - } - } - - if(m_DoIgnorePixelValue) - { - if ( m_InternalImage->GetDimension() == 3 ) - { - AccessFixedDimensionByItk_1( - m_InternalImage, - InternalMaskIgnoredPixels, - 3, - m_InternalImageMask3D.GetPointer() ); - } - else if ( m_InternalImage->GetDimension() == 2 ) - { - AccessFixedDimensionByItk_1( - m_InternalImage, - InternalMaskIgnoredPixels, - 2, - m_InternalImageMask2D.GetPointer() ); - } - } - } - - - bool ImageStatisticsCalculator::GetPrincipalAxis( - const BaseGeometry *geometry, Vector3D vector, - unsigned int &axis ) - { - vector.Normalize(); - for ( unsigned int i = 0; i < 3; ++i ) - { - Vector3D axisVector = geometry->GetAxisVector( i ); - axisVector.Normalize(); - - if ( fabs( fabs( axisVector * vector ) - 1.0) < mitk::eps ) - { - axis = i; - return true; - } - } - - return false; - } - - - unsigned int ImageStatisticsCalculator::calcNumberOfBins(mitk::ScalarType min, mitk::ScalarType max) - { - return std::ceil( ( (max - min ) / m_HistogramBinSize) ); - } - + typename MaskUtilType::Pointer maskUtil = MaskUtilType::New(); + maskUtil->SetImage(image); + maskUtil->SetMask(maskImage.GetPointer()); - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::InternalCalculateStatisticsUnmasked( - const itk::Image< TPixel, VImageDimension > *image, - StatisticsContainer *statisticsContainer, - HistogramContainer* histogramContainer ) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - typedef typename ImageType::IndexType IndexType; + // if mask is smaller than image, extract the image region where the mask is + typename ImageType::Pointer adaptedImage = ImageType::New(); - typedef itk::Statistics::ScalarImageToHistogramGenerator< ImageType > - HistogramGeneratorType; + adaptedImage = maskUtil->ExtractMaskImageRegion(); // this also checks mask sanity - statisticsContainer->clear(); - histogramContainer->clear(); + // find min, max, minindex and maxindex + typename MinMaxLabelFilterType::Pointer minMaxFilter = MinMaxLabelFilterType::New(); + minMaxFilter->SetInput(adaptedImage); + minMaxFilter->SetLabelInput(maskImage); + minMaxFilter->UpdateLargestPossibleRegion(); - // Progress listening... - typedef itk::SimpleMemberCommand< ImageStatisticsCalculator > ITKCommandType; - ITKCommandType::Pointer progressListener; - progressListener = ITKCommandType::New(); - progressListener->SetCallbackFunction( this, - &ImageStatisticsCalculator::UnmaskedStatisticsProgressUpdate ); + // set histogram parameters for each label individually (min/max may be different for each label) + typedef typename std::map MapType; + typedef typename std::pair PairType; + std::vector relevantLabels = minMaxFilter->GetRelevantLabels(); + MapType minVals; + MapType maxVals; + std::map nBins; - // Issue 100 artificial progress events since ScalarIMageToHistogramGenerator - // does not (yet?) support progress reporting - this->InvokeEvent( itk::StartEvent() ); - for ( unsigned int i = 0; i < 100; ++i ) - { - this->UnmaskedStatisticsProgressUpdate(); - } - - // Calculate statistics (separate filter) - typedef itk::ExtendedStatisticsImageFilter< ImageType > StatisticsFilterType; - typename StatisticsFilterType::Pointer statisticsFilter = StatisticsFilterType::New(); - statisticsFilter->SetInput( image ); - statisticsFilter->SetBinSize( 100 ); - statisticsFilter->SetCoordinateTolerance( 0.001 ); - statisticsFilter->SetDirectionTolerance( 0.001 ); - - unsigned long observerTag = statisticsFilter->AddObserver( itk::ProgressEvent(), progressListener ); - try - { - statisticsFilter->Update(); - } - catch (const itk::ExceptionObject& e) - { - mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - catch( const std::exception& e ) - { - //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } + for (LabelPixelType label:relevantLabels) + { + minVals.insert(PairType(label, minMaxFilter->GetMin(label))); + maxVals.insert(PairType(label, minMaxFilter->GetMax(label))); - statisticsFilter->RemoveObserver( observerTag ); - this->InvokeEvent( itk::EndEvent() ); - - // Calculate minimum and maximum - typedef itk::MinimumMaximumImageCalculator< ImageType > MinMaxFilterType; - typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); - minMaxFilter->SetImage( image ); - unsigned long observerTag2 = minMaxFilter->AddObserver( itk::ProgressEvent(), progressListener ); - minMaxFilter->Compute(); - minMaxFilter->RemoveObserver( observerTag2 ); - this->InvokeEvent( itk::EndEvent() ); - - Statistics statistics; - statistics.Reset(); - statistics.SetLabel(1); - statistics.SetN(image->GetBufferedRegion().GetNumberOfPixels()); - statistics.SetMin(statisticsFilter->GetMinimum()); - statistics.SetMax(statisticsFilter->GetMaximum()); - statistics.SetMean(statisticsFilter->GetMean()); - statistics.SetMedian(statisticsFilter->GetMedian()); - statistics.SetVariance(statisticsFilter->GetVariance()); - statistics.SetSkewness(statisticsFilter->GetSkewness()); - statistics.SetKurtosis(statisticsFilter->GetKurtosis()); - statistics.SetUniformity( statisticsFilter->GetUniformity()); - statistics.SetEntropy( statisticsFilter->GetEntropy()); - statistics.SetUPP( statisticsFilter->GetUPP()); - statistics.SetMPP( statisticsFilter->GetMPP()); - statistics.SetSigma(statisticsFilter->GetSigma()); - statistics.SetRMS(sqrt( statistics.GetMean() * statistics.GetMean() + statistics.GetSigma() * statistics.GetSigma() )); - - statistics.GetMinIndex().set_size(image->GetImageDimension()); - statistics.GetMaxIndex().set_size(image->GetImageDimension()); - - vnl_vector maxIndex; - vnl_vector minIndex; - - maxIndex.set_size( VImageDimension ); - minIndex.set_size( VImageDimension ); - - typename MinMaxFilterType::IndexType tempMaxIndex = minMaxFilter->GetIndexOfMaximum(); - typename MinMaxFilterType::IndexType tempMinIndex = minMaxFilter->GetIndexOfMinimum(); - - for (unsigned int i=0; i (std::ceil(minMaxFilter->GetMax(label) - minMaxFilter->GetMin(label))) / m_binSizeForHistogramStatistics, 10.); // do not allow less than 10 bins + } + else + { + nBinsForHistogram = m_nBinsForHistogramStatistics; + } - statistics.SetMaxIndex(maxIndex); - statistics.SetMinIndex(minIndex); + nBins.insert(typename std::pair(label, nBinsForHistogram)); + } - if( IsHotspotCalculated() && VImageDimension == 3 ) - { - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - typename MaskImageType::Pointer nullMask; - bool isHotspotDefined(false); - Statistics hotspotStatistics = this->CalculateHotspotStatistics(image, nullMask.GetPointer(), m_HotspotRadiusInMM, isHotspotDefined, 0 ); - if (isHotspotDefined) - { - statistics.SetHasHotspotStatistics(true); - statistics.GetHotspotStatistics() = hotspotStatistics; - } - else - { - statistics.SetHasHotspotStatistics(false); - } - - if(statistics.GetHotspotStatistics().HasHotspotStatistics() ) - { - MITK_DEBUG << "Hotspot statistics available"; - statistics.SetHotspotIndex(hotspotStatistics.GetHotspotIndex()); - } - else - { - MITK_ERROR << "No hotspot statistics available!"; - } - } + typename ImageStatisticsFilterType::Pointer imageStatisticsFilter = ImageStatisticsFilterType::New(); + imageStatisticsFilter->SetDirectionTolerance(0.001); + imageStatisticsFilter->SetCoordinateTolerance(0.001); + imageStatisticsFilter->SetInput(adaptedImage); + imageStatisticsFilter->SetLabelInput(maskImage); + imageStatisticsFilter->SetHistogramParametersForLabels(nBins, minVals, maxVals); + imageStatisticsFilter->Update(); - statisticsContainer->push_back( statistics ); + std::list labels = imageStatisticsFilter->GetRelevantLabels(); + std::list::iterator it = labels.begin(); + m_StatisticsByTimeStep[timeStep].resize(0); - // Calculate histogram - // calculate bin size or number of bins - unsigned int numberOfBins = 200; // default number of bins - if (m_UseDefaultBinSize) - { - m_HistogramBinSize = std::ceil( (statistics.GetMax() - statistics.GetMin() + 1)/numberOfBins ); - } - else - { - numberOfBins = calcNumberOfBins(statistics.GetMin(), statistics.GetMax()); - } - typename HistogramGeneratorType::Pointer histogramGenerator = HistogramGeneratorType::New(); - histogramGenerator->SetInput( image ); - histogramGenerator->SetMarginalScale( 100 ); - histogramGenerator->SetNumberOfBins( numberOfBins ); - histogramGenerator->SetHistogramMin( statistics.GetMin() ); - histogramGenerator->SetHistogramMax( statistics.GetMax() ); - histogramGenerator->Compute(); - histogramContainer->push_back( histogramGenerator->GetOutput() ); - } - - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::InternalMaskIgnoredPixels( - const itk::Image< TPixel, VImageDimension > *image, - itk::Image< unsigned short, VImageDimension > *maskImage ) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - - itk::ImageRegionIterator - itmask(maskImage, maskImage->GetLargestPossibleRegion()); - itk::ImageRegionConstIterator - itimage(image, image->GetLargestPossibleRegion()); - - itmask.GoToBegin(); - itimage.GoToBegin(); - - while( !itmask.IsAtEnd() ) - { - if(m_IgnorePixelValue == itimage.Get()) - { - itmask.Set(0); - } + while(it != labels.end()) + { + StatisticsContainer::Pointer statisticsResult = StatisticsContainer::New(); + + // find min, max, minindex and maxindex + // make sure to only look in the masked region, use a masker for this + + vnl_vector minIndex, maxIndex; + mitk::Point3D worldCoordinateMin; + mitk::Point3D worldCoordinateMax; + mitk::Point3D indexCoordinateMin; + mitk::Point3D indexCoordinateMax; + m_InternalImageForStatistics->GetGeometry()->IndexToWorld(minMaxFilter->GetMinIndex(*it), worldCoordinateMin); + m_InternalImageForStatistics->GetGeometry()->IndexToWorld(minMaxFilter->GetMaxIndex(*it), worldCoordinateMax); + m_Image->GetGeometry()->WorldToIndex(worldCoordinateMin, indexCoordinateMin); + m_Image->GetGeometry()->WorldToIndex(worldCoordinateMax, indexCoordinateMax); + + //typename ImageType::IndexType tmpMinIndex = minMaxFilter->GetMinIndex(*it); + //typename ImageType::IndexType tmpMaxIndex = minMaxFilter->GetMaxIndex(*it); + + //minIndex.set_size(tmpMaxIndex.GetIndexDimension()); + //maxIndex.set_size(tmpMaxIndex.GetIndexDimension()); + minIndex.set_size(3); + maxIndex.set_size(3); + + //for (unsigned int i=0; i < tmpMaxIndex.GetIndexDimension(); i++) + for (unsigned int i=0; i < 3; i++) + { + //minIndex[i] = tmpMinIndex[i] + (maskImage->GetOrigin()[i] - image->GetOrigin()[i]) / (double) maskImage->GetSpacing()[i]; + //maxIndex[i] = tmpMaxIndex[i] + (maskImage->GetOrigin()[i] - image->GetOrigin()[i]) / (double) maskImage->GetSpacing()[i]; + minIndex[i] = indexCoordinateMin[i]; + maxIndex[i] = indexCoordinateMax[i]; + } - ++itmask; - ++itimage; - } - } - - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::InternalCalculateStatisticsMasked( - const itk::Image< TPixel, VImageDimension > *image, - itk::Image< unsigned short, VImageDimension > *maskImage, - StatisticsContainer* statisticsContainer, - HistogramContainer* histogramContainer ) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - typedef typename ImageType::IndexType IndexType; - typedef typename ImageType::PointType PointType; - typedef typename ImageType::SpacingType SpacingType; - typedef typename ImageType::Pointer ImagePointer; - typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, MaskImageType > LabelStatisticsFilterType; - typedef itk::ChangeInformationImageFilter< MaskImageType > ChangeInformationFilterType; - typedef itk::ExtractImageFilter< ImageType, ImageType > ExtractImageFilterType; - - statisticsContainer->clear(); - histogramContainer->clear(); - - // Make sure that mask is set - if ( maskImage == nullptr ) - { - itkExceptionMacro( << "Mask image needs to be set!" ); - } + statisticsResult->SetMinIndex(minIndex); + statisticsResult->SetMaxIndex(maxIndex); + + // just debug + TPixel min_Filter = minMaxFilter->GetMin(*it); + TPixel max_Filter = minMaxFilter->GetMax(*it); + TPixel min_Itk = imageStatisticsFilter->GetMinimum(*it); + TPixel max_Itk = imageStatisticsFilter->GetMaximum(*it); + + assert(abs(minMaxFilter->GetMax(*it) - imageStatisticsFilter->GetMaximum(*it)) < mitk::eps); + assert(abs(minMaxFilter->GetMin(*it) - imageStatisticsFilter->GetMinimum(*it)) < mitk::eps); + + + statisticsResult->SetN(imageStatisticsFilter->GetSum(*it) / (double) imageStatisticsFilter->GetMean(*it)); + statisticsResult->SetMean(imageStatisticsFilter->GetMean(*it)); + statisticsResult->SetMin(imageStatisticsFilter->GetMinimum(*it)); + statisticsResult->SetMax(imageStatisticsFilter->GetMaximum(*it)); + statisticsResult->SetVariance(imageStatisticsFilter->GetVariance(*it)); + statisticsResult->SetStd(imageStatisticsFilter->GetSigma(*it)); + statisticsResult->SetSkewness(imageStatisticsFilter->GetSkewness(*it)); + statisticsResult->SetKurtosis(imageStatisticsFilter->GetKurtosis(*it)); + statisticsResult->SetRMS(std::sqrt(std::pow(imageStatisticsFilter->GetMean(*it), 2.) + imageStatisticsFilter->GetVariance(*it))); // variance = sigma^2 + statisticsResult->SetMPP(imageStatisticsFilter->GetMPP(*it)); + statisticsResult->SetLabel(*it); + + statisticsResult->SetEntropy(imageStatisticsFilter->GetEntropy(*it)); + statisticsResult->SetMedian(imageStatisticsFilter->GetMedian(*it)); + statisticsResult->SetUniformity(imageStatisticsFilter->GetUniformity(*it)); + statisticsResult->SetUPP(imageStatisticsFilter->GetUPP(*it)); + statisticsResult->SetHistogram(imageStatisticsFilter->GetHistogram(*it)); + + m_StatisticsByTimeStep[timeStep].push_back(statisticsResult); + ++it; + } - // Make sure that spacing of mask and image are the same - //SpacingType imageSpacing = image->GetSpacing(); - //SpacingType maskSpacing = maskImage->GetSpacing(); - //PointType zeroPoint; zeroPoint.Fill( 0.0 ); - //if ( (zeroPoint + imageSpacing).SquaredEuclideanDistanceTo( (zeroPoint + maskSpacing) ) > mitk::eps ) - //{ - // itkExceptionMacro( << "Mask needs to have same spacing as image! (Image spacing: " << imageSpacing << "; Mask spacing: " << maskSpacing << ")" ); - //} - // Make sure that orientation of mask and image are the same - typedef typename ImageType::DirectionType DirectionType; - DirectionType imageDirection = image->GetDirection(); - DirectionType maskDirection = maskImage->GetDirection(); - for( int i = 0; i < imageDirection.ColumnDimensions; ++i ) - { - for( int j = 0; j < imageDirection.ColumnDimensions; ++j ) - { - double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; - if ( fabs( differenceDirection ) > mitk::eps ) + // swap maskGenerators back + if (swapMasks) { - double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; - if ( fabs( differenceDirection ) > 0.001 /*mitk::eps*/ ) // TODO: temp fix (bug 17121) - { - itkExceptionMacro( << "Mask needs to have same direction as image! (Image direction: " << imageDirection << "; Mask direction: " << maskDirection << ")" ); - } + m_SecondaryMask = m_InternalMask; + m_InternalMask = nullptr; } - } - } - // Make sure that the voxels of mask and image are correctly "aligned", i.e., voxel boundaries are the same in both images - PointType imageOrigin = image->GetOrigin(); - PointType maskOrigin = maskImage->GetOrigin(); - long offset[ImageType::ImageDimension]; - - typedef itk::ContinuousIndex ContinousIndexType; - ContinousIndexType maskOriginContinousIndex, imageOriginContinousIndex; - - image->TransformPhysicalPointToContinuousIndex(maskOrigin, maskOriginContinousIndex); - image->TransformPhysicalPointToContinuousIndex(imageOrigin, imageOriginContinousIndex); - - for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) - { - double misalignment = maskOriginContinousIndex[i] - floor( maskOriginContinousIndex[i] + 0.5 ); - if ( fabs( misalignment ) > mitk::eps ) - { - itkWarningMacro( << "Pixels/voxels of mask and image are not sufficiently aligned! (Misalignment: " << misalignment << ")" ); - } - - double indexCoordDistance = maskOriginContinousIndex[i] - imageOriginContinousIndex[i]; - offset[i] = int( indexCoordDistance + image->GetBufferedRegion().GetIndex()[i] + 0.5 ); - } - - // Adapt the origin and region (index/size) of the mask so that the origin of both are the same - typename ChangeInformationFilterType::Pointer adaptMaskFilter; - adaptMaskFilter = ChangeInformationFilterType::New(); - adaptMaskFilter->ChangeOriginOn(); - adaptMaskFilter->ChangeRegionOn(); - adaptMaskFilter->SetInput( maskImage ); - adaptMaskFilter->SetOutputOrigin( image->GetOrigin() ); - adaptMaskFilter->SetOutputOffset( offset ); - adaptMaskFilter->SetCoordinateTolerance( 0.001 ); - adaptMaskFilter->SetDirectionTolerance( 0.001 ); - - - typename MaskImageType::Pointer adaptedMaskImage; - try - { - adaptMaskFilter->Update(); - adaptedMaskImage = adaptMaskFilter->GetOutput(); - } - catch( const itk::ExceptionObject &e) - { - mitkThrow() << "Attempt to adapt shifted origin of the mask image failed due to ITK Exception: \n" << e.what(); - } - catch( const std::exception& e ) - { - //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - - - // Make sure that mask region is contained within image region - if ( adaptedMaskImage.IsNotNull() && - !image->GetLargestPossibleRegion().IsInside( adaptedMaskImage->GetLargestPossibleRegion() ) ) - { - itkWarningMacro( << "Mask region needs to be inside of image region! (Image region: " - << image->GetLargestPossibleRegion() << "; Mask region: " << adaptedMaskImage->GetLargestPossibleRegion() << ")" ); - } - - - // If mask region is smaller than image region, extract the sub-sampled region from the original image - typename ImageType::SizeType imageSize = image->GetBufferedRegion().GetSize(); - typename ImageType::SizeType maskSize = maskImage->GetBufferedRegion().GetSize(); - bool maskSmallerImage = false; - for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) - { - if ( maskSize[i] < imageSize[i] ) - { - maskSmallerImage = true; - } - } - - typename ImageType::ConstPointer adaptedImage; - if ( maskSmallerImage ) - { - typename ExtractImageFilterType::Pointer extractImageFilter = ExtractImageFilterType::New(); - extractImageFilter->SetInput( image ); - extractImageFilter->SetExtractionRegion( adaptedMaskImage->GetBufferedRegion() ); - extractImageFilter->SetCoordinateTolerance( 0.001 ); - extractImageFilter->SetDirectionTolerance( 0.001 ); - extractImageFilter->Update(); - adaptedImage = extractImageFilter->GetOutput(); } - else - { - adaptedImage = image; - } - - // Initialize Filter - typedef itk::StatisticsImageFilter< ImageType > StatisticsFilterType; - typename StatisticsFilterType::Pointer statisticsFilter = StatisticsFilterType::New(); - statisticsFilter->SetInput( adaptedImage ); - try + bool ImageStatisticsCalculator::IsUpdateRequired(unsigned int timeStep) const { - statisticsFilter->Update(); - } - catch( const itk::ExceptionObject& e) - { - mitkThrow() << "Image statistics initialization computation failed with ITK Exception: \n " << e.what(); - } - catch( const std::exception& e ) - { - //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - - // Calculate bin size or number of bins - unsigned int numberOfBins = 200; // default number of bins - double maximum = 0.0; - double minimum = 0.0; + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long inputImageTimeStamp = m_Image->GetMTime(); + unsigned long statisticsTimeStamp = m_StatisticsUpdateTimePerTimeStep[timeStep]; - if (m_UseBinSizeBasedOnVOIRegion) - { - maximum = statisticsFilter->GetMaximum(); - minimum = statisticsFilter->GetMinimum(); - - if (m_UseDefaultBinSize) - { - m_HistogramBinSize = std::ceil( static_cast((statisticsFilter->GetMaximum() - statisticsFilter->GetMinimum() + 1)/numberOfBins) ); - } - else - { - numberOfBins = calcNumberOfBins(statisticsFilter->GetMinimum(), statisticsFilter->GetMaximum()); - } - } - else - { - double sig = 0.0; - int counter = 0; - - //Find the min and max values for the Roi to set the range for the histogram - GetMinAndMaxValue( minimum, maximum, counter, sig, image, maskImage); - numberOfBins = maximum - minimum; - if(maximum - minimum <= 10) - { - numberOfBins = 100; - } - } - - - typename LabelStatisticsFilterType::Pointer labelStatisticsFilter = LabelStatisticsFilterType::New(); - labelStatisticsFilter->SetInput( adaptedImage ); - labelStatisticsFilter->SetLabelInput( adaptedMaskImage ); - labelStatisticsFilter->SetCoordinateTolerance( 0.001 ); - labelStatisticsFilter->SetDirectionTolerance( 0.001 ); - labelStatisticsFilter->UseHistogramsOn(); - labelStatisticsFilter->SetHistogramParameters( numberOfBins, floor(minimum), ceil(maximum) ); //statisticsFilter->GetMinimum() statisticsFilter->GetMaximum() - - // Add progress listening - typedef itk::SimpleMemberCommand< ImageStatisticsCalculator > ITKCommandType; - ITKCommandType::Pointer progressListener; - progressListener = ITKCommandType::New(); - progressListener->SetCallbackFunction( this, - &ImageStatisticsCalculator::MaskedStatisticsProgressUpdate ); - unsigned long observerTag = labelStatisticsFilter->AddObserver( - itk::ProgressEvent(), progressListener ); - - // Execute filter - this->InvokeEvent( itk::StartEvent() ); - - // Make sure that only the mask region is considered (otherwise, if the mask region is smaller - // than the image region, the Update() would result in an exception). - labelStatisticsFilter->GetOutput()->SetRequestedRegion( adaptedMaskImage->GetLargestPossibleRegion() ); - - // Execute the filter - try - { - labelStatisticsFilter->Update(); - } - catch( const itk::ExceptionObject& e) - { - mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - catch( const std::exception& e ) - { - //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - - this->InvokeEvent( itk::EndEvent() ); - - if( observerTag ) - labelStatisticsFilter->RemoveObserver( observerTag ); - - // Find all relevant labels of mask (other than 0) - std::list< int > relevantLabels = labelStatisticsFilter->GetRelevantLabels(); - unsigned int i; - - if ( labelStatisticsFilter->GetMaskingNonEmpty() ) - { - std::list< int >::iterator it; - for ( it = relevantLabels.begin(), i = 0; - it != relevantLabels.end(); - ++it, ++i ) - { - Statistics statistics; // restore previous code - labelStatisticsFilter->GetHistogram(*it) ; - histogramContainer->push_back( HistogramType::ConstPointer( labelStatisticsFilter->GetHistogram( (*it) ) ) ); - - statistics.SetLabel (*it); - statistics.SetN(labelStatisticsFilter->GetCount( *it )); - statistics.SetMin(labelStatisticsFilter->GetMinimum( *it )); - statistics.SetMax(labelStatisticsFilter->GetMaximum( *it )); - statistics.SetMean(labelStatisticsFilter->GetMean( *it )); - statistics.SetMedian(labelStatisticsFilter->GetMedian( *it)); - statistics.SetMedian(labelStatisticsFilter->GetMedian( *it )); - statistics.SetVariance(labelStatisticsFilter->GetVariance( *it )); - statistics.SetSigma(labelStatisticsFilter->GetSigma( *it )); - statistics.SetSkewness(labelStatisticsFilter->GetSkewness( *it )); - statistics.SetKurtosis(labelStatisticsFilter->GetKurtosis( *it )); - statistics.SetUniformity( labelStatisticsFilter->GetUniformity( *it )); - statistics.SetEntropy( labelStatisticsFilter->GetEntropy( *it )); - statistics.SetUPP( labelStatisticsFilter->GetUPP( *it)); - statistics.SetMPP( labelStatisticsFilter->GetMPP( *it)); - statistics.SetRMS(sqrt( statistics.GetMean() * statistics.GetMean() - + statistics.GetSigma() * statistics.GetSigma() )); - - // restrict image to mask area for min/max index calculation - typedef itk::MaskImageFilter< ImageType, MaskImageType, ImageType > MaskImageFilterType; - typename MaskImageFilterType::Pointer masker = MaskImageFilterType::New(); - bool isMinAndMaxSameValue = (statistics.GetMin() == statistics.GetMax()); - // bug 17962: following is a workaround for the case when min and max are the same, we can probably find a nicer way here - double outsideValue = (isMinAndMaxSameValue ? (statistics.GetMax()/2) : (statistics.GetMin()+statistics.GetMax())/2); - masker->SetOutsideValue( outsideValue ); - masker->SetInput1(adaptedImage); - masker->SetInput2(adaptedMaskImage); - masker->SetCoordinateTolerance( 0.001 ); - masker->SetDirectionTolerance( 0.001 ); - masker->Update(); - // get index of minimum and maximum - typedef itk::MinimumMaximumImageCalculator< ImageType > MinMaxFilterType; - typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); - minMaxFilter->SetImage( masker->GetOutput() ); - unsigned long observerTag2 = minMaxFilter->AddObserver( itk::ProgressEvent(), progressListener ); - minMaxFilter->Compute(); - minMaxFilter->RemoveObserver( observerTag2 ); - this->InvokeEvent( itk::EndEvent() ); - - typename MinMaxFilterType::IndexType tempMaxIndex = minMaxFilter->GetIndexOfMaximum(); - // bug 17962: following is a workaround for the case when min and max are the same, we can probably find a nicer way here - typename MinMaxFilterType::IndexType tempMinIndex = - (isMinAndMaxSameValue ? minMaxFilter->GetIndexOfMaximum() : minMaxFilter->GetIndexOfMinimum()); - - // FIX BUG 14644 - //If a PlanarFigure is used for segmentation the - //adaptedImage is a single slice (2D). Adding the - // 3. dimension. - - // FIX Bug 19625 pt. 1 - // m_Image will yield 4 coordinates if it has 4 Dimensions, the min max value is however only searched for in one timeSliceImage (3D) - // the 3D min/max coordinates are then set and the 4th coordinate is blank, causing random numbers to appear in the GUI - // (Fix = replace m_Image with VImageDimension (Dimension of image)) - - vnl_vector maxIndex; - vnl_vector minIndex; - unsigned int imageDimension = m_Image->GetDimension(); - unsigned int maxDimensionsToDisplay = 3; // we do not want to display the time step as part of the coordinates - maxIndex.set_size(std::min(imageDimension, maxDimensionsToDisplay)); - minIndex.set_size(std::min(imageDimension, maxDimensionsToDisplay)); - - if (m_MaskingMode == MASKING_MODE_PLANARFIGURE && imageDimension == 3) - { - maxIndex[m_PlanarFigureCoordinate0] = tempMaxIndex[0]; - maxIndex[m_PlanarFigureCoordinate1] = tempMaxIndex[1]; - maxIndex[m_PlanarFigureAxis] = m_PlanarFigureSlice; - - minIndex[m_PlanarFigureCoordinate0] = tempMinIndex[0] ; - minIndex[m_PlanarFigureCoordinate1] = tempMinIndex[1]; - minIndex[m_PlanarFigureAxis] = m_PlanarFigureSlice; - } else + if (thisClassTimeStamp > statisticsTimeStamp) // inputs have changed { - for (unsigned int i = 0; i statisticsTimeStamp) // image has changed { - bool isDefined(false); - Statistics hotspotStatistics = CalculateHotspotStatistics(adaptedImage.GetPointer(), adaptedMaskImage.GetPointer(),GetHotspotRadiusInMM(), isDefined, *it); - statistics.GetHotspotStatistics() = hotspotStatistics; - if(statistics.GetHotspotStatistics().HasHotspotStatistics()) - { - MITK_DEBUG << "Hotspot statistics available"; - statistics.SetHotspotIndex( hotspotStatistics.GetHotspotIndex() ); - } - else - { - MITK_ERROR << "No hotspot statistics available!"; - } + return true; } - statisticsContainer->push_back( statistics ); - } - } - else - { - histogramContainer->push_back( HistogramType::ConstPointer( m_EmptyHistogram ) ); - statisticsContainer->push_back( Statistics() ); - } - } - - - template - ImageStatisticsCalculator::ImageExtrema - ImageStatisticsCalculator::CalculateExtremaWorld( - const itk::Image *inputImage, - itk::Image *maskImage, - double neccessaryDistanceToImageBorderInMM, - unsigned int label) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - - typedef itk::ImageRegionConstIteratorWithIndex MaskImageIteratorType; - typedef itk::ImageRegionConstIteratorWithIndex InputImageIndexIteratorType; - - typename ImageType::SpacingType spacing = inputImage->GetSpacing(); - - ImageExtrema minMax; - minMax.Defined = false; - minMax.MaxIndex.set_size(VImageDimension); - minMax.MaxIndex.set_size(VImageDimension); - - typename ImageType::RegionType allowedExtremaRegion = inputImage->GetLargestPossibleRegion(); - bool keepDistanceToImageBorders( neccessaryDistanceToImageBorderInMM > 0 ); - if (keepDistanceToImageBorders) - { - long distanceInPixels[VImageDimension]; - for(unsigned short dimension = 0; dimension < VImageDimension; ++dimension) - { - // To confirm that the whole hotspot is inside the image we have to keep a specific distance to the image-borders, which is as long as - // the radius. To get the amount of indices we divide the radius by spacing and add 0.5 because voxels are center based: - // For example with a radius of 2.2 and a spacing of 1 two indices are enough because 2.2 / 1 + 0.5 = 2.7 => 2. - // But with a radius of 2.7 we need 3 indices because 2.7 / 1 + 0.5 = 3.2 => 3 - distanceInPixels[dimension] = int( neccessaryDistanceToImageBorderInMM / spacing[dimension] + 0.5); - } - - allowedExtremaRegion.ShrinkByRadius(distanceInPixels); - } - - InputImageIndexIteratorType imageIndexIt(inputImage, allowedExtremaRegion); - - float maxValue = itk::NumericTraits::min(); - float minValue = itk::NumericTraits::max(); - - typename ImageType::IndexType maxIndex; - typename ImageType::IndexType minIndex; - - for(unsigned short i = 0; i < VImageDimension; ++i) - { - maxIndex[i] = 0; - minIndex[i] = 0; - } - - if (maskImage != nullptr) - { - MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); - typename ImageType::IndexType imageIndex; - typename ImageType::PointType worldPosition; - typename ImageType::IndexType maskIndex; - - for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) - { - imageIndex = maskIndex = maskIt.GetIndex(); - - if(maskIt.Get() == label) + if (m_MaskGenerator.IsNotNull()) { - if( allowedExtremaRegion.IsInside(imageIndex) ) - { - imageIndexIt.SetIndex( imageIndex ); - double value = imageIndexIt.Get(); - minMax.Defined = true; - - //Calculate minimum, maximum and corresponding index-values - if( value > maxValue ) + unsigned long maskGeneratorTimeStamp = m_MaskGenerator->GetMTime(); + if (maskGeneratorTimeStamp > statisticsTimeStamp) // there is a mask generator and it has changed { - maxIndex = imageIndexIt.GetIndex(); - maxValue = value; + return true; } + } - if(value < minValue ) + if (m_SecondaryMaskGenerator.IsNotNull()) + { + unsigned long maskGeneratorTimeStamp = m_SecondaryMaskGenerator->GetMTime(); + if (maskGeneratorTimeStamp > statisticsTimeStamp) // there is a secondary mask generator and it has changed { - minIndex = imageIndexIt.GetIndex(); - minValue = value; + return true; } - } } - } + + return false; } - else - { - for(imageIndexIt.GoToBegin(); !imageIndexIt.IsAtEnd(); ++imageIndexIt) - { - double value = imageIndexIt.Get(); - minMax.Defined = true; - //Calculate minimum, maximum and corresponding index-values - if( value > maxValue ) + + ImageStatisticsCalculator::StatisticsContainer::StatisticsContainer(): + m_N(nan("")), + m_Mean(nan("")), + m_Min(nan("")), + m_Max(nan("")), + m_Std(nan("")), + m_Variance(nan("")), + m_Skewness(nan("")), + m_Kurtosis(nan("")), + m_RMS(nan("")), + m_MPP(nan("")), + m_Median(nan("")), + m_Uniformity(nan("")), + m_UPP(nan("")), + m_Entropy(nan("")) + { + m_minIndex.set_size(0); + m_maxIndex.set_size(0); + } + + ImageStatisticsCalculator::statisticsMapType ImageStatisticsCalculator::StatisticsContainer::GetStatisticsAsMap() + { + ImageStatisticsCalculator::statisticsMapType statisticsAsMap; + + statisticsAsMap["N"] = m_N; + statisticsAsMap["Mean"] = m_Mean; + statisticsAsMap["Min"] = m_Min; + statisticsAsMap["Max"] = m_Max; + statisticsAsMap["StandardDeviation"] = m_Std; + statisticsAsMap["Variance"] = m_Variance; + statisticsAsMap["Skewness"] = m_Skewness; + statisticsAsMap["Kurtosis"] = m_Kurtosis; + statisticsAsMap["RMS"] = m_RMS; + statisticsAsMap["MPP"] = m_MPP; + statisticsAsMap["Median"] = m_Median; + statisticsAsMap["Uniformity"] = m_Uniformity; + statisticsAsMap["UPP"] = m_UPP; + statisticsAsMap["Entropy"] = m_Entropy; + statisticsAsMap["Label"] = m_Label; + + return statisticsAsMap; + } + + + void ImageStatisticsCalculator::StatisticsContainer::Reset() + { + m_N = nan(""); + m_Mean = nan(""); + m_Min = nan(""); + m_Max = nan(""); + m_Std = nan(""); + m_Variance = nan(""); + m_Skewness = nan(""); + m_Kurtosis = nan(""); + m_RMS = nan(""); + m_MPP = nan(""); + m_Median = nan(""); + m_Uniformity = nan(""); + m_UPP = nan(""); + m_Entropy = nan(""); + m_Histogram = HistogramType::New(); + m_minIndex.set_size(0); + m_maxIndex.set_size(0); + m_Label = 0; + } + + void ImageStatisticsCalculator::StatisticsContainer::Print() + { + ImageStatisticsCalculator::statisticsMapType statMap = this->GetStatisticsAsMap(); + // print all map key value pairs + // const auto& val:statMap + for (auto it = statMap.begin(); it != statMap.end(); ++it) { - maxIndex = imageIndexIt.GetIndex(); - maxValue = value; + std::cout << it->first << ": " << it->second << std::endl; } - if(value < minValue ) + // print the min and max index + std::cout << "Min Index:" << std::endl; + for (auto it = this->GetMinIndex().begin(); it != this->GetMinIndex().end(); ++it) { - minIndex = imageIndexIt.GetIndex(); - minValue = value; + std::cout << *it << " "; } - } - } - - minMax.MaxIndex.set_size(VImageDimension); - minMax.MinIndex.set_size(VImageDimension); - - for(unsigned int i = 0; i < minMax.MaxIndex.size(); ++i) - { - minMax.MaxIndex[i] = maxIndex[i]; - } - - for(unsigned int i = 0; i < minMax.MinIndex.size(); ++i) - { - minMax.MinIndex[i] = minIndex[i]; - } - - minMax.Max = maxValue; - minMax.Min = minValue; - - return minMax; - } + std::cout << std::endl; - template - itk::Size - ImageStatisticsCalculator - ::CalculateConvolutionKernelSize(double spacing[VImageDimension], double radiusInMM) - { - typedef itk::Image< float, VImageDimension > KernelImageType; - typedef typename KernelImageType::SizeType SizeType; - SizeType maskSize; - - for(unsigned int i = 0; i < VImageDimension; ++i) - { - maskSize[i] = static_cast( 2 * radiusInMM / spacing[i]); - - // We always want an uneven size to have a clear center point in the convolution mask - if(maskSize[i] % 2 == 0 ) - { - ++maskSize[i]; - } - } - return maskSize; - } - - template - itk::SmartPointer< itk::Image > - ImageStatisticsCalculator - ::GenerateHotspotSearchConvolutionKernel(double mmPerPixel[VImageDimension], double radiusInMM) - { - std::stringstream ss; - for (unsigned int i = 0; i < VImageDimension; ++i) - { - ss << mmPerPixel[i]; - if (i < VImageDimension -1) - ss << ","; - } - MITK_DEBUG << "Update convolution kernel for spacing (" << ss.str() << ") and radius " << radiusInMM << "mm"; - - - double radiusInMMSquared = radiusInMM * radiusInMM; - typedef itk::Image< float, VImageDimension > KernelImageType; - typename KernelImageType::Pointer convolutionKernel = KernelImageType::New(); - - // Calculate size and allocate mask image - typedef typename KernelImageType::SizeType SizeType; - SizeType maskSize = this->CalculateConvolutionKernelSize(mmPerPixel, radiusInMM); - - Point3D convolutionMaskCenterIndex; convolutionMaskCenterIndex.Fill(0.0); - for(unsigned int i = 0; i < VImageDimension; ++i) - { - convolutionMaskCenterIndex[i] = 0.5 * (double)(maskSize[i]-1); - } - - typedef typename KernelImageType::IndexType IndexType; - IndexType maskIndex; - maskIndex.Fill(0); - - typedef typename KernelImageType::RegionType RegionType; - RegionType maskRegion; - maskRegion.SetSize(maskSize); - maskRegion.SetIndex(maskIndex); - - convolutionKernel->SetRegions(maskRegion); - convolutionKernel->SetSpacing(mmPerPixel); - convolutionKernel->Allocate(); - - // Fill mask image values by subsampling the image grid - typedef itk::ImageRegionIteratorWithIndex MaskIteratorType; - MaskIteratorType maskIt(convolutionKernel,maskRegion); - - int numberOfSubVoxelsPerDimension = 2; // per dimension! - int numberOfSubVoxels = ::pow( static_cast(numberOfSubVoxelsPerDimension), static_cast(VImageDimension) ); - double subVoxelSizeInPixels = 1.0 / (double)numberOfSubVoxelsPerDimension; - double valueOfOneSubVoxel = 1.0 / (double)numberOfSubVoxels; - double maskValue = 0.0; - Point3D subVoxelIndexPosition; - double distanceSquared = 0.0; - - typedef itk::ContinuousIndex ContinuousIndexType; - for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) - { - ContinuousIndexType indexPoint(maskIt.GetIndex()); - Point3D voxelPosition; - for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) - { - voxelPosition[dimension] = indexPoint[dimension]; - } - - maskValue = 0.0; - Vector3D subVoxelOffset; subVoxelOffset.Fill(0.0); - // iterate sub-voxels by iterating all possible offsets - for (subVoxelOffset[0] = -0.5 + subVoxelSizeInPixels / 2.0; - subVoxelOffset[0] < +0.5; - subVoxelOffset[0] += subVoxelSizeInPixels) - { - for (subVoxelOffset[1] = -0.5 + subVoxelSizeInPixels / 2.0; - subVoxelOffset[1] < +0.5; - subVoxelOffset[1] += subVoxelSizeInPixels) + // print the min and max index + std::cout << "Max Index:" << std::endl; + for (auto it = this->GetMaxIndex().begin(); it != this->GetMaxIndex().end(); ++it) { - for (subVoxelOffset[2] = -0.5 + subVoxelSizeInPixels / 2.0; - subVoxelOffset[2] < +0.5; - subVoxelOffset[2] += subVoxelSizeInPixels) - { - subVoxelIndexPosition = voxelPosition + subVoxelOffset; // this COULD be integrated into the for-loops if neccessary (add voxelPosition to initializer and end condition) - distanceSquared = - (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] * (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] - + (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] * (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] - + (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2] * (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2]; - - if (distanceSquared <= radiusInMMSquared) - { - maskValue += valueOfOneSubVoxel; - } - } + std::cout << *it << " "; } - } - maskIt.Set( maskValue ); - } - - return convolutionKernel; - } - - template - itk::SmartPointer > - ImageStatisticsCalculator::GenerateConvolutionImage( const itk::Image* inputImage ) - { - double mmPerPixel[VImageDimension]; - for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) - { - mmPerPixel[dimension] = inputImage->GetSpacing()[dimension]; - } - - // update convolution kernel - typedef itk::Image< float, VImageDimension > KernelImageType; - typename KernelImageType::Pointer convolutionKernel = this->GenerateHotspotSearchConvolutionKernel(mmPerPixel, m_HotspotRadiusInMM); - - // update convolution image - typedef itk::Image< TPixel, VImageDimension > InputImageType; - typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; - typedef itk::FFTConvolutionImageFilter ConvolutionFilterType; - - typename ConvolutionFilterType::Pointer convolutionFilter = ConvolutionFilterType::New(); - typedef itk::ConstantBoundaryCondition BoundaryConditionType; - BoundaryConditionType boundaryCondition; - boundaryCondition.SetConstant(0.0); - - if (GetHotspotMustBeCompletlyInsideImage()) - { - // overwrite default boundary condition - convolutionFilter->SetBoundaryCondition(&boundaryCondition); - } - - convolutionFilter->SetInput(inputImage); - convolutionFilter->SetKernelImage(convolutionKernel); - convolutionFilter->SetNormalize(true); - MITK_DEBUG << "Update Convolution image for hotspot search"; - convolutionFilter->UpdateLargestPossibleRegion(); - - typename ConvolutionImageType::Pointer convolutionImage = convolutionFilter->GetOutput(); - convolutionImage->SetSpacing( inputImage->GetSpacing() ); // only workaround because convolution filter seems to ignore spacing of input image - - m_HotspotRadiusInMMChanged = false; - return convolutionImage; - } - - template < typename TPixel, unsigned int VImageDimension> - void - ImageStatisticsCalculator - ::FillHotspotMaskPixels( itk::Image* maskImage, - itk::Point sphereCenter, - double sphereRadiusInMM) - { - typedef itk::Image< TPixel, VImageDimension > MaskImageType; - typedef itk::ImageRegionIteratorWithIndex MaskImageIteratorType; - - MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); - - typename MaskImageType::IndexType maskIndex; - typename MaskImageType::PointType worldPosition; - - for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) - { - maskIndex = maskIt.GetIndex(); - maskImage->TransformIndexToPhysicalPoint(maskIndex, worldPosition); - maskIt.Set( worldPosition.EuclideanDistanceTo(sphereCenter) <= sphereRadiusInMM ? 1 : 0 ); - } - } - - template < typename TPixel, unsigned int VImageDimension> - ImageStatisticsCalculator::Statistics - ImageStatisticsCalculator::CalculateHotspotStatistics( - const itk::Image* inputImage, - itk::Image* maskImage, - double radiusInMM, - bool& isHotspotDefined, - unsigned int label) - { - // get convolution image (updated in GenerateConvolutionImage()) - typedef itk::Image< TPixel, VImageDimension > InputImageType; - typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; - typedef itk::Image< float, VImageDimension > KernelImageType; - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - - //typename ConvolutionImageType::Pointer convolutionImage = dynamic_cast(this->GenerateConvolutionImage(inputImage)); - typename ConvolutionImageType::Pointer convolutionImage = this->GenerateConvolutionImage(inputImage); - - if (convolutionImage.IsNull()) - { - MITK_ERROR << "Empty convolution image in CalculateHotspotStatistics(). We should never reach this state (logic error)."; - throw std::logic_error("Empty convolution image in CalculateHotspotStatistics()"); - } - - // find maximum in convolution image, given the current mask - double requiredDistanceToBorder = m_HotspotMustBeCompletelyInsideImage ? m_HotspotRadiusInMM : -1.0; - ImageExtrema convolutionImageInformation = CalculateExtremaWorld(convolutionImage.GetPointer(), maskImage, requiredDistanceToBorder, label); - - isHotspotDefined = convolutionImageInformation.Defined; - - if (!isHotspotDefined) - { - m_EmptyStatistics.Reset(VImageDimension); - MITK_ERROR << "No origin of hotspot-sphere was calculated! Returning empty statistics"; - return m_EmptyStatistics; - } - else - { - // create a binary mask around the "hotspot" region, fill the shape of a sphere around our hotspot center - typedef itk::ImageDuplicator< InputImageType > DuplicatorType; - typename DuplicatorType::Pointer copyMachine = DuplicatorType::New(); - copyMachine->SetInputImage(inputImage); - copyMachine->Update(); - - typedef itk::CastImageFilter< InputImageType, MaskImageType > CastFilterType; - typename CastFilterType::Pointer caster = CastFilterType::New(); - caster->SetInput( copyMachine->GetOutput() ); - caster->Update(); - typename MaskImageType::Pointer hotspotMaskITK = caster->GetOutput(); - - typedef typename InputImageType::IndexType IndexType; - IndexType maskCenterIndex; - for (unsigned int d =0; d< VImageDimension;++d) maskCenterIndex[d]=convolutionImageInformation.MaxIndex[d]; - typename ConvolutionImageType::PointType maskCenter; - inputImage->TransformIndexToPhysicalPoint(maskCenterIndex,maskCenter); - - this->FillHotspotMaskPixels(hotspotMaskITK.GetPointer(), maskCenter, radiusInMM); - - // calculate statistics within the binary mask - typedef itk::ExtendedLabelStatisticsImageFilter< InputImageType, MaskImageType> LabelStatisticsFilterType; - typename LabelStatisticsFilterType::Pointer labelStatisticsFilter; - labelStatisticsFilter = LabelStatisticsFilterType::New(); - labelStatisticsFilter->SetInput( inputImage ); - labelStatisticsFilter->SetLabelInput( hotspotMaskITK ); - labelStatisticsFilter->SetCoordinateTolerance( 0.001 ); - labelStatisticsFilter->SetDirectionTolerance( 0.001 ); - - labelStatisticsFilter->Update(); - - Statistics hotspotStatistics; - hotspotStatistics.SetHotspotIndex(convolutionImageInformation.MaxIndex); - hotspotStatistics.SetMean(convolutionImageInformation.Max); - - if ( labelStatisticsFilter->HasLabel( 1 ) ) - { - hotspotStatistics.SetLabel (1); - hotspotStatistics.SetN(labelStatisticsFilter->GetCount(1)); - hotspotStatistics.SetMin(labelStatisticsFilter->GetMinimum(1)); - hotspotStatistics.SetMax(labelStatisticsFilter->GetMaximum(1)); - hotspotStatistics.SetMedian(labelStatisticsFilter->GetMedian(1)); - hotspotStatistics.SetVariance(labelStatisticsFilter->GetVariance(1)); - hotspotStatistics.SetSigma(labelStatisticsFilter->GetSigma(1)); - hotspotStatistics.SetRMS(sqrt( hotspotStatistics.GetMean() * hotspotStatistics.GetMean() - + hotspotStatistics.GetSigma() * hotspotStatistics.GetSigma() )); - - MITK_DEBUG << "Statistics for inside hotspot: Mean " << hotspotStatistics.GetMean() - << ", SD " << hotspotStatistics.GetSigma() - << ", Max " << hotspotStatistics.GetMax() - << ", Min " << hotspotStatistics.GetMin(); - } - else - { - MITK_ERROR << "Uh oh! Unable to calculate statistics for hotspot region..."; - return m_EmptyStatistics; - } - - return hotspotStatistics; - } - } - - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::InternalCalculateMaskFromPlanarFigure( - const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - - typedef itk::CastImageFilter< ImageType, MaskImage2DType > CastFilterType; - - // Generate mask image as new image with same header as input image and - // initialize with 1. - typename CastFilterType::Pointer castFilter = CastFilterType::New(); - castFilter->SetInput( image ); - castFilter->Update(); - castFilter->GetOutput()->FillBuffer( 1 ); - - // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. - // These points are used by the vtkLassoStencilSource to create - // a vtkImageStencil. - const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); - const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); - const mitk::BaseGeometry *imageGeometry3D = m_Image->GetGeometry( 0 ); - // If there is a second poly line in a closed planar figure, treat it as a hole. - PlanarFigure::PolyLineType planarFigureHolePolyline; - - if (m_PlanarFigure->GetPolyLinesSize() == 2) - planarFigureHolePolyline = m_PlanarFigure->GetPolyLine(1); - - - // Determine x- and y-dimensions depending on principal axis - int i0, i1; - switch ( axis ) - { - case 0: - i0 = 1; - i1 = 2; - break; - - case 1: - i0 = 0; - i1 = 2; - break; - - case 2: - default: - i0 = 0; - i1 = 1; - break; - } - m_PlanarFigureCoordinate0= i0; - m_PlanarFigureCoordinate1= i1; - - // store the polyline contour as vtkPoints object - bool outOfBounds = false; - vtkSmartPointer points = vtkSmartPointer::New(); - typename PlanarFigure::PolyLineType::const_iterator it; - for ( it = planarFigurePolyline.begin(); - it != planarFigurePolyline.end(); - ++it ) - { - Point3D point3D; - - // Convert 2D point back to the local index coordinates of the selected - // image - planarFigurePlaneGeometry->Map( *it, point3D ); - - // Polygons (partially) outside of the image bounds can not be processed - // further due to a bug in vtkPolyDataToImageStencil - if ( !imageGeometry3D->IsInside( point3D ) ) - { - outOfBounds = true; - } - - imageGeometry3D->WorldToIndex( point3D, point3D ); - - points->InsertNextPoint( point3D[i0], point3D[i1], 0 ); + std::cout << std::endl; } - vtkSmartPointer holePoints = nullptr; - - if (!planarFigureHolePolyline.empty()) + std::string ImageStatisticsCalculator::StatisticsContainer::GetAsString() { - holePoints = vtkSmartPointer::New(); - - Point3D point3D; - PlanarFigure::PolyLineType::const_iterator end = planarFigureHolePolyline.end(); - - for (it = planarFigureHolePolyline.begin(); it != end; ++it) - { - planarFigurePlaneGeometry->Map(*it, point3D); - imageGeometry3D->WorldToIndex(point3D, point3D); - holePoints->InsertNextPoint(point3D[i0], point3D[i1], 0); - } - } - - // mark a malformed 2D planar figure ( i.e. area = 0 ) as out of bounds - // this can happen when all control points of a rectangle lie on the same line = two of the three extents are zero - double bounds[6] = {0, 0, 0, 0, 0, 0}; - points->GetBounds( bounds ); - bool extent_x = (fabs(bounds[0] - bounds[1])) < mitk::eps; - bool extent_y = (fabs(bounds[2] - bounds[3])) < mitk::eps; - bool extent_z = (fabs(bounds[4] - bounds[5])) < mitk::eps; - - // throw an exception if a closed planar figure is deformed, i.e. has only one non-zero extent - if ( m_PlanarFigure->IsClosed() && - ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) - { - mitkThrow() << "Figure has a zero area and cannot be used for masking."; - } - - if ( outOfBounds ) - { - throw std::runtime_error( "Figure at least partially outside of image bounds!" ); - } - - // create a vtkLassoStencilSource and set the points of the Polygon - vtkSmartPointer lassoStencil = vtkSmartPointer::New(); - lassoStencil->SetShapeToPolygon(); - lassoStencil->SetPoints( points ); - - vtkSmartPointer holeLassoStencil = nullptr; - - if (holePoints.GetPointer() != nullptr) - { - holeLassoStencil = vtkSmartPointer::New(); - holeLassoStencil->SetShapeToPolygon(); - holeLassoStencil->SetPoints(holePoints); - } - - // Export from ITK to VTK (to use a VTK filter) - typedef itk::VTKImageImport< MaskImage2DType > ImageImportType; - typedef itk::VTKImageExport< MaskImage2DType > ImageExportType; - - typename ImageExportType::Pointer itkExporter = ImageExportType::New(); - itkExporter->SetInput( castFilter->GetOutput() ); - - vtkSmartPointer vtkImporter = vtkSmartPointer::New(); - this->ConnectPipelines( itkExporter, vtkImporter ); - - // Apply the generated image stencil to the input image - vtkSmartPointer imageStencilFilter = vtkSmartPointer::New(); - imageStencilFilter->SetInputConnection( vtkImporter->GetOutputPort() ); - imageStencilFilter->SetStencilConnection(lassoStencil->GetOutputPort()); - imageStencilFilter->ReverseStencilOff(); - imageStencilFilter->SetBackgroundValue( 0 ); - imageStencilFilter->Update(); - - vtkSmartPointer holeStencilFilter = nullptr; + std::string res = ""; + ImageStatisticsCalculator::statisticsMapType statMap = this->GetStatisticsAsMap(); + // print all map key value pairs + // const auto& val:statMap + for (auto it = statMap.begin(); it != statMap.end(); ++it) + { + res += std::string(it->first) + ": " + std::to_string(it->second) + "\n"; + } - if (holeLassoStencil.GetPointer() != nullptr) - { - holeStencilFilter = vtkSmartPointer::New(); - holeStencilFilter->SetInputConnection(imageStencilFilter->GetOutputPort()); - holeStencilFilter->SetStencilConnection(holeLassoStencil->GetOutputPort()); - holeStencilFilter->ReverseStencilOn(); - holeStencilFilter->SetBackgroundValue(0); - holeStencilFilter->Update(); - } + // print the min and max index + res += "Min Index:" + std::string("\n"); + for (auto it = this->GetMinIndex().begin(); it != this->GetMinIndex().end(); it++) + { + res += std::to_string(*it) + std::string(" "); + } + res += "\n"; - // Export from VTK back to ITK - vtkSmartPointer vtkExporter = vtkSmartPointer::New(); - vtkExporter->SetInputConnection( holeStencilFilter.GetPointer() == nullptr - ? imageStencilFilter->GetOutputPort() - : holeStencilFilter->GetOutputPort()); - vtkExporter->Update(); - - typename ImageImportType::Pointer itkImporter = ImageImportType::New(); - this->ConnectPipelines( vtkExporter, itkImporter ); - itkImporter->Update(); - - typedef itk::ImageDuplicator< ImageImportType::OutputImageType > DuplicatorType; - DuplicatorType::Pointer duplicator = DuplicatorType::New(); - duplicator->SetInputImage( itkImporter->GetOutput() ); - duplicator->Update(); - - // Store mask - m_InternalImageMask2D = duplicator->GetOutput(); - } - - - void ImageStatisticsCalculator::UnmaskedStatisticsProgressUpdate() - { - // Need to throw away every second progress event to reach a final count of - // 100 since two consecutive filters are used in this case - static int updateCounter = 0; - if ( updateCounter++ % 2 == 0 ) - { - this->InvokeEvent( itk::ProgressEvent() ); + // print the min and max index + res += "Max Index:" + std::string("\n"); + for (auto it = this->GetMaxIndex().begin(); it != this->GetMaxIndex().end(); it++) + { + res += std::to_string(*it) + " "; + } + res += "\n"; + return res; } - } - - void ImageStatisticsCalculator::MaskedStatisticsProgressUpdate() - { - this->InvokeEvent( itk::ProgressEvent() ); - } } diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.h b/Modules/ImageStatistics/mitkImageStatisticsCalculator.h index faaa2cf62c..4b98a49c2e 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsCalculator.h +++ b/Modules/ImageStatistics/mitkImageStatisticsCalculator.h @@ -1,609 +1,414 @@ -/*=================================================================== -The Medical Imaging Interaction Toolkit (MITK) +#ifndef MITKIMAGESTATISTICSCALCULATOR +#define MITKIMAGESTATISTICSCALCULATOR -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 _MITK_IMAGESTATISTICSCALCULATOR_H -#define _MITK_IMAGESTATISTICSCALCULATOR_H - -#include -#include "MitkImageStatisticsExports.h" +#include +#include +#include #include -#include -#include "mitkImage.h" -#include "mitkPlanarFigure.h" - -#ifndef __itkHistogram_h -#include -#endif - -#include - - -#include - -// just a helper to unclutter our code -// to be replaced with references to m_Member (when deprecated public members in Statistics are removed) -#define mitkSetGetConstMacro(name, type) \ - virtual type Get##name() const \ - { \ - return this->name; \ - } \ - \ - virtual void Set##name(const type _arg) \ - { \ - if ( this->name != _arg ) \ - { \ - this->name = _arg; \ - } \ - } +#include +#include +#include namespace mitk { - /** - * \brief Class for calculating statistics and histogram for an (optionally - * masked) image. - * - * Images can be masked by either a label image (of the same dimensions as - * the original image) or by a closed mitk::PlanarFigure, e.g. a circle or - * polygon. When masking with a planar figure, the slice corresponding to the - * plane containing the figure is extracted and then clipped with contour - * defined by the figure. Planar figures need to be aligned along the main axes - * of the image (axial, sagittal, coronal). Planar figures on arbitrary - * rotated planes are not supported. - * - * For each operating mode (no masking, masking by image, masking by planar - * figure), the calculated statistics and histogram are cached so that, when - * switching back and forth between operation modes without modifying mask or - * image, the information doesn't need to be recalculated. - * - * The class also has the possibility to calculate the location and separate - * statistics for a region called "hotspot". The hotspot is a sphere of - * user-defined size and its location is chosen in a way that the average - * pixel value within the sphere is maximized. - * - * \warning Hotspot calculation does not work in case of 2D-images! - * - * Note: currently time-resolved and multi-channel pictures are not properly - * supported. - * - * \section HotspotStatistics_caption Calculation of hotspot statistics - * - * Since calculation of hotspot location and statistics is not - * straight-forward, the following paragraphs will describe it in more detail. - * - * Note: Calculation of hotspot statistics is optional and set to off by default. - * Multilabel-masks are supported. - * - * \subsection HotspotStatistics_description Hotspot Definition - * - * The hotspot of an image is motivated from PET readings. It is defined - * as a spherical region of fixed size which maximizes the average pixel value - * within the region. The following image illustrates the concept: the - * colored areas are different image intensities and the hotspot is located - * in the hottest region of the image. - * - * Note: Only hotspots are calculated for which the whole hotspot-sphere is - * inside the image by default. This behaviour can be changed by - * by calling SetHotspotMustBeCompletlyInsideImage(). - * \warning Note that SetHotspotMustBeCompletlyInsideImage(false) may overrate - * "hot" regions at image borders, because they have a stronger influence on the - * mean value! Think clearly about this fact and make sure this is what you - * want/need in your application, before calling - * SetHotspotMustBeCompletlyInsideImage(false)! - * - * - * \image html hotspotexample.JPG - * - * \subsection HotspotStatistics_calculation Hotspot Calculation - * - * Since only the size of the hotspot is known initially, we need to calculate - * two aspects (both implemented in CalculateHotspotStatistics() ): - * - the hotspot location - * - statistics of the pixels within the hotspot. - * - * Finding the hotspot location requires to calculate the average value at each - * position. This is done by convolution of the image with a sperical kernel - * image which reflects partial volumes (important in the case of low-resolution - * PET images). - * - * Once the hotspot location is known, calculating the actual statistics is a - * simple task which is implemented in CalculateHotspotStatistics() using a second - * instance of the ImageStatisticsCalculator. - * - * Step 1: Finding the hotspot by image convolution - * - * As described above, we use image convolution with a rasterized sphere to - * average the image at each position. To handle coarse resolutions, which would - * normally force us to decide for partially contained voxels whether to count - * them or not, we supersample the kernel image and use non-integer kernel values - * (see GenerateHotspotSearchConvolutionKernel()), which reflect the volume part that is contained in the - * sphere. For example, if three subvoxels are inside the sphere, the corresponding - * kernel voxel gets a value of 0.75 (3 out of 4 subvoxels, see 2D example below). - * - * \image html convolutionkernelsupersampling.jpg - * - * Convolution itself is done by means of the itkFFTConvolutionImageFilter. - * To find the hotspot location, we simply iterate the averaged image and find a - * maximum location (see CalculateExtremaWorld()). In case of images with multiple - * maxima the method returns value and corresponding index of the extrema that is - * found by the iterator first. - * - * Step 2: Computation of hotspot statistics - * - * Once the hotspot location is found, statistics for the region are calculated - * by simply iterating the input image and regarding all pixel centers inside the - * hotspot-sphere for statistics. - * \warning Index positions of maximum/minimum are not provided, because they are not necessarily unique - * \todo If index positions of maximum/minimum are required, output needs to be changed to multiple positions / regions, etc. - * - * \subsection HotspotStatistics_tests Tests - * - * To check the correctness of the hotspot calculation, a special class - * (\ref hotspottestdoc) has been created, which generates images with - * known hotspot location and statistics. A number of unit tests use this class - * to first generate an image of known properites and then verify that - * ImageStatisticsCalculator is able to reproduce the known statistics. - * - */ - class MITKIMAGESTATISTICS_EXPORT ImageStatisticsCalculator : public itk::Object - { - public: - - /** \brief Enum for possible masking modi. */ - enum - { - MASKING_MODE_NONE = 0, - MASKING_MODE_IMAGE = 1, - MASKING_MODE_PLANARFIGURE = 2 - }; - - typedef itk::Statistics::Histogram HistogramType; - typedef HistogramType::ConstIterator HistogramConstIteratorType; - - /** \brief Class for common statistics, includig hotspot properties. */ - class MITKIMAGESTATISTICS_EXPORT Statistics + class MITKIMAGESTATISTICS_EXPORT ImageStatisticsCalculator: public itk::Object { public: + /** Standard Self typedef */ + typedef ImageStatisticsCalculator Self; + typedef itk::Object Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(ImageStatisticsCalculator_v2, itk::Object) + + typedef double statisticsValueType; + typedef std::map statisticsMapType; + typedef itk::Statistics::Histogram HistogramType; + typedef unsigned short MaskPixelType; + + /**Documentation + @brief Container class for storing the computed image statistics. + + Container class for storing the computed image statistics. Stored statistics are: + - N: number of voxels + - Mean + - MPP (Mean of positive pixels) + - Median + - Skewness + - Kurtosis + - Uniformity + - UPP (Uniformity of positive pixels) + - Variance + - Std (Standard Deviation) + - Min + - Max + - RMS (Root Mean Square) + - Label (if applicable, the label (unsigned short) of the mask the statistics belong to) + - Entropy + + It furthermore stores the following: + - MinIndex (Index of Image where the Minimum is located) + - MaxIndex (Index of Image where the Maximum is located) + - Histogram of Pixel Values*/ + class MITKIMAGESTATISTICS_EXPORT StatisticsContainer : public itk::Object + { + public: + /** Standard Self typedef */ + typedef StatisticsContainer Self; + typedef itk::Object Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(StatisticsContainer, itk::Object) + + typedef double RealType; + + /**Documentation + @brief Returns a std::map containing all real valued statistics stored in this class (= all statistics except minIndex, maxIndex and the histogram)*/ + statisticsMapType GetStatisticsAsMap(); + + /**Documentation + @brief Deletes all stored values*/ + void Reset(); + + void SetN(long n) + { + m_N = n; + } + + const long& GetN() const + { + return m_N; + } + + void SetMean(RealType mean) + { + m_Mean = mean; + } + + const RealType& GetMean() const + { + return m_Mean; + } + + void SetVariance(RealType variance) + { + m_Variance = variance; + } + + const RealType& GetVariance() const + { + return m_Variance; + } + + void SetStd(RealType std) + { + m_Std = std; + } + + const RealType& GetStd() const + { + return m_Std; + } + + void SetMin(RealType minVal) + { + m_Min = minVal; + } + + const RealType& GetMin() const + { + return m_Min; + } + + void SetMax(RealType maxVal) + { + m_Max = maxVal; + } + + const RealType& GetMax() const + { + return m_Max; + } + + void SetRMS(RealType rms) + { + m_RMS = rms; + } + + const RealType& GetRMS() const + { + return m_RMS; + } + + void SetSkewness(RealType skewness) + { + m_Skewness = skewness; + } + + const RealType& GetSkewness() const + { + return m_Skewness; + } + + void SetKurtosis(RealType kurtosis) + { + m_Kurtosis = kurtosis; + } + + const RealType& GetKurtosis() const + { + return m_Kurtosis; + } + + void SetMPP(RealType mpp) + { + m_MPP = mpp; + } + + const RealType& GetMPP() const + { + return m_MPP; + } + + void SetLabel(unsigned int label) + { + m_Label = label; + } + + const unsigned int& GetLabel() const + { + return m_Label; + } + + void SetMinIndex(vnl_vector minIndex) + { + m_minIndex = minIndex; + } + + vnl_vector GetMinIndex() const + { + return m_minIndex; + } + + void SetMaxIndex(vnl_vector maxIndex) + { + m_maxIndex = maxIndex; + } + + vnl_vector GetMaxIndex() const + { + return m_maxIndex; + } + + void SetHistogram(HistogramType::Pointer hist) + { + if (m_Histogram != hist) + { + m_Histogram = hist; + } + } + + const HistogramType::Pointer GetHistogram() const + { + return m_Histogram; + } + + void SetEntropy(RealType entropy) + { + m_Entropy = entropy; + } + + const RealType & GetEntropy() const + { + return m_Entropy; + } + + void SetMedian(RealType median) + { + m_Median = median; + } + + const RealType & GetMedian() const + { + return m_Median; + } + + void SetUniformity(RealType uniformity) + { + m_Uniformity = uniformity; + } + + const RealType & GetUniformity() const + { + return m_Uniformity; + } + + void SetUPP(RealType upp) + { + m_UPP = upp; + } + + const RealType & GetUPP() const + { + return m_UPP; + } + + /**Documentation + @brief Creates a StatisticsMapType containing all real valued statistics stored in this class (= all statistics except minIndex, maxIndex and the histogram) and prints its contents to std::cout*/ + void Print(); + + /**Documentation + @brief Generates a string that contains all real valued statistics stored in this class (= all statistics except minIndex, maxIndex and the histogram)*/ + std::string GetAsString(); + + + protected: + StatisticsContainer(); + + private: + itk::LightObject::Pointer InternalClone() const + { + itk::LightObject::Pointer ioPtr = Superclass::InternalClone(); + Self::Pointer rval = dynamic_cast(ioPtr.GetPointer()); + if (rval.IsNull()) + { + itkExceptionMacro(<< "downcast to type " + << "StatisticsContainer" + << " failed."); + } + + rval->SetEntropy(this->GetEntropy()); + rval->SetKurtosis(this->GetKurtosis()); + rval->SetLabel(this->GetLabel()); + rval->SetMax(this->GetMax()); + rval->SetMin(this->GetMin()); + rval->SetMean(this->GetMean()); + rval->SetMedian(this->GetMedian()); + rval->SetMPP(this->GetMPP()); + rval->SetN(this->GetN()); + rval->SetRMS(this->GetRMS()); + rval->SetSkewness(this->GetSkewness()); + rval->SetStd(this->GetStd()); + rval->SetUniformity(this->GetUniformity()); + rval->SetUPP(this->GetUPP()); + rval->SetVariance(this->GetVariance()); + rval->SetHistogram(this->GetHistogram()); + rval->SetMinIndex(this->GetMinIndex()); + rval->SetMaxIndex(this->GetMaxIndex()); + return ioPtr; + } + + // not pretty, is temporary + long m_N; + RealType m_Mean, m_Min, m_Max, m_Std, m_Variance; + RealType m_Skewness; + RealType m_Kurtosis; + RealType m_RMS; + RealType m_MPP; + vnl_vector m_minIndex, m_maxIndex; + RealType m_Median; + RealType m_Uniformity; + RealType m_UPP; + RealType m_Entropy; + unsigned int m_Label; + HistogramType::Pointer m_Histogram; + + }; + + /**Documentation + @brief Set the image for which the statistics are to be computed.*/ + void SetInputImage(mitk::Image::Pointer image); + + /**Documentation + @brief Set the mask generator that creates the mask which is to be used to calculate statistics. If no more mask is desired simply set @param mask to nullptr*/ + void SetMask(mitk::MaskGenerator::Pointer mask); + + /**Documentation + @brief Set this if more than one mask should be applied (for instance if a IgnorePixelValueMask were to be used alongside with a segmentation). + Both masks are combined using pixel wise AND operation. The secondary mask does not have to be the same size than the primary but they need to have some overlap*/ + void SetSecondaryMask(mitk::MaskGenerator::Pointer mask); + + /**Documentation + @brief Set number of bins to be used for histogram statistics. If Bin size is set after number of bins, bin size will be used instead!*/ + void SetNBinsForHistogramStatistics(unsigned int nBins); + + /**Documentation + @brief Retrieve the number of bins used for histogram statistics. Careful: The return value does not indicate whether NBins or BinSize is used. + That solely depends on which parameter has been set last.*/ + unsigned int GetNBinsForHistogramStatistics() const; + + /**Documentation + @brief Set bin size to be used for histogram statistics. If nbins is set after bin size, nbins will be used instead!*/ + void SetBinSizeForHistogramStatistics(double binSize); + + /**Documentation + @brief Retrieve the bin size for histogram statistics. Careful: The return value does not indicate whether NBins or BinSize is used. + That solely depends on which parameter has been set last.*/ + double GetBinSizeForHistogramStatistics() const; + + /**Documentation + @brief Returns the statistics for label @a label and timeStep @a timeStep. If these requested statistics are not computed yet the computation is done as well. + For performance reasons, statistics for all labels in the image are computed at once. + */ + StatisticsContainer::Pointer GetStatistics(unsigned int timeStep=0, unsigned int label=1); + + protected: + ImageStatisticsCalculator(){ + m_nBinsForHistogramStatistics = 100; + m_binSizeForHistogramStatistics = 10; + m_UseBinSizeOverNBins = false; + }; - Statistics(bool withHotspotStatistics = true); - Statistics(const Statistics& other); - - virtual ~Statistics(); - - Statistics& operator=(Statistics const& stats); - - const Statistics& GetHotspotStatistics() const; // real statistics - Statistics& GetHotspotStatistics(); // real statistics - bool HasHotspotStatistics() const; - void SetHasHotspotStatistics(bool hasHotspotStatistics); // set a flag. if set, return empty hotspotstatistics object - - void Reset(unsigned int dimension = 2); - - mitkSetGetConstMacro(Label, unsigned int) - mitkSetGetConstMacro(N, unsigned int) - mitkSetGetConstMacro(Min, double) - mitkSetGetConstMacro(Max, double) - mitkSetGetConstMacro(Mean, double) - mitkSetGetConstMacro(Median, double) - - double GetVariance() const; - /** \brief Set variance - * - * This method checks whether the variance is negative: - * The reason that the variance may be negative is that the underlying itk::LabelStatisticsImageFilter uses a naïve algorithm - * for calculating the variance ( http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance ) which can lead to negative values - * due to rounding errors. - * - * If the variance is negative the value will be set to 0.0, else the given value will be set. - */ - void SetVariance( const double ); - - double GetSigma() const; - /** \brief Set standard deviation (sigma) - * - * This method checks if the given standard deviation is a positive value. This is done because the underlying itk::LabelStatisticsImageFilter uses - * a naïve algorithm to calculate the variance. This may lead to a negative variance and because the square root of the variance is taken it also - * leads to NaN for sigma. - * - * If the given value is not reasonable the value will be set to 0.0, else the given value will be set. - * - * \see SetVariance() - */ - void SetSigma( const double ); - - mitkSetGetConstMacro(RMS, double) - mitkSetGetConstMacro(MinIndex, vnl_vector) - mitkSetGetConstMacro(MaxIndex, vnl_vector) - mitkSetGetConstMacro(HotspotIndex, vnl_vector) - - mitkSetGetConstMacro(Skewness, double) - mitkSetGetConstMacro(Kurtosis, double) - mitkSetGetConstMacro(Uniformity, double) - mitkSetGetConstMacro(Entropy, double) - mitkSetGetConstMacro(UPP, double); - mitkSetGetConstMacro(MPP, double); private: - unsigned int Label; - unsigned int N; - double Min; - double Max; - double Mean; - double Median; - double Variance; - double Sigma; - double RMS; - vnl_vector MinIndex; - vnl_vector MaxIndex; - - bool m_SkewnessAndKurtosisSuccesfulCalculated; - bool m_ImageStatisticsSuccesfulCalculated; - - - Statistics* m_HotspotStatistics; - - double Skewness; - double Kurtosis; - double Uniformity; - double Entropy; - double UPP; - double MPP; - - bool m_HasHotspotStatistics; - vnl_vector HotspotIndex; //< index of hotspotsphere origin - }; - - typedef std::vector< HistogramType::ConstPointer > HistogramContainer; - typedef std::vector< Statistics > StatisticsContainer; - - mitkClassMacroItkParent( ImageStatisticsCalculator, itk::Object ); - itkFactorylessNewMacro(Self) - itkCloneMacro(Self) - - /** \brief Automatically calculate bin size to obtain 200 bins. */ - void SetUseDefaultBinSize(bool useDefault); - - /** \brief Set image from which to compute statistics. */ - void SetImage( const mitk::Image *image ); - - /** \brief Set image for masking. */ - void SetImageMask( const mitk::Image *imageMask ); - - /** \brief Set planar figure for masking. */ - void SetPlanarFigure( mitk::PlanarFigure *planarFigure ); - - - /** \brief Set/Get operation mode for masking */ - void SetMaskingMode( unsigned int mode ); - - /** \brief Set/Get operation mode for masking */ - itkGetMacro( MaskingMode, unsigned int ); - - /** \brief Set/Get operation mode for masking */ - void SetMaskingModeToNone(); - - /** \brief Set/Get operation mode for masking */ - void SetMaskingModeToImage(); - - /** \brief Set/Get operation mode for masking */ - void SetMaskingModeToPlanarFigure(); - - /** \brief Set a pixel value for pixels that will be ignored in the statistics */ - void SetIgnorePixelValue(double value); - - /** \brief Get the pixel value for pixels that will be ignored in the statistics */ - double GetIgnorePixelValue(); - - /** \brief Set whether a pixel value should be ignored in the statistics */ - void SetDoIgnorePixelValue(bool doit); - - /** \brief Get whether a pixel value will be ignored in the statistics */ - bool GetDoIgnorePixelValue(); - - /** \brief Set bin size for histogram resolution.*/ - void SetHistogramBinSize( double size); - - /** \brief Get bin size for histogram resolution.*/ - double GetHistogramBinSize(); - - /** \brief Sets the radius for the hotspot */ - void SetHotspotRadiusInMM (double hotspotRadiusInMM); - - /** \brief Returns the radius of the hotspot */ - double GetHotspotRadiusInMM(); - - /** \brief Sets whether the hotspot should be calculated */ - void SetCalculateHotspot(bool calculateHotspot); - - /** \brief Returns true whether the hotspot should be calculated, otherwise false */ - bool IsHotspotCalculated(); - - /** \brief Sets flag whether hotspot is completly inside the image. Please note that if set to false - it can be possible that statistics are calculated for which the whole hotspot is not inside the image! - - \warning regarding positions at the image centers may produce unexpected hotspot locations, please see \ref HotspotStatistics_description - */ - void SetHotspotMustBeCompletlyInsideImage(bool hotspotIsCompletlyInsideImage, bool warn = true); - - /** \brief Returns true if hotspot has to be completly inside the image. */ - bool GetHotspotMustBeCompletlyInsideImage() const; - - /** \brief Compute statistics (together with histogram) for the current - * masking mode. - * - * Computation is not executed if statistics is already up to date. In this - * case, false is returned; otherwise, true.*/ - virtual bool ComputeStatistics( unsigned int timeStep = 0 ); - - - /** \brief Retrieve the histogram depending on the current masking mode. - * - * \param label The label for which to retrieve the histogram in multi-label situations (ascending order). - */ - const HistogramType *GetHistogram( unsigned int timeStep = 0, unsigned int label = 0 ) const; - - /** \brief Retrieve the histogram depending on the current masking mode (for all image labels. */ - const HistogramContainer &GetHistogramVector( unsigned int timeStep = 0 ) const; - - typedef std::map BinFrequencyType; - - /* Returning a map including bin and Frequency*/ - BinFrequencyType GetBinsAndFreuqencyForHistograms( unsigned int timeStep = 0, unsigned int label = 0) const; - - /** \brief Retrieve statistics depending on the current masking mode. - * - - * \param label The label for which to retrieve the statistics in multi-label situations (ascending order). - */ - const Statistics &GetStatistics( unsigned int timeStep = 0, unsigned int label = 0 ) const; + template < typename TPixel, unsigned int VImageDimension > void InternalCalculateStatisticsUnmasked( + typename itk::Image< TPixel, VImageDimension >* image, + unsigned int timeStep); - /** \brief Retrieve statistics depending on the current masking mode (for all image labels). */ - const StatisticsContainer &GetStatisticsVector( unsigned int timeStep = 0 ) const; + template < typename TPixel, unsigned int VImageDimension > typename HistogramType::Pointer InternalCalculateHistogramUnmasked( + typename itk::Image< TPixel, VImageDimension >* image, + double minVal, + double maxVal); + template < typename TPixel, unsigned int VImageDimension > void InternalCalculateStatisticsMasked( + typename itk::Image< TPixel, VImageDimension >* image, + unsigned int timeStep); + bool IsUpdateRequired(unsigned int timeStep) const; + std::string GetNameOfClass() + { + return std::string("ImageStatisticsCalculator_v2"); + } - protected: - typedef std::vector< HistogramContainer > HistogramVector; - typedef std::vector< StatisticsContainer > StatisticsVector; + mitk::Image::Pointer m_Image; + mitk::Image::Pointer m_ImageTimeSlice; + mitk::Image::Pointer m_InternalImageForStatistics; - typedef std::vector< itk::TimeStamp > TimeStampVectorType; - typedef std::vector< bool > BoolVectorType; + mitk::MaskGenerator::Pointer m_MaskGenerator; + mitk::Image::Pointer m_InternalMask; - typedef itk::Image< unsigned short, 3 > MaskImage3DType; - typedef itk::Image< unsigned short, 2 > MaskImage2DType; + mitk::MaskGenerator::Pointer m_SecondaryMaskGenerator; + mitk::Image::Pointer m_SecondaryMask; - ImageStatisticsCalculator(); + unsigned int m_nBinsForHistogramStatistics; + double m_binSizeForHistogramStatistics; + bool m_UseBinSizeOverNBins; - virtual ~ImageStatisticsCalculator(); - - /** \brief Depending on the masking mode, the image and mask from which to - * calculate statistics is extracted from the original input image and mask - * data. - * - * For example, a when using a PlanarFigure as mask, the 2D image slice - * corresponding to the PlanarFigure will be extracted from the original - * image. If masking is disabled, the original image is simply passed - * through. */ - void ExtractImageAndMask( unsigned int timeStep = 0 ); - - /*calculate the min and max value, this is done because we need the min and max value before execution the statistics filter to have the wright range for the histogramm*/ - template < typename TPixel, unsigned int VImageDimension > - void GetMinAndMaxValue(double &minimum, double &maximum, int &counter, double &sigma, const itk::Image< TPixel, VImageDimension > *InputImage, - itk::Image< unsigned short, VImageDimension > *MaskImageType); - - /** \brief If the passed vector matches any of the three principal axes - * of the passed geometry, the ínteger value corresponding to the axis - * is set and true is returned. */ - bool GetPrincipalAxis( const BaseGeometry *geometry, Vector3D vector, - unsigned int &axis ); - - template < typename TPixel, unsigned int VImageDimension > - void InternalCalculateStatisticsUnmasked( - const itk::Image< TPixel, VImageDimension > *image, - StatisticsContainer* statisticsContainer, - HistogramContainer *histogramContainer ); - - template < typename TPixel, unsigned int VImageDimension > - void InternalCalculateStatisticsMasked( - const itk::Image< TPixel, VImageDimension > *image, - itk::Image< unsigned short, VImageDimension > *maskImage, - StatisticsContainer* statisticsContainer, - HistogramContainer* histogramContainer ); - - template < typename TPixel, unsigned int VImageDimension > - void InternalCalculateMaskFromPlanarFigure( - const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ); - - template < typename TPixel, unsigned int VImageDimension > - void InternalMaskIgnoredPixels( - const itk::Image< TPixel, VImageDimension > *image, - itk::Image< unsigned short, VImageDimension > *maskImage ); - - class ImageExtrema - { - public: - bool Defined; - double Max; - double Min; - vnl_vector MaxIndex; - vnl_vector MinIndex; - - ImageExtrema() - :Defined(false) - ,Max(itk::NumericTraits::min()) - ,Min(itk::NumericTraits::max()) - { - } + std::vector> m_StatisticsByTimeStep; + std::vector m_StatisticsUpdateTimePerTimeStep; }; +} +#endif // MITKIMAGESTATISTICSCALCULATOR - /** \brief Calculates minimum, maximum, mean value and their - * corresponding indices in a given ROI. As input the function - * needs an image and a mask. Returns an ImageExtrema object. */ - template - ImageExtrema CalculateExtremaWorld( - const itk::Image *inputImage, - itk::Image *maskImage, - double neccessaryDistanceToImageBorderInMM, - unsigned int label); - - - /** \brief Calculates the hotspot statistics depending on - * masking mode. Hotspot statistics are calculated for a - * hotspot which is completly located inside the image by default. */ - template < typename TPixel, unsigned int VImageDimension> - Statistics CalculateHotspotStatistics( - const itk::Image *inputImage, - itk::Image *maskImage, - double radiusInMM, - bool& isHotspotDefined, - unsigned int label); - - /** Connection from ITK to VTK */ - template - void ConnectPipelines(ITK_Exporter exporter, vtkSmartPointer importer) - { - importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); - - importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); - importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); - importer->SetSpacingCallback(exporter->GetSpacingCallback()); - importer->SetOriginCallback(exporter->GetOriginCallback()); - importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); - - importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); - - importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); - importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); - importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); - importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); - importer->SetCallbackUserData(exporter->GetCallbackUserData()); - } - - /** Connection from VTK to ITK */ - template - void ConnectPipelines(vtkSmartPointer exporter, ITK_Importer importer) - { - importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); - - importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); - importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); - importer->SetSpacingCallback(exporter->GetSpacingCallback()); - importer->SetOriginCallback(exporter->GetOriginCallback()); - importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); - - importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); - - importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); - importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); - importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); - importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); - importer->SetCallbackUserData(exporter->GetCallbackUserData()); - } - - - void UnmaskedStatisticsProgressUpdate(); - - void MaskedStatisticsProgressUpdate(); - - /** \brief Returns size of convolution kernel depending on spacing and radius. */ - template - itk::Size - CalculateConvolutionKernelSize(double spacing[VImageDimension], double radiusInMM); - - /** \brief Generates image of kernel which is needed for convolution. */ - template - itk::SmartPointer< itk::Image > - GenerateHotspotSearchConvolutionKernel(double spacing[VImageDimension], double radiusInMM); - - /** \brief Convolves image with spherical kernel image. Used for hotspot calculation. */ - template - itk::SmartPointer< itk::Image > - GenerateConvolutionImage( const itk::Image* inputImage ); - - /** \brief Fills pixels of the spherical hotspot mask. */ - template < typename TPixel, unsigned int VImageDimension> - void - FillHotspotMaskPixels( itk::Image* maskImage, - itk::Point sphereCenter, - double sphereRadiusInMM); - - /** m_Image contains the input image (e.g. 2D, 3D, 3D+t)*/ - mitk::Image::ConstPointer m_Image; - - mitk::Image::ConstPointer m_ImageMask; - - mitk::PlanarFigure::Pointer m_PlanarFigure; - - HistogramVector m_ImageHistogramVector; - HistogramVector m_MaskedImageHistogramVector; - HistogramVector m_PlanarFigureHistogramVector; - - HistogramType::Pointer m_EmptyHistogram; - HistogramContainer m_EmptyHistogramContainer; - - - StatisticsVector m_ImageStatisticsVector; - StatisticsVector m_MaskedImageStatisticsVector; - StatisticsVector m_PlanarFigureStatisticsVector; - StatisticsVector m_MaskedImageHotspotStatisticsVector; - - Statistics m_EmptyStatistics; - StatisticsContainer m_EmptyStatisticsContainer; - - unsigned int m_MaskingMode; - bool m_MaskingModeChanged; - - /** m_InternalImage contains a image volume at one time step (e.g. 2D, 3D)*/ - mitk::Image::ConstPointer m_InternalImage; - MaskImage3DType::Pointer m_InternalImageMask3D; - MaskImage2DType::Pointer m_InternalImageMask2D; - - TimeStampVectorType m_ImageStatisticsTimeStampVector; - TimeStampVectorType m_MaskedImageStatisticsTimeStampVector; - TimeStampVectorType m_PlanarFigureStatisticsTimeStampVector; - - BoolVectorType m_ImageStatisticsCalculationTriggerVector; - BoolVectorType m_MaskedImageStatisticsCalculationTriggerVector; - BoolVectorType m_PlanarFigureStatisticsCalculationTriggerVector; - - double m_IgnorePixelValue; - bool m_DoIgnorePixelValue; - bool m_IgnorePixelValueChanged; - - unsigned int m_PlanarFigureAxis; // Normal axis for PlanarFigure - unsigned int m_PlanarFigureSlice; // Slice which contains PlanarFigure - int m_PlanarFigureCoordinate0; // First plane-axis for PlanarFigure - int m_PlanarFigureCoordinate1; // Second plane-axis for PlanarFigure - - double m_HistogramBinSize; ///Bin size for histogram resoluion. - bool m_UseDefaultBinSize; - bool m_UseBinSizeBasedOnVOIRegion; - double m_HotspotRadiusInMM; - bool m_CalculateHotspot; - bool m_HotspotRadiusInMMChanged; - bool m_HotspotMustBeCompletelyInsideImage; - - - private: - - unsigned int calcNumberOfBins(mitk::ScalarType min, mitk::ScalarType max); - - - }; - -} // namespace - -#endif diff --git a/Modules/ImageStatistics/mitkIntensityProfile.cpp b/Modules/ImageStatistics/mitkIntensityProfile.cpp index e8e5ca066d..795e1750c1 100644 --- a/Modules/ImageStatistics/mitkIntensityProfile.cpp +++ b/Modules/ImageStatistics/mitkIntensityProfile.cpp @@ -1,380 +1,380 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include #include #include #include "mitkIntensityProfile.h" using namespace mitk; template static void ReadPixel(const PixelType&, Image::Pointer image, const itk::Index<3>& index, ScalarType* returnValue) { switch (image->GetDimension()) { case 2: { ImagePixelReadAccessor readAccess(image, image->GetSliceData(0)); *returnValue = readAccess.GetPixelByIndex(reinterpret_cast&>(index)); break; } case 3: { ImagePixelReadAccessor readAccess(image, image->GetVolumeData(0)); *returnValue = readAccess.GetPixelByIndex(index); break; } default: *returnValue = 0; break; } } static IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, itk::PolyLineParametricPath<3>::Pointer path) { IntensityProfile::Pointer intensityProfile = IntensityProfile::New(); itk::PolyLineParametricPath<3>::InputType input = path->StartOfInput(); BaseGeometry* imageGeometry = image->GetGeometry(); const PixelType pixelType = image->GetPixelType(); IntensityProfile::MeasurementVectorType measurementVector; itk::PolyLineParametricPath<3>::OffsetType offset; Point3D worldPoint; itk::Index<3> index; do { imageGeometry->IndexToWorld(path->Evaluate(input), worldPoint); imageGeometry->WorldToIndex(worldPoint, index); mitkPixelTypeMultiplex3(ReadPixel, pixelType, image, index, measurementVector.GetDataPointer()); intensityProfile->PushBack(measurementVector); offset = path->IncrementInput(input); } while ((offset[0] | offset[1] | offset[2]) != 0); return intensityProfile; } template static typename itk::InterpolateImageFunction::Pointer CreateInterpolateImageFunction(InterpolateImageFunction::Enum interpolator) { switch (interpolator) { case InterpolateImageFunction::NearestNeighbor: return itk::NearestNeighborInterpolateImageFunction::New().GetPointer(); case InterpolateImageFunction::Linear: return itk::LinearInterpolateImageFunction::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Blackman_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Blackman_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Blackman_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Cosine_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Cosine_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Cosine_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Hamming_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Hamming_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Hamming_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Lanczos_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Lanczos_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Lanczos_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Welch_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Welch_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Welch_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); default: return itk::NearestNeighborInterpolateImageFunction::New().GetPointer(); } } template static void ComputeIntensityProfile(itk::Image* image, itk::PolyLineParametricPath<3>::Pointer path, unsigned int numSamples, InterpolateImageFunction::Enum interpolator, IntensityProfile::Pointer intensityProfile) { typename itk::InterpolateImageFunction >::Pointer interpolateImageFunction = CreateInterpolateImageFunction >(interpolator); interpolateImageFunction->SetInputImage(image); const itk::PolyLineParametricPath<3>::InputType startOfInput = path->StartOfInput(); const itk::PolyLineParametricPath<3>::InputType delta = 1.0 / (numSamples - 1); IntensityProfile::MeasurementVectorType measurementVector; for (unsigned int i = 0; i < numSamples; ++i) { measurementVector[0] = interpolateImageFunction->EvaluateAtContinuousIndex(path->Evaluate(startOfInput + i * delta)); intensityProfile->PushBack(measurementVector); } } static IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, itk::PolyLineParametricPath<3>::Pointer path, unsigned int numSamples, InterpolateImageFunction::Enum interpolator) { IntensityProfile::Pointer intensityProfile = IntensityProfile::New(); AccessFixedDimensionByItk_n(image, ComputeIntensityProfile, 3, (path, numSamples, interpolator, intensityProfile)); return intensityProfile; } class AddPolyLineElementToPath { public: AddPolyLineElementToPath(const PlaneGeometry* planarFigureGeometry, const BaseGeometry* imageGeometry, itk::PolyLineParametricPath<3>::Pointer path) : m_PlanarFigureGeometry(planarFigureGeometry), m_ImageGeometry(imageGeometry), m_Path(path) { } void operator()(const PlanarFigure::PolyLineElement& polyLineElement) { m_PlanarFigureGeometry->Map(polyLineElement, m_WorldPoint); m_ImageGeometry->WorldToIndex(m_WorldPoint, m_ContinuousIndexPoint); m_Vertex.CastFrom(m_ContinuousIndexPoint); m_Path->AddVertex(m_Vertex); } private: const PlaneGeometry* m_PlanarFigureGeometry; const BaseGeometry* m_ImageGeometry; itk::PolyLineParametricPath<3>::Pointer m_Path; Point3D m_WorldPoint; Point3D m_ContinuousIndexPoint; itk::PolyLineParametricPath<3>::ContinuousIndexType m_Vertex; }; static itk::PolyLineParametricPath<3>::Pointer CreatePathFromPlanarFigure(BaseGeometry* imageGeometry, PlanarFigure* planarFigure) { itk::PolyLineParametricPath<3>::Pointer path = itk::PolyLineParametricPath<3>::New(); const PlanarFigure::PolyLineType polyLine = planarFigure->GetPolyLine(0); std::for_each(polyLine.begin(), polyLine.end(), AddPolyLineElementToPath(planarFigure->GetPlaneGeometry(), imageGeometry, path)); return path; } static void AddPointToPath(const BaseGeometry* imageGeometry, const Point3D& point, itk::PolyLineParametricPath<3>::Pointer path) { Point3D continuousIndexPoint; imageGeometry->WorldToIndex(point, continuousIndexPoint); itk::PolyLineParametricPath<3>::ContinuousIndexType vertex; vertex.CastFrom(continuousIndexPoint); path->AddVertex(vertex); } static itk::PolyLineParametricPath<3>::Pointer CreatePathFromPoints(BaseGeometry* imageGeometry, const Point3D& startPoint, const Point3D& endPoint) { itk::PolyLineParametricPath<3>::Pointer path = itk::PolyLineParametricPath<3>::New(); AddPointToPath(imageGeometry, startPoint, path); AddPointToPath(imageGeometry, endPoint, path); return path; } IntensityProfile::Pointer mitk::ComputeIntensityProfile(Image::Pointer image, PlanarFigure::Pointer planarFigure) { return ::ComputeIntensityProfile(image, CreatePathFromPlanarFigure(image->GetGeometry(), planarFigure)); } IntensityProfile::Pointer mitk::ComputeIntensityProfile(Image::Pointer image, PlanarLine::Pointer planarLine, unsigned int numSamples, InterpolateImageFunction::Enum interpolator) { return ::ComputeIntensityProfile(image, CreatePathFromPlanarFigure(image->GetGeometry(), planarLine.GetPointer()), numSamples, interpolator); } IntensityProfile::Pointer mitk::ComputeIntensityProfile(Image::Pointer image, const Point3D& startPoint, const Point3D& endPoint, unsigned int numSamples, InterpolateImageFunction::Enum interpolator) { return ::ComputeIntensityProfile(image, CreatePathFromPoints(image->GetGeometry(), startPoint, endPoint), numSamples, interpolator); } IntensityProfile::InstanceIdentifier mitk::ComputeGlobalMaximum(IntensityProfile::Pointer intensityProfile, IntensityProfile::MeasurementType &max) { max = -vcl_numeric_limits::min(); IntensityProfile::InstanceIdentifier maxIndex = 0; IntensityProfile::ConstIterator end = intensityProfile->End(); IntensityProfile::MeasurementType measurement; for (IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) { measurement = it.GetMeasurementVector()[0]; if (measurement > max) { max = measurement; maxIndex = it.GetInstanceIdentifier(); } } return maxIndex; } IntensityProfile::InstanceIdentifier mitk::ComputeGlobalMinimum(IntensityProfile::Pointer intensityProfile, IntensityProfile::MeasurementType &min) { min = vcl_numeric_limits::max(); IntensityProfile::InstanceIdentifier minIndex = 0; IntensityProfile::ConstIterator end = intensityProfile->End(); IntensityProfile::MeasurementType measurement; for (IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) { measurement = it.GetMeasurementVector()[0]; if (measurement < min) { min = measurement; minIndex = it.GetInstanceIdentifier(); } } return minIndex; } IntensityProfile::InstanceIdentifier mitk::ComputeCenterOfMaximumArea(IntensityProfile::Pointer intensityProfile, IntensityProfile::InstanceIdentifier radius) { //const IntensityProfile::MeasurementType min = intensityProfile->GetMeasurementVector(ComputeGlobalMinimum(intensityProfile))[0]; IntensityProfile::MeasurementType min; ComputeGlobalMinimum(intensityProfile, min); const IntensityProfile::InstanceIdentifier areaWidth = 1 + 2 * radius; IntensityProfile::MeasurementType maxArea = 0; for (IntensityProfile::InstanceIdentifier i = 0; i < areaWidth; ++i) maxArea += intensityProfile->GetMeasurementVector(i)[0] - min; const IntensityProfile::InstanceIdentifier lastIndex = intensityProfile->Size() - areaWidth; IntensityProfile::InstanceIdentifier centerOfMaxArea = radius; IntensityProfile::MeasurementType area = maxArea; for (IntensityProfile::InstanceIdentifier i = 1; i <= lastIndex; ++i) { area += intensityProfile->GetMeasurementVector(i + areaWidth - 1)[0] - min; area -= intensityProfile->GetMeasurementVector(i - 1)[0] - min; if (area > maxArea) { maxArea = area; centerOfMaxArea = i + radius; // TODO: If multiple areas in the neighborhood have the same intensity chose the middle one instead of the first one. } } return centerOfMaxArea; } std::vector mitk::CreateVectorFromIntensityProfile(IntensityProfile::Pointer intensityProfile) { std::vector result; result.reserve(intensityProfile->Size()); IntensityProfile::ConstIterator end = intensityProfile->End(); for (IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) result.push_back(it.GetMeasurementVector()[0]); return result; } IntensityProfile::Pointer mitk::CreateIntensityProfileFromVector(const std::vector& vector) { const IntensityProfile::InstanceIdentifier size = vector.size(); IntensityProfile::Pointer result = IntensityProfile::New(); result->Resize(size); for (IntensityProfile::InstanceIdentifier i = 0; i < size; ++i) result->SetMeasurement(i, 0, vector[i]); return result; } -void mitk::ComputeIntensityProfileStatistics(IntensityProfile::Pointer intensityProfile, ImageStatisticsCalculator::Statistics &stats) +void mitk::ComputeIntensityProfileStatistics(IntensityProfile::Pointer intensityProfile, ImageStatisticsCalculator::StatisticsContainer::Pointer stats) { typedef std::vector StatsVecType; StatsVecType statsVec = mitk::CreateVectorFromIntensityProfile( intensityProfile ); IntensityProfile::MeasurementType min; IntensityProfile::MeasurementType max; mitk::ComputeGlobalMinimum( intensityProfile, min ); mitk::ComputeGlobalMaximum( intensityProfile, max ); StatsVecType::size_type numSamples = statsVec.size(); double mean = 0.0; double rms = 0.0; for ( StatsVecType::const_iterator it = statsVec.begin(); it != statsVec.end(); ++it ) { double val = *it; mean += val; rms += val*val; } mean /= numSamples; rms /= numSamples; double var = 0.0; for ( StatsVecType::const_iterator it = statsVec.begin(); it != statsVec.end(); ++it ) { double diff = *it - mean; var += diff*diff; } var /= ( numSamples - 1 ); rms = sqrt( rms ); - stats.SetMin( static_cast( min ) ); - stats.SetMax( static_cast( max ) ); - stats.SetN( numSamples ); - stats.SetMean( mean ); - stats.SetVariance( var ); - stats.SetRMS( rms ); + stats->SetMin( static_cast( min ) ); + stats->SetMax( static_cast( max ) ); + stats->SetN( numSamples ); + stats->SetMean( mean ); + stats->SetVariance( var ); + stats->SetRMS( rms ); } diff --git a/Modules/ImageStatistics/mitkIntensityProfile.h b/Modules/ImageStatistics/mitkIntensityProfile.h index c0ba4bb96d..04afd64c47 100644 --- a/Modules/ImageStatistics/mitkIntensityProfile.h +++ b/Modules/ImageStatistics/mitkIntensityProfile.h @@ -1,137 +1,137 @@ /*=================================================================== 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 mitkIntensityProfile_h #define mitkIntensityProfile_h #include #include #include #include #include namespace mitk { typedef itk::Statistics::ListSample::MeasurementVectorType> IntensityProfile; /** \brief Compute intensity profile of an image for each pixel along the first PolyLine of a given planar figure. * * \param[in] image A two or three-dimensional image which consists of single component pixels. * \param[in] planarFigure A planar figure from which the first PolyLine is used to evaluate the intensity profile. * * \return The computed intensity profile. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, PlanarFigure::Pointer planarFigure); namespace InterpolateImageFunction { enum Enum { NearestNeighbor, Linear, WindowedSinc_Blackman_3, WindowedSinc_Blackman_4, WindowedSinc_Blackman_5, WindowedSinc_Cosine_3, WindowedSinc_Cosine_4, WindowedSinc_Cosine_5, WindowedSinc_Hamming_3, WindowedSinc_Hamming_4, WindowedSinc_Hamming_5, WindowedSinc_Lanczos_3, WindowedSinc_Lanczos_4, WindowedSinc_Lanczos_5, WindowedSinc_Welch_3, WindowedSinc_Welch_4, WindowedSinc_Welch_5 }; } /** \brief Compute intensity profile of an image for each sample along a planar line. * * \param[in] image A three-dimensional image which consists of single component pixels. * \param[in] planarLine A planar line along which the intensity profile will be evaluated. * \param[in] numSamples Number of samples along the planar line (must be at least 2). * \param[in] interpolator Image interpolation function which is used to read each sample. * * \return The computed intensity profile. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, PlanarLine::Pointer planarLine, unsigned int numSamples, InterpolateImageFunction::Enum interpolator = InterpolateImageFunction::NearestNeighbor); /** \brief Compute intensity profile of an image for each sample between two points. * * \param[in] image A three-dimensional image which consists of single component pixels. * \param[in] startPoint A point at which the first sample is to be read. * \param[in] endPoint A point at which the last sample is to be read. * \param[in] numSamples Number of samples between startPoint and endPoint (must be at least 2). * \param[in] interpolator Image interpolation function which is used to read each sample. * * \return The computed intensity profile. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, const Point3D& startPoint, const Point3D& endPoint, unsigned int numSamples, InterpolateImageFunction::Enum interpolator = InterpolateImageFunction::NearestNeighbor); /** \brief Compute global maximum of an intensity profile. * * \param[in] intensityProfile An intensity profile. * * \return Index of the global maximum. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::InstanceIdentifier ComputeGlobalMaximum(IntensityProfile::Pointer intensityProfile, IntensityProfile::MeasurementType &max); /** \brief Compute global minimum of an intensity profile. * * \param[in] intensityProfile An intensity profile. * * \return Index of the global minimum. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::InstanceIdentifier ComputeGlobalMinimum(IntensityProfile::Pointer intensityProfile, IntensityProfile::MeasurementType &min); /** \brief Compute statistics of an intensity profile. * * \param[in] intensityProfile An intensity profile. * * \param[in] stats An ImageStatisticsCalculator::Statistics object to hold the calculated statistics. * */ - MITKIMAGESTATISTICS_EXPORT void ComputeIntensityProfileStatistics(IntensityProfile::Pointer intensityProfile, ImageStatisticsCalculator::Statistics &stats); + MITKIMAGESTATISTICS_EXPORT void ComputeIntensityProfileStatistics(IntensityProfile::Pointer intensityProfile, ImageStatisticsCalculator::StatisticsContainer::Pointer stats); /** \brief Compute center of maximum area under the curve of an intensity profile. * * \param[in] intensityProfile An intensity profile. * \param[in] radius Radius of the area (width of area equals 1 + 2 * radius). * * \return Index of the maximum area center. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::InstanceIdentifier ComputeCenterOfMaximumArea(IntensityProfile::Pointer intensityProfile, IntensityProfile::InstanceIdentifier radius); /** \brief Convert an intensity profile to a standard library vector. * * \param[in] intensityProfile An intensity profile. * * \return Standard library vector which contains the input intensity profile measurements. */ MITKIMAGESTATISTICS_EXPORT std::vector CreateVectorFromIntensityProfile(IntensityProfile::Pointer intensityProfile); /** \brief Convert a standard library vector to an intensity profile. * * \param[in] vector An standard library vector which contains intensity profile measurements. * * \return An intensity profile. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::Pointer CreateIntensityProfileFromVector(const std::vector& vector); } #endif diff --git a/Modules/ImageStatistics/mitkMaskGenerator.cpp b/Modules/ImageStatistics/mitkMaskGenerator.cpp new file mode 100644 index 0000000000..81c3e3a7aa --- /dev/null +++ b/Modules/ImageStatistics/mitkMaskGenerator.cpp @@ -0,0 +1,51 @@ +#include + +namespace mitk +{ +void MaskGenerator::SetTimeStep(unsigned int timeStep) +{ + if (m_TimeStep != timeStep) + { + m_TimeStep = timeStep; + this->Modified(); + } +} + +MaskGenerator::MaskGenerator(): + m_TimeStep(0) +{ + m_inputImage = nullptr; +} + +mitk::Image::Pointer MaskGenerator::GetMask() +{ + return mitk::Image::New(); +} + +//typename itk::Region<3>::Pointer MaskGenerator::GetImageRegionOfMask(Image::Pointer image) +//{ +// if (m_InternalMask.IsNull() || m_Modified) +// { +// MITK_ERROR << "Update MaskGenerator first!"; +// } + +// mitk::BaseGeometry::Pointer imageGeometry = image->GetGeometry(); +// mitk::BaseGeometry::Pointer maskGeometry = m_InternalMask->GetGeometry(); + + +//} + +void MaskGenerator::SetInputImage(mitk::Image::Pointer inputImg) +{ + if (inputImg != m_inputImage) + { + m_inputImage = inputImg; + this->Modified(); + } +} + +mitk::Image::Pointer MaskGenerator::GetReferenceImage() +{ + return m_inputImage; +} +} diff --git a/Modules/ImageStatistics/mitkMaskGenerator.h b/Modules/ImageStatistics/mitkMaskGenerator.h new file mode 100644 index 0000000000..627c7bc4e1 --- /dev/null +++ b/Modules/ImageStatistics/mitkMaskGenerator.h @@ -0,0 +1,70 @@ +#ifndef MITKMASKGENERATOR +#define MITKMASKGENERATOR + +#include +#include +#include +#include +#include + +namespace mitk +{ +/** +* \class MaskGenerator +* \brief Base Class for all Mask Generators. Mask generators are classes that provide functionality for the +* creation of binary (or unsigned short) masks that can be applied to an image. See dervied classes for more +* information. +*/ +class MITKIMAGESTATISTICS_EXPORT MaskGenerator: public itk::Object +{ +public: + /** Standard Self typedef */ + typedef MaskGenerator Self; + typedef itk::Object Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(MaskGenerator, itk::Object) + + //~MaskGenerator(); + + /** + * @brief GetMask must be overridden by derived classes. + * @return mitk::Image::Pointer of generated mask + */ + virtual mitk::Image::Pointer GetMask(); + + /** + * @brief SetTimeStep is used to set the time step for which the mask is to be generated + * @param timeStep + */ + void SetTimeStep(unsigned int timeStep); + + /** + * @brief GetReferenceImage per default returns the inputImage (as set by SetInputImage). If no input image is set it will return a nullptr. + */ + virtual mitk::Image::Pointer GetReferenceImage(); + + /** + * @brief SetInputImage is used to set the input image to the mask generator. Some subclasses require an input image, others don't. See the documentation of the specific Mask Generator for more information. + */ + void SetInputImage(mitk::Image::Pointer inputImg); + +protected: + MaskGenerator(); + + unsigned int m_TimeStep; + mitk::Image::Pointer m_InternalMask; + mitk::Image::Pointer m_inputImage; + +private: + +}; +} + +#endif // MITKMASKGENERATOR + diff --git a/Modules/ImageStatistics/mitkMaskUtilities.cpp b/Modules/ImageStatistics/mitkMaskUtilities.cpp new file mode 100644 index 0000000000..38f82022ac --- /dev/null +++ b/Modules/ImageStatistics/mitkMaskUtilities.cpp @@ -0,0 +1,189 @@ +#ifndef MITKMASKUTIL_CPP +#define MITKMASKUTIL_CPP + +#include +//#include +#include +#include +#include +#include + +namespace mitk +{ + template + void MaskUtilities::SetImage(ImageType* image) + { + if (image != m_Image) + { + m_Image = image; + } + } + + template + void MaskUtilities::SetMask(MaskType* mask) + { + if (mask != m_Mask) + { + m_Mask = mask; + } + } + + template + bool MaskUtilities::CheckMaskSanity() + { + if (m_Mask==nullptr || m_Image==nullptr) + { + MITK_ERROR << "Set an image and a mask first"; + } + + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef typename ImageType::PointType PointType; + typedef typename ImageType::DirectionType DirectionType; + + bool maskSanity = true; + + + if (m_Mask==nullptr) + { + MITK_ERROR << "Something went wrong when casting the mitk mask image to an itk mask image. Do the mask and the input image have the same dimension?"; + // note to self: We could try to convert say a 2d mask to a 3d mask if the image is 3d. (mask and image dimension have to match.) + } + // check direction + DirectionType imageDirection = m_Image->GetDirection(); + DirectionType maskDirection = m_Mask->GetDirection(); + for( int i = 0; i < imageDirection.ColumnDimensions; ++i ) + { + for( int j = 0; j < imageDirection.ColumnDimensions; ++j ) + { + double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; + if ( fabs( differenceDirection ) > mitk::eps ) + { + double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; + if ( fabs( differenceDirection ) > 0.001 /*mitk::eps*/ ) // TODO: temp fix (bug 17121) + { + maskSanity = false; + MITK_INFO << "Mask needs to have same direction as image! (Image direction: " << imageDirection << "; Mask direction: " << maskDirection << ")"; + } + } + } + } + + // check spacing + PointType imageSpacing = m_Image->GetSpacing(); + PointType maskSpacing = m_Mask->GetSpacing(); + for (unsigned int i = 0; i < VImageDimension; i++) + { + if ( fabs( maskSpacing[i] - imageSpacing[i] ) > mitk::eps ) + { + maskSanity = false; + MITK_INFO << "Spacing of mask and image is not equal. Mask: " << maskSpacing << " image: " << imageSpacing; + } + } + + // check alignment + // Make sure that the voxels of mask and image are correctly "aligned", i.e., voxel boundaries are the same in both images + PointType imageOrigin = m_Image->GetOrigin(); + PointType maskOrigin = m_Mask->GetOrigin(); + + typedef itk::ContinuousIndex ContinousIndexType; + ContinousIndexType maskOriginContinousIndex, imageOriginContinousIndex; + + m_Image->TransformPhysicalPointToContinuousIndex(maskOrigin, maskOriginContinousIndex); + m_Image->TransformPhysicalPointToContinuousIndex(imageOrigin, imageOriginContinousIndex); + + for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) + { + double misalignment = maskOriginContinousIndex[i] - floor( maskOriginContinousIndex[i] + 0.5 ); + // misalignment must be a multiple (int) of spacing in that direction + if ( fmod(misalignment,imageSpacing[i]) > mitk::eps ) + { + maskSanity = false; + MITK_INFO << "Pixels/voxels of mask and image are not sufficiently aligned! (Misalignment: " << fmod(misalignment,imageSpacing[i]) << ")"; + } + } + + // mask must be completely inside image region + // Make sure that mask region is contained within image region + if ( m_Mask!=nullptr && + !m_Image->GetLargestPossibleRegion().IsInside( m_Mask->GetLargestPossibleRegion() ) ) + { + maskSanity = false; + MITK_INFO << "Mask region needs to be inside of image region! (Image region: " + << m_Image->GetLargestPossibleRegion() << "; Mask region: " << m_Mask->GetLargestPossibleRegion() << ")"; + } + return maskSanity; + } + + template + typename itk::Image::Pointer MaskUtilities::ExtractMaskImageRegion() + { + if (m_Mask==nullptr || m_Image==nullptr) + { + MITK_ERROR << "Set an image and a mask first"; + } + + bool maskSanity = CheckMaskSanity(); + + if (!maskSanity) + { + MITK_ERROR << "Mask and image are not compatible"; + } + + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef itk::Image< unsigned short, VImageDimension > MaskType; + typedef itk::ExtractImageFilter< ImageType, ImageType > ExtractImageFilterType; + + typename ImageType::SizeType imageSize = m_Image->GetBufferedRegion().GetSize(); + typename ImageType::SizeType maskSize = m_Mask->GetBufferedRegion().GetSize(); + + typename itk::Image::Pointer extractedImg = itk::Image::New(); + + bool maskSmallerImage = false; + for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) + { + if ( maskSize[i] < imageSize[i] ) + { + maskSmallerImage = true; + } + } + + if ( maskSmallerImage ) + { + typename ExtractImageFilterType::Pointer extractImageFilter = ExtractImageFilterType::New(); + typename MaskType::PointType maskOrigin = m_Mask->GetOrigin(); + typename ImageType::PointType imageOrigin = m_Image->GetOrigin(); + typename MaskType::SpacingType maskSpacing = m_Mask->GetSpacing(); + typename ImageType::RegionType extractionRegion; + typename ImageType::IndexType extractionRegionIndex; + + + for (unsigned int i=0; i < maskOrigin.GetPointDimension(); i++) + { + extractionRegionIndex[i] = (maskOrigin[i] - imageOrigin[i]) / maskSpacing[i]; + } + + extractionRegion.SetIndex(extractionRegionIndex); + extractionRegion.SetSize(m_Mask->GetLargestPossibleRegion().GetSize()); + + extractImageFilter->SetInput( m_Image ); + extractImageFilter->SetExtractionRegion( extractionRegion ); + extractImageFilter->SetCoordinateTolerance( 0.001 ); + extractImageFilter->SetDirectionTolerance( 0.001 ); + extractImageFilter->Update(); + extractedImg = extractImageFilter->GetOutput(); + extractedImg->SetOrigin(m_Mask->GetOrigin()); + extractedImg->SetLargestPossibleRegion(m_Mask->GetLargestPossibleRegion()); + extractedImg->SetBufferedRegion(m_Mask->GetBufferedRegion()); + + } + else + { + extractedImg = m_Image; + } + + return extractedImg; + } + +} + +#endif diff --git a/Modules/ImageStatistics/mitkMaskUtilities.h b/Modules/ImageStatistics/mitkMaskUtilities.h new file mode 100644 index 0000000000..23d2b6e9fc --- /dev/null +++ b/Modules/ImageStatistics/mitkMaskUtilities.h @@ -0,0 +1,69 @@ +#ifndef MITKMASKUTIL +#define MITKMASKUTIL + +#include +#include +#include + +namespace mitk +{ +/** + * @brief Utility class for mask operations. It checks whether an image and a mask are compatible (spacing, orientation, etc...) + * and it can also crop an image to the LargestPossibleRegion of the Mask + */ +template +class MITKIMAGESTATISTICS_EXPORT MaskUtilities: public itk::Object + { + public: + /** Standard Self typedef */ + typedef MaskUtilities Self; + typedef itk::Object Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(MaskUtilities, itk::Object) + + typedef itk::Image ImageType; + typedef itk::Image MaskType; + + /** + * @brief Set image + */ + void SetImage(ImageType* image); + + /** + * @brief Set mask + */ + void SetMask(MaskType* mask); + + /** + * @brief Checks whether mask and image are compatible for joint access (as via iterators). + * Spacing and direction must be the same between the two and they must be aligned. Also, the mask must be completely inside the image + */ + bool CheckMaskSanity(); + + /** + * @brief Crops the image to the LargestPossibleRegion of the mask + */ + typename itk::Image::Pointer ExtractMaskImageRegion(); + + protected: + MaskUtilities(){} + + ~MaskUtilities(){} + + private: + itk::Image* m_Image; + itk::Image* m_Mask; + }; +} + +#ifndef ITK_MANUAL_INSTANTIATION +#include +#endif + +#endif diff --git a/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.h b/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.h new file mode 100644 index 0000000000..b5768f4cd4 --- /dev/null +++ b/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.h @@ -0,0 +1,84 @@ +#ifndef MITK_MINMAXIMAGEFILTERWITHINDEX_H +#define MITK_MINMAXIMAGEFILTERWITHINDEX_H + +#include + +#include +#include +#include + + + +namespace itk +{ +template +class MinMaxImageFilterWithIndex: public itk::ImageToImageFilter +{ +public: + /** Standard Self typedef */ + typedef MinMaxImageFilterWithIndex Self; + typedef ImageToImageFilter< TInputImage, TInputImage > Superclass; + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkTypeMacro(MinMaxImageFilterWithIndex, ImageToImageFilter); + + typedef typename TInputImage::RegionType RegionType; + typedef typename TInputImage::SizeType SizeType; + typedef typename TInputImage::IndexType IndexType; + typedef typename TInputImage::PixelType PixelType; + typedef typename NumericTraits< PixelType >::RealType RealType; + + + RealType GetMin() const + { + return m_Min; + } + + RealType GetMax() const + { + return m_Max; + } + + IndexType GetMinIndex() const + { + return m_MinIndex; + } + + IndexType GetMaxIndex() const + { + return m_MaxIndex; + } + +protected: + void AllocateOutputs(); + + void ThreadedGenerateData(const RegionType & + outputRegionForThread, + ThreadIdType threadId); + + void BeforeThreadedGenerateData(); + + void AfterThreadedGenerateData(); + +private: + std::vector m_ThreadMin; + std::vector m_ThreadMax; + std::vector m_ThreadMinIndex; + std::vector m_ThreadMaxIndex; + + PixelType m_Min; + PixelType m_Max; + IndexType m_MinIndex; + IndexType m_MaxIndex; +}; +} + +#include "mitkMinMaxImageFilterWithIndex.hxx" + + +#endif diff --git a/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.hxx b/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.hxx new file mode 100644 index 0000000000..c925cb5aa4 --- /dev/null +++ b/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.hxx @@ -0,0 +1,106 @@ +#ifndef MITK_MinMaxImageFilterWithIndex_HXX +#define MITK_MinMaxImageFilterWithIndex_HXX + +#include +#include + +namespace itk +{ + + +template< typename TInputImage > +void MinMaxImageFilterWithIndex< TInputImage >::AllocateOutputs() +{ + // Pass the input through as the output + typename TInputImage::Pointer image = + const_cast< TInputImage * >( this->GetInput() ); + + this->GraftOutput(image); + + // Nothing that needs to be allocated for the remaining outputs +} + +template< typename TInputImage > +void MinMaxImageFilterWithIndex< TInputImage >::ThreadedGenerateData(const RegionType & + outputRegionForThread, + ThreadIdType threadId) +{ + const SizeValueType size0 = outputRegionForThread.GetSize(0); + if( size0 == 0) + { + return; + } + PixelType value; + + PixelType threadMin, threadMax; + IndexType threadMinIndex, threadMaxIndex; + + threadMin = std::numeric_limits::max(); + threadMax = std::numeric_limits::min(); + + ImageRegionConstIteratorWithIndex< TInputImage > it (this->GetInput(), outputRegionForThread); + + // do the work + while ( !it.IsAtEnd() ) + { + value = it.Get(); + if (value < threadMin) + { + threadMin = value; + threadMinIndex = it.GetIndex(); + } + if (value > threadMax) + { + threadMax = value; + threadMaxIndex = it.GetIndex(); + } + ++it; + } + + m_ThreadMax[threadId] = threadMax; + m_ThreadMin[threadId] = threadMin; + m_ThreadMaxIndex[threadId] = threadMaxIndex; + m_ThreadMinIndex[threadId] = threadMinIndex; +} + +template< typename TInputImage > +void MinMaxImageFilterWithIndex< TInputImage >::BeforeThreadedGenerateData() +{ + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + m_ThreadMin.resize(numberOfThreads); + m_ThreadMax.resize(numberOfThreads); + m_ThreadMinIndex.resize(numberOfThreads); + m_ThreadMaxIndex.resize(numberOfThreads); + + for (unsigned int i =0; i < numberOfThreads; i++) + { + m_ThreadMin[i] = std::numeric_limits::max(); + m_ThreadMax[i] = std::numeric_limits::min(); + } + + m_Min = std::numeric_limits::max(); + m_Max = std::numeric_limits::min(); + +} + +template< typename TInputImage > +void MinMaxImageFilterWithIndex< TInputImage >::AfterThreadedGenerateData() +{ + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + for (ThreadIdType i = 0; i < numberOfThreads; i++) + { + if (m_ThreadMin[i] < m_Min) + { + m_Min = m_ThreadMin[i]; + m_MinIndex = m_ThreadMinIndex[i]; + } + if (m_ThreadMax[i] > m_Max) + { + m_Max = m_ThreadMax[i]; + m_MaxIndex = m_ThreadMaxIndex[i]; + } + } +} +} +#endif diff --git a/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.h b/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.h new file mode 100644 index 0000000000..e0c4158777 --- /dev/null +++ b/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.h @@ -0,0 +1,177 @@ +#ifndef MITK_MINMAXLABELIMAGEFILTERWITHINDEX_H +#define MITK_MINMAXLABELIMAGEFILTERWITHINDEX_H + +#include + +#include +#include +#include +#include "itksys/hash_map.hxx" + + +namespace itk +{ +template +class MinMaxLabelImageFilterWithIndex: public itk::ImageToImageFilter +{ +public: + /** Standard Self typedef */ + typedef MinMaxLabelImageFilterWithIndex Self; + typedef ImageToImageFilter< TInputImage, TInputImage > Superclass; + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkTypeMacro(MinMaxLabelImageFilterWithIndex, ImageToImageFilter); + + typedef typename TInputImage::RegionType RegionType; + typedef typename TInputImage::SizeType SizeType; + typedef typename TInputImage::IndexType IndexType; + typedef typename TInputImage::PixelType PixelType; + typedef typename NumericTraits< PixelType >::RealType RealType; + + typedef typename TLabelImage::RegionType LabelRegionType; + typedef typename TLabelImage::SizeType LabelSizeType; + typedef typename TLabelImage::IndexType LabelIndexType; + typedef typename TLabelImage::PixelType LabelPixelType; + + /** + * @brief The LabelExtrema class is just a container for global min/max values and their indices as well as all min and max values (+indices) of the mask labels + */ + class LabelExtrema + { + public: + PixelType m_Min, m_Max; + IndexType m_MinIndex, m_MaxIndex; + + LabelExtrema(): + m_Min(std::numeric_limits::max()), + m_Max(std::numeric_limits::min()) + {} + }; + + typedef typename itksys::hash_map ExtremaMapType; + typedef typename ExtremaMapType::iterator ExtremaMapTypeIterator; + typedef typename ExtremaMapType::const_iterator ExtremaMapTypeConstIterator; + typedef typename ExtremaMapType::value_type MapValueType; + + PixelType GetMin(LabelPixelType label) const + { + ExtremaMapTypeConstIterator it = m_LabelExtrema.find(label); + if (it == m_LabelExtrema.end()) + { + MITK_ERROR << "invalid label"; + } + + return (*it).second.m_Min; + } + + PixelType GetMax(LabelPixelType label) const + { + ExtremaMapTypeConstIterator it = m_LabelExtrema.find(label); + if (it == m_LabelExtrema.end()) + { + MITK_ERROR << "invalid label"; + } + + return (*it).second.m_Max; + } + + /** + * @brief Returns a std::vector containing all labels for which min and max values (and indices) have been computed + */ + std::vector GetRelevantLabels() const + { + std::vector labels; + for (auto&& it:m_LabelExtrema) + { + labels.push_back(it.first); + } + return labels; + } + + IndexType GetMinIndex(LabelPixelType label) const + { + ExtremaMapTypeConstIterator it = m_LabelExtrema.find(label); + if (it == m_LabelExtrema.end()) + { + MITK_ERROR << "invalid label"; + } + + return (*it).second.m_MinIndex; + + } + + IndexType GetMaxIndex(LabelPixelType label) const + { + ExtremaMapTypeConstIterator it = m_LabelExtrema.find(label); + if (it == m_LabelExtrema.end()) + { + MITK_ERROR << "invalid label"; + } + + return (*it).second.m_MaxIndex; + + } + + PixelType GetGlobalMin() const + { + return m_GlobalMin; + } + + PixelType GetGlobalMax() const + { + return m_GlobalMax; + } + + IndexType GetGlobalMinIndex() const + { + return m_GlobalMinIndex; + } + + IndexType GetGlobalMaxIndex() const + { + return m_GlobalMaxIndex; + } + + /** Set the label image */ + void SetLabelInput(const TLabelImage *input) + { + // Process object is not const-correct so the const casting is required. + this->SetNthInput( 1, const_cast< TLabelImage * >( input ) ); + } + + /** Get the label image */ + const TLabelImage * GetLabelInput() const + { + return itkDynamicCastInDebugMode< TLabelImage * >( const_cast< DataObject * >( this->ProcessObject::GetInput(1) ) ); + } + +protected: + void AllocateOutputs(); + + void ThreadedGenerateData(const RegionType & + outputRegionForThread, + ThreadIdType threadId); + + void BeforeThreadedGenerateData(); + + void AfterThreadedGenerateData(); + +private: + std::vector m_ThreadExtrema; + + ExtremaMapType m_LabelExtrema; + PixelType m_GlobalMin; + PixelType m_GlobalMax; + IndexType m_GlobalMinIndex, m_GlobalMaxIndex; +}; +} + +#include "mitkMinMaxLabelmageFilterWithIndex.hxx" + + +#endif diff --git a/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.hxx b/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.hxx new file mode 100644 index 0000000000..c1a0af7ae2 --- /dev/null +++ b/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.hxx @@ -0,0 +1,130 @@ +#ifndef MITK_MinMaxLabelImageFilterWithIndex_HXX +#define MITK_MinMaxLabelImageFilterWithIndex_HXX + +#include +#include + +namespace itk +{ + + +template< typename TInputImage, typename TLabelImage > +void MinMaxLabelImageFilterWithIndex< TInputImage, TLabelImage >::AllocateOutputs() +{ + // Pass the input through as the output + typename TInputImage::Pointer image = + const_cast< TInputImage * >( this->GetInput() ); + + this->GraftOutput(image); + + // Nothing that needs to be allocated for the remaining outputs +} + +template< typename TInputImage, typename TLabelImage > +void MinMaxLabelImageFilterWithIndex< TInputImage, TLabelImage >::ThreadedGenerateData(const RegionType & + outputRegionForThread, + ThreadIdType threadId) +{ + const SizeValueType size0 = outputRegionForThread.GetSize(0); + if( size0 == 0) + { + return; + } + PixelType value; + LabelPixelType label; + + ExtremaMapType threadExtrema; + ExtremaMapTypeIterator threadExtremaIt; + + ImageRegionConstIteratorWithIndex< TInputImage > it (this->GetInput(), outputRegionForThread); + ImageRegionConstIteratorWithIndex< TLabelImage > labelit (this->GetLabelInput(), outputRegionForThread); + + // do the work + while ( !it.IsAtEnd() ) + { + value = it.Get(); + label = labelit.Get(); + + threadExtremaIt = threadExtrema.find(label); + + // if label does not exist yet, create a new entry in the map. + if (threadExtremaIt == threadExtrema.end()) + { + threadExtremaIt = threadExtrema.insert( MapValueType(label, LabelExtrema()) ).first; + } + + if (value < (*threadExtremaIt).second.m_Min) + { + (*threadExtremaIt).second.m_Min = value; + (*threadExtremaIt).second.m_MinIndex = it.GetIndex(); + } + if (value > (*threadExtremaIt).second.m_Max) + { + (*threadExtremaIt).second.m_Max = value; + (*threadExtremaIt).second.m_MaxIndex = it.GetIndex(); + } + ++it; + ++labelit; + } + + m_ThreadExtrema[threadId] = threadExtrema; +} + +template< typename TInputImage, typename TLabelImage > +void MinMaxLabelImageFilterWithIndex< TInputImage, TLabelImage >::BeforeThreadedGenerateData() +{ + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + m_ThreadExtrema.resize(numberOfThreads); + + for (unsigned int i =0; i < numberOfThreads; i++) + { + m_ThreadExtrema[i] = ExtremaMapType(); + } +} + +template< typename TInputImage, typename TLabelImage > +void MinMaxLabelImageFilterWithIndex< TInputImage, TLabelImage >::AfterThreadedGenerateData() +{ + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + m_GlobalMin = std::numeric_limits::max(); + m_GlobalMax = std::numeric_limits::min(); + + ExtremaMapTypeIterator it; + + for (ThreadIdType i = 0; i < numberOfThreads; i++) + { + for (auto&& it2 : m_ThreadExtrema[i]) + { + it = m_LabelExtrema.find(it2.first); + if (it == m_LabelExtrema.end()) + { + it = m_LabelExtrema.insert( MapValueType(it2.first, LabelExtrema()) ).first; + } + + if (it2.second.m_Min < (*it).second.m_Min) + { + (*it).second.m_Min = it2.second.m_Min; + (*it).second.m_MinIndex = it2.second.m_MinIndex; + if (it2.second.m_Min < m_GlobalMin) + { + m_GlobalMin = it2.second.m_Min; + m_GlobalMinIndex = it2.second.m_MinIndex; + } + } + + if (it2.second.m_Max > (*it).second.m_Max) + { + (*it).second.m_Max = it2.second.m_Max; + (*it).second.m_MaxIndex = it2.second.m_MaxIndex; + if (it2.second.m_Max > m_GlobalMax) + { + m_GlobalMax = it2.second.m_Max; + m_GlobalMaxIndex = it2.second.m_MaxIndex; + } + } + } + } +} +} +#endif diff --git a/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.cpp b/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.cpp new file mode 100644 index 0000000000..e3d467c857 --- /dev/null +++ b/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.cpp @@ -0,0 +1 @@ +#include diff --git a/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.h b/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.h new file mode 100644 index 0000000000..830ed2003c --- /dev/null +++ b/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.h @@ -0,0 +1,43 @@ +#ifndef MITKMULTILABELMASKGENERATOR +#define MITKMULTILABELMASKGENERATOR + +#include +#include +#include +#include + + +namespace mitk +{ +/** + * @brief The MultiLabelMaskGenerator class NOT IMPLEMENTED YET! + */ +class MITKIMAGESTATISTICS_EXPORT MultiLabelMaskGenerator: public MaskGenerator +{ +public: + /* void setLabelSetImage(mitk::LabelSetImage::Pointer labelSetImage); + + void addLabel(LabelSetImage::PixelType, std::vector::size_type layer=0); + void removeLabel(LabelSetImage::PixelType, std::vector::size_type layer=0); + + void addLabels(std::pair::size_type, std::vector> labelsToAdd); + void removeLabels(std::pair::size_type, std::vector> labelsToAdd); + + void addLabels(std::vector labels, std::vector::size_type layer=0); + void removeLabels(std::vector labels, std::vector::size_type layer=0); + + void removeLayer(std::vector::size_type layer); + + mitk::Image::Pointer GetMask(); + +protected: + +private: + mitk::LabelSetImage::Pointer m_LabelSetImage; + std::vector> m_selectedLabels;*/ + +}; + +} + +#endif diff --git a/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp new file mode 100644 index 0000000000..57ef871eb6 --- /dev/null +++ b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp @@ -0,0 +1,418 @@ +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + + + +namespace mitk +{ + +void PlanarFigureMaskGenerator::SetPlanarFigure(mitk::PlanarFigure::Pointer planarFigure) +{ + if ( planarFigure.IsNull() ) + { + throw std::runtime_error( "Error: planar figure empty!" ); + } + if ( !planarFigure->IsClosed() ) + { + throw std::runtime_error( "Masking not possible for non-closed figures" ); + } + + const PlaneGeometry *planarFigurePlaneGeometry = planarFigure->GetPlaneGeometry(); + if ( planarFigurePlaneGeometry == nullptr ) + { + throw std::runtime_error( "Planar-Figure not yet initialized!" ); + } + + const PlaneGeometry *planarFigureGeometry = + dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); + if ( planarFigureGeometry == nullptr ) + { + throw std::runtime_error( "Non-planar planar figures not supported!" ); + } + + if (planarFigure != m_PlanarFigure) + { + this->Modified(); + m_PlanarFigure = planarFigure; + } + +} + +mitk::Image::Pointer PlanarFigureMaskGenerator::GetReferenceImage() +{ + if (IsUpdateRequired()) + { + this->CalculateMask(); + } + return m_ReferenceImage; +} + +template < typename TPixel, unsigned int VImageDimension > +void PlanarFigureMaskGenerator::InternalCalculateMaskFromPlanarFigure( + const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) +{ + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef itk::Image< unsigned short, 2 > MaskImage2DType; + + typename MaskImage2DType::Pointer maskImage = MaskImage2DType::New(); + maskImage->SetOrigin(image->GetOrigin()); + maskImage->SetSpacing(image->GetSpacing()); + maskImage->SetLargestPossibleRegion(image->GetLargestPossibleRegion()); + maskImage->SetBufferedRegion(image->GetBufferedRegion()); + maskImage->SetDirection(image->GetDirection()); + maskImage->SetNumberOfComponentsPerPixel(image->GetNumberOfComponentsPerPixel()); + maskImage->Allocate(); + maskImage->FillBuffer(1); + + // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. + // These points are used by the vtkLassoStencilSource to create + // a vtkImageStencil. + const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); + const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); + const mitk::BaseGeometry *imageGeometry3D = m_inputImage->GetGeometry( 0 ); + // If there is a second poly line in a closed planar figure, treat it as a hole. + PlanarFigure::PolyLineType planarFigureHolePolyline; + + if (m_PlanarFigure->GetPolyLinesSize() == 2) + planarFigureHolePolyline = m_PlanarFigure->GetPolyLine(1); + + + // Determine x- and y-dimensions depending on principal axis + // TODO use plane geometry normal to determine that automatically, then check whether the PF is aligned with one of the three principal axis + int i0, i1; + switch ( axis ) + { + case 0: + i0 = 1; + i1 = 2; + break; + + case 1: + i0 = 0; + i1 = 2; + break; + + case 2: + default: + i0 = 0; + i1 = 1; + break; + } + + // store the polyline contour as vtkPoints object + bool outOfBounds = false; + vtkSmartPointer points = vtkSmartPointer::New(); + typename PlanarFigure::PolyLineType::const_iterator it; + for ( it = planarFigurePolyline.begin(); + it != planarFigurePolyline.end(); + ++it ) + { + Point3D point3D; + + // Convert 2D point back to the local index coordinates of the selected + // image + // Fabian: From PlaneGeometry documentation: + // Converts a 2D point given in mm (pt2d_mm) relative to the upper-left corner of the geometry into the corresponding world-coordinate (a 3D point in mm, pt3d_mm). + // To convert a 2D point given in units (e.g., pixels in case of an image) into a 2D point given in mm (as required by this method), use IndexToWorld. + planarFigurePlaneGeometry->Map( *it, point3D ); + + // Polygons (partially) outside of the image bounds can not be processed + // further due to a bug in vtkPolyDataToImageStencil + if ( !imageGeometry3D->IsInside( point3D ) ) + { + outOfBounds = true; + } + + imageGeometry3D->WorldToIndex( point3D, point3D ); + + points->InsertNextPoint( point3D[i0], point3D[i1], 0 ); + } + + vtkSmartPointer holePoints = nullptr; + + if (!planarFigureHolePolyline.empty()) + { + holePoints = vtkSmartPointer::New(); + + Point3D point3D; + PlanarFigure::PolyLineType::const_iterator end = planarFigureHolePolyline.end(); + + for (it = planarFigureHolePolyline.begin(); it != end; ++it) + { + // Fabian: same as above + planarFigurePlaneGeometry->Map(*it, point3D); + imageGeometry3D->WorldToIndex(point3D, point3D); + holePoints->InsertNextPoint(point3D[i0], point3D[i1], 0); + } + } + + // mark a malformed 2D planar figure ( i.e. area = 0 ) as out of bounds + // this can happen when all control points of a rectangle lie on the same line = two of the three extents are zero + double bounds[6] = {0, 0, 0, 0, 0, 0}; + points->GetBounds( bounds ); + bool extent_x = (fabs(bounds[0] - bounds[1])) < mitk::eps; + bool extent_y = (fabs(bounds[2] - bounds[3])) < mitk::eps; + bool extent_z = (fabs(bounds[4] - bounds[5])) < mitk::eps; + + // throw an exception if a closed planar figure is deformed, i.e. has only one non-zero extent + if ( m_PlanarFigure->IsClosed() && + ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) + { + mitkThrow() << "Figure has a zero area and cannot be used for masking."; + } + + if ( outOfBounds ) + { + throw std::runtime_error( "Figure at least partially outside of image bounds!" ); + } + + // create a vtkLassoStencilSource and set the points of the Polygon + vtkSmartPointer lassoStencil = vtkSmartPointer::New(); + lassoStencil->SetShapeToPolygon(); + lassoStencil->SetPoints( points ); + + vtkSmartPointer holeLassoStencil = nullptr; + + if (holePoints.GetPointer() != nullptr) + { + holeLassoStencil = vtkSmartPointer::New(); + holeLassoStencil->SetShapeToPolygon(); + holeLassoStencil->SetPoints(holePoints); + } + + // Export from ITK to VTK (to use a VTK filter) + typedef itk::VTKImageImport< MaskImage2DType > ImageImportType; + typedef itk::VTKImageExport< MaskImage2DType > ImageExportType; + + typename ImageExportType::Pointer itkExporter = ImageExportType::New(); + itkExporter->SetInput( maskImage ); +// itkExporter->SetInput( castFilter->GetOutput() ); + + vtkSmartPointer vtkImporter = vtkSmartPointer::New(); + this->ConnectPipelines( itkExporter, vtkImporter ); + + // Apply the generated image stencil to the input image + vtkSmartPointer imageStencilFilter = vtkSmartPointer::New(); + imageStencilFilter->SetInputConnection( vtkImporter->GetOutputPort() ); + imageStencilFilter->SetStencilConnection(lassoStencil->GetOutputPort()); + imageStencilFilter->ReverseStencilOff(); + imageStencilFilter->SetBackgroundValue( 0 ); + imageStencilFilter->Update(); + + vtkSmartPointer holeStencilFilter = nullptr; + + if (holeLassoStencil.GetPointer() != nullptr) + { + holeStencilFilter = vtkSmartPointer::New(); + holeStencilFilter->SetInputConnection(imageStencilFilter->GetOutputPort()); + holeStencilFilter->SetStencilConnection(holeLassoStencil->GetOutputPort()); + holeStencilFilter->ReverseStencilOn(); + holeStencilFilter->SetBackgroundValue(0); + holeStencilFilter->Update(); + } + + // Export from VTK back to ITK + vtkSmartPointer vtkExporter = vtkSmartPointer::New(); + vtkExporter->SetInputConnection( holeStencilFilter.GetPointer() == nullptr + ? imageStencilFilter->GetOutputPort() + : holeStencilFilter->GetOutputPort()); + vtkExporter->Update(); + + typename ImageImportType::Pointer itkImporter = ImageImportType::New(); + this->ConnectPipelines( vtkExporter, itkImporter ); + itkImporter->Update(); + + typedef itk::ImageDuplicator< ImageImportType::OutputImageType > DuplicatorType; + DuplicatorType::Pointer duplicator = DuplicatorType::New(); + duplicator->SetInputImage( itkImporter->GetOutput() ); + duplicator->Update(); + + // Store mask + m_InternalITKImageMask2D = duplicator->GetOutput(); +} + +bool PlanarFigureMaskGenerator::GetPrincipalAxis( + const BaseGeometry *geometry, Vector3D vector, + unsigned int &axis ) +{ + vector.Normalize(); + for ( unsigned int i = 0; i < 3; ++i ) + { + Vector3D axisVector = geometry->GetAxisVector( i ); + axisVector.Normalize(); + + if ( fabs( fabs( axisVector * vector ) - 1.0) < mitk::eps ) + { + axis = i; + return true; + } + } + + return false; +} + +void PlanarFigureMaskGenerator::CalculateMask() +{ + if (m_inputImage.IsNull()) + { + MITK_ERROR << "Image is not set."; + } + + if (m_PlanarFigure.IsNull()) + { + MITK_ERROR << "PlanarFigure is not set."; + } + + if (m_TimeStep != 0) + { + MITK_WARN << "Multiple TimeSteps are not supported in PlanarFigureMaskGenerator (yet)."; + } + + const BaseGeometry *imageGeometry = m_inputImage->GetGeometry(); + if ( imageGeometry == nullptr ) + { + throw std::runtime_error( "Image geometry invalid!" ); + } + + if (m_inputImage->GetTimeSteps() > 0) + { + mitk::ImageTimeSelector::Pointer imgTimeSel = mitk::ImageTimeSelector::New(); + imgTimeSel->SetInput(m_inputImage); + imgTimeSel->SetTimeNr(m_TimeStep); + imgTimeSel->UpdateLargestPossibleRegion(); + m_InternalTimeSliceImage = imgTimeSel->GetOutput(); + } + else + { + m_InternalTimeSliceImage = m_inputImage; + } + + m_InternalITKImageMask2D = nullptr; + const PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); + const PlaneGeometry *planarFigureGeometry = dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); + //const BaseGeometry *imageGeometry = m_inputImage->GetGeometry(); + + // Find principal direction of PlanarFigure in input image + unsigned int axis; + if ( !this->GetPrincipalAxis( imageGeometry, + planarFigureGeometry->GetNormal(), axis ) ) + { + throw std::runtime_error( "Non-aligned planar figures not supported!" ); + } + m_PlanarFigureAxis = axis; + + // Find slice number corresponding to PlanarFigure in input image + itk::Image< unsigned short, 3 >::IndexType index; + imageGeometry->WorldToIndex( planarFigureGeometry->GetOrigin(), index ); + + unsigned int slice = index[axis]; + + // extract image slice which corresponds to the planarFigure and store it in m_InternalImageSlice + mitk::Image::Pointer inputImageSlice = extract2DImageSlice(axis, slice); + //mitk::IOUtil::SaveImage(inputImageSlice, "/home/fabian/inputSliceImage.nrrd"); + // Compute mask from PlanarFigure + AccessFixedDimensionByItk_1(inputImageSlice, + InternalCalculateMaskFromPlanarFigure, + 2, axis) + + //convert itk mask to mitk::Image::Pointer and return it + mitk::Image::Pointer planarFigureMaskImage; + planarFigureMaskImage = mitk::GrabItkImageMemory(m_InternalITKImageMask2D); + //mitk::IOUtil::SaveImage(planarFigureMaskImage, "/home/fabian/planarFigureMaskImage.nrrd"); + + //Convert2Dto3DImageFilter::Pointer sliceTo3DImageConverter = Convert2Dto3DImageFilter::New(); + //sliceTo3DImageConverter->SetInput(planarFigureMaskImage); + //sliceTo3DImageConverter->Update(); + //mitk::IOUtil::SaveImage(sliceTo3DImageConverter->GetOutput(), "/home/fabian/3DsliceImage.nrrd"); + + m_ReferenceImage = inputImageSlice; + //mitk::IOUtil::SaveImage(m_ReferenceImage, "/home/fabian/referenceImage.nrrd"); + m_InternalMask = planarFigureMaskImage; +} + +mitk::Image::Pointer PlanarFigureMaskGenerator::GetMask() +{ + if (IsUpdateRequired()) + { + this->CalculateMask(); + this->Modified(); + } + + m_InternalMaskUpdateTime = m_InternalMask->GetMTime(); + return m_InternalMask; +} + +mitk::Image::Pointer PlanarFigureMaskGenerator::extract2DImageSlice(unsigned int axis, unsigned int slice) +{ + // Extract slice with given position and direction from image + unsigned int dimension = m_InternalTimeSliceImage->GetDimension(); + mitk::Image::Pointer imageSlice = mitk::Image::New(); + + if (dimension == 3) + { + ExtractImageFilter::Pointer imageExtractor = ExtractImageFilter::New(); + imageExtractor->SetInput( m_InternalTimeSliceImage ); + imageExtractor->SetSliceDimension( axis ); + imageExtractor->SetSliceIndex( slice ); + imageExtractor->Update(); + imageSlice = imageExtractor->GetOutput(); + } + else if(dimension == 2) + { + imageSlice = m_InternalTimeSliceImage; + } + else + { + MITK_ERROR << "Unsupported image dimension. Dimension is: " << dimension << ". Only 2D and 3D images are supported."; + } + + return imageSlice; +} + +bool PlanarFigureMaskGenerator::IsUpdateRequired() const +{ + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); + unsigned long planarFigureTimeStamp = m_PlanarFigure->GetMTime(); + unsigned long inputImageTimeStamp = m_inputImage->GetMTime(); + + if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed + { + return true; + } + + if (m_InternalMaskUpdateTime < planarFigureTimeStamp || m_InternalMaskUpdateTime < inputImageTimeStamp) // mask image has changed outside of this class + { + return true; + } + + if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class + { + return true; + } + + return false; +} + +} + diff --git a/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.h b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.h new file mode 100644 index 0000000000..8585238d56 --- /dev/null +++ b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.h @@ -0,0 +1,119 @@ +#ifndef MITKPLANARFIGUREMASKGENERATOR +#define MITKPLANARFIGUREMASKGENERATOR + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mitk +{ +/** +* \class PlanarFigureMaskGenerator +* \brief Derived from MaskGenerator. This class is used to convert a mitk::PlanarFigure into a binary image mask +*/ +class MITKIMAGESTATISTICS_EXPORT PlanarFigureMaskGenerator: public MaskGenerator + { + public: + /** Standard Self typedef */ + typedef PlanarFigureMaskGenerator Self; + typedef MaskGenerator Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(PlanarFigureMaskGenerator, MaskGenerator) + + /** + * @brief GetMask Computes and returns the mask + * @return mitk::Image::Pointer of the generated mask + */ + mitk::Image::Pointer GetMask(); + + void SetPlanarFigure(mitk::PlanarFigure::Pointer planarFigure); + + mitk::Image::Pointer GetReferenceImage(); + + protected: + PlanarFigureMaskGenerator():Superclass(){ + m_InternalMaskUpdateTime = 0; + m_InternalMask = mitk::Image::New(); + m_ReferenceImage = nullptr; + } + + private: + void CalculateMask(); + + template < typename TPixel, unsigned int VImageDimension > + void InternalCalculateMaskFromPlanarFigure( + const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ); + + mitk::Image::Pointer extract2DImageSlice(unsigned int axis, unsigned int slice); + + bool GetPrincipalAxis(const BaseGeometry *geometry, Vector3D vector, + unsigned int &axis ); + + /** Connection from ITK to VTK */ + template + void ConnectPipelines(ITK_Exporter exporter, vtkSmartPointer importer) + { + importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); + + importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); + importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); + importer->SetSpacingCallback(exporter->GetSpacingCallback()); + importer->SetOriginCallback(exporter->GetOriginCallback()); + importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); + + importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); + + importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); + importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); + importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); + importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); + importer->SetCallbackUserData(exporter->GetCallbackUserData()); + } + + /** Connection from VTK to ITK */ + template + void ConnectPipelines(vtkSmartPointer exporter, ITK_Importer importer) + { + importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); + + importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); + importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); + importer->SetSpacingCallback(exporter->GetSpacingCallback()); + importer->SetOriginCallback(exporter->GetOriginCallback()); + importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); + + importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); + + importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); + importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); + importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); + importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); + importer->SetCallbackUserData(exporter->GetCallbackUserData()); + } + + bool IsUpdateRequired() const; + + mitk::PlanarFigure::Pointer m_PlanarFigure; + itk::Image::Pointer m_InternalITKImageMask2D; + mitk::Image::Pointer m_InternalTimeSliceImage; + mitk::Image::Pointer m_ReferenceImage; + unsigned int m_PlanarFigureAxis; + unsigned long m_InternalMaskUpdateTime; + }; +} + +#endif // MITKPLANARFIGUREMASKGENERATOR + diff --git a/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.cpp b/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.cpp index 8e5d173598..221b97c69e 100644 --- a/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.cpp +++ b/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.cpp @@ -1,214 +1,216 @@ /*=================================================================== 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 "mitkPointSetDifferenceStatisticsCalculator.h" mitk::PointSetDifferenceStatisticsCalculator::PointSetDifferenceStatisticsCalculator() : m_StatisticsCalculated(false) { + m_Statistics = ImageStatisticsCalculator::StatisticsContainer::New(); m_PointSet1 = mitk::PointSet::New(); m_PointSet2 = mitk::PointSet::New(); //m_Statistics.Reset(); } mitk::PointSetDifferenceStatisticsCalculator::PointSetDifferenceStatisticsCalculator(mitk::PointSet::Pointer pSet1, mitk::PointSet::Pointer pSet2) { + m_Statistics = ImageStatisticsCalculator::StatisticsContainer::New(); m_PointSet1 = pSet1; m_PointSet2 = pSet2; m_StatisticsCalculated = false; //m_Statistics.Reset(); } mitk::PointSetDifferenceStatisticsCalculator::~PointSetDifferenceStatisticsCalculator() { } void mitk::PointSetDifferenceStatisticsCalculator::SetPointSets(mitk::PointSet::Pointer pSet1, mitk::PointSet::Pointer pSet2) { if (pSet1.IsNotNull()) { m_PointSet1 = pSet1; } if (pSet2.IsNotNull()) { m_PointSet2 = pSet2; } m_StatisticsCalculated = false; //m_Statistics.Reset(); } std::vector mitk::PointSetDifferenceStatisticsCalculator::GetDifferences() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } return m_DifferencesVector; } std::vector mitk::PointSetDifferenceStatisticsCalculator::GetSquaredDifferences() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } return m_SquaredDifferencesVector; } double mitk::PointSetDifferenceStatisticsCalculator::GetMean() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetMean(); + return m_Statistics->GetMean(); } double mitk::PointSetDifferenceStatisticsCalculator::GetSD() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetSigma(); + return m_Statistics->GetStd(); } double mitk::PointSetDifferenceStatisticsCalculator::GetVariance() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetVariance(); + return m_Statistics->GetVariance(); } double mitk::PointSetDifferenceStatisticsCalculator::GetRMS() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetRMS(); + return m_Statistics->GetRMS(); } double mitk::PointSetDifferenceStatisticsCalculator::GetMedian() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetMedian(); + return m_Statistics->GetMedian(); } double mitk::PointSetDifferenceStatisticsCalculator::GetMax() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetMax(); + return m_Statistics->GetMax(); } double mitk::PointSetDifferenceStatisticsCalculator::GetMin() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetMin(); + return m_Statistics->GetMin(); } double mitk::PointSetDifferenceStatisticsCalculator::GetNumberOfPoints() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetN(); + return m_Statistics->GetN(); } void mitk::PointSetDifferenceStatisticsCalculator::ComputeStatistics() { if ((m_PointSet1.IsNull())||(m_PointSet2.IsNull())) { itkExceptionMacro("Point sets specified are not valid. Please specify correct Point sets"); } else if (m_PointSet1->GetSize()!=m_PointSet2->GetSize()) { itkExceptionMacro("PointSets are not equal. Please make sure that your PointSets have the same size and hold corresponding points."); } else if (m_PointSet1->GetSize()==0) { itkExceptionMacro("There are no points in the PointSets. Please make sure that the PointSets contain points"); } else { double mean = 0.0; double sd = 0.0; double rms= 0.0; std::vector differencesVector; mitk::Point3D point1; mitk::Point3D point2; int numberOfPoints = m_PointSet1->GetSize(); //Iterate over both pointsets in order to compare all points pair-wise mitk::PointSet::PointsIterator end = m_PointSet1->End(); for( mitk::PointSet::PointsIterator pointSetIterator = m_PointSet1->Begin(), pointSetIterator2 = m_PointSet2->Begin(); pointSetIterator != end; ++pointSetIterator, ++pointSetIterator2) //iterate simultaneously over both sets { point1 = pointSetIterator.Value(); point2 = pointSetIterator2.Value(); double squaredDistance = point1.SquaredEuclideanDistanceTo(point2); mean+=sqrt(squaredDistance); rms+=squaredDistance; this->m_SquaredDifferencesVector.push_back(squaredDistance); differencesVector.push_back(sqrt(squaredDistance)); } m_DifferencesVector = differencesVector; mean = mean/numberOfPoints; rms = sqrt(rms/numberOfPoints); for (std::vector::size_type i=0; iSetMean(mean); + m_Statistics->SetStd(sd); + m_Statistics->SetVariance(variance); + m_Statistics->SetRMS(rms); + m_Statistics->SetMin(differencesVector.at(0)); + m_Statistics->SetMax(differencesVector.at(numberOfPoints-1)); + m_Statistics->SetMedian(median); + m_Statistics->SetN(numberOfPoints); m_StatisticsCalculated = true; } } diff --git a/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.h b/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.h index 1dbffb566b..0ab47a40e6 100644 --- a/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.h +++ b/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.h @@ -1,110 +1,110 @@ /*=================================================================== 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 _MITK_PointSetDifferenceStatisticsCalculator_H #define _MITK_PointSetDifferenceStatisticsCalculator_H #include #include #include "mitkImageStatisticsCalculator.h" #include namespace mitk { /** * \brief Class for calculating the difference between two corresponding point sets. * The user can access the single distances between corresponding points as well as a complete statistic (mean, sd, rms, median, max, min) * The point sets must be of equal size! */ class MITKIMAGESTATISTICS_EXPORT PointSetDifferenceStatisticsCalculator : public itk::Object { public: mitkClassMacroItkParent( PointSetDifferenceStatisticsCalculator, itk::Object ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) mitkNewMacro2Param(PointSetDifferenceStatisticsCalculator,mitk::PointSet::Pointer,mitk::PointSet::Pointer); /*! \brief set point sets to be compared */ void SetPointSets(mitk::PointSet::Pointer pSet1, mitk::PointSet::Pointer pSet2); /*! \brief returns a vector holding the differences between the corresponding points of the point sets */ std::vector GetDifferences(); /*! \brief returns a vector holding the squared differences between the corresponding points of the point sets */ std::vector GetSquaredDifferences(); /*! \brief returns the mean distance of all corresponding points of the point sets */ double GetMean(); /*! \brief returns the standard deviation of the distances between all corresponding points of the point sets */ double GetSD(); /*! \brief returns the variance of the distances between all corresponding points of the point sets */ double GetVariance(); /*! \brief returns the root mean squared distance of all corresponding points of the point sets */ double GetRMS(); /*! \brief returns the median distance of all corresponding points of the point sets */ double GetMedian(); /*! \brief returns the maximal distance of all corresponding points of the point sets */ double GetMax(); /*! \brief returns the minimal distance of all corresponding points of the point sets */ double GetMin(); /*! \brief returns the total number of corresponding points of the point sets */ double GetNumberOfPoints(); protected: PointSetDifferenceStatisticsCalculator(); PointSetDifferenceStatisticsCalculator(mitk::PointSet::Pointer,mitk::PointSet::Pointer); virtual ~PointSetDifferenceStatisticsCalculator(); /*! \brief Method for computing the complete statistics of the differences between the given point sets. */ void ComputeStatistics(); - mitk::ImageStatisticsCalculator::Statistics m_Statistics; ///< struct holding the statistics + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer m_Statistics; ///< struct holding the statistics std::vector m_DifferencesVector; ///< vector holding the differences between the corresponding points std::vector m_SquaredDifferencesVector; ///< vector holding the squared differences between the corresponding points mitk::PointSet::Pointer m_PointSet1; ///< first point set used for comparison mitk::PointSet::Pointer m_PointSet2; ///< second point set used for comparison bool m_StatisticsCalculated; ///< flag indicating whether statistics are already calculated or not. }; } #endif // #define _MITK_PointSetDifferenceStatisticsCalculator_H diff --git a/Modules/ImageStatistics/mitkitkMaskImageFilter.h b/Modules/ImageStatistics/mitkitkMaskImageFilter.h new file mode 100644 index 0000000000..24fcfd8ecf --- /dev/null +++ b/Modules/ImageStatistics/mitkitkMaskImageFilter.h @@ -0,0 +1,289 @@ +/*========================================================================= + * + * Copyright Insight Software Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +#ifndef __itkMaskImageFilter2_h +#define __itkMaskImageFilter2_h + +#include "itkBinaryFunctorImageFilter.h" +#include "itkNumericTraits.h" +#include "itkVariableLengthVector.h" +#include + +namespace itk +{ +namespace Functor +{ +/** + * \class MaskInput2 + * \brief + * \ingroup ITKImageIntensity + */ +template< typename TInput, typename TMask, typename TOutput = TInput > +class MaskInput2 +{ +public: + typedef typename NumericTraits< TInput >::AccumulateType AccumulatorType; + + MaskInput2() + { + m_MaskingValue = NumericTraits< TMask >::ZeroValue(); + InitializeOutsideValue( static_cast( ITK_NULLPTR ) ); + } + ~MaskInput2() {} + bool operator!=(const MaskInput2 &) const + { + return false; + } + + bool operator==(const MaskInput2 & other) const + { + return !( *this != other ); + } + + inline TOutput operator()(const TInput & A, const TMask & B) const + { + if ( B == m_MaskingValue ) + { + return static_cast< TOutput >( A ); + } + else + { + return m_OutsideValue; + } + } + + /** Method to explicitly set the outside value of the mask */ + void SetOutsideValue(const TOutput & outsideValue) + { + m_OutsideValue = outsideValue; + } + + /** Method to get the outside value of the mask */ + const TOutput & GetOutsideValue() const + { + return m_OutsideValue; + } + + /** Method to explicitly set the masking value */ + void SetMaskingValue(const TMask & maskingValue) + { + m_MaskingValue = maskingValue; + } + /** Method to get the masking value */ + const TMask & GetMaskingValue() const + { + return m_MaskingValue; + } + +private: + + template < typename TPixelType > + void InitializeOutsideValue( TPixelType * ) + { + this->m_OutsideValue = NumericTraits< TPixelType >::ZeroValue(); + } + + template < typename TValue > + void InitializeOutsideValue( VariableLengthVector * ) + { + // set the outside value to be of zero length + this->m_OutsideValue = VariableLengthVector< TValue >(0); + } + + TOutput m_OutsideValue; + TMask m_MaskingValue; +}; +} +/** \class MaskImageFilter + * \brief Mask an image with a mask. + * + * This class is templated over the types of the + * input image type, the mask image type and the type of the output image. + * Numeric conversions (castings) are done by the C++ defaults. + * + * The pixel type of the input 2 image must have a valid definition of the + * operator != with zero. This condition is required because internally this + * filter will perform the operation + * + * \code + * if pixel_from_mask_image == masking_value + * pixel_output_image = pixel_input_image + * else + * pixel_output_image = outside_value + * \endcode + * + * The pixel from the input 1 is cast to the pixel type of the output image. + * + * Note that the input and the mask images must be of the same size. + * + * + * \sa MaskNegatedImageFilter + * \ingroup IntensityImageFilters + * \ingroup MultiThreaded + * \ingroup ITKImageIntensity + * + * \wiki + * \wikiexample{ImageProcessing/MaskImageFilter,Apply a mask to an image} + * \endwiki + */ +template< typename TInputImage, typename TMaskImage, typename TOutputImage = TInputImage > +class MITKIMAGESTATISTICS_EXPORT MaskImageFilter2: + public + BinaryFunctorImageFilter< TInputImage, TMaskImage, TOutputImage, + Functor::MaskInput2< + typename TInputImage::PixelType, + typename TMaskImage::PixelType, + typename TOutputImage::PixelType > > + +{ +public: + /** Standard class typedefs. */ + typedef MaskImageFilter2 Self; + typedef BinaryFunctorImageFilter< TInputImage, TMaskImage, TOutputImage, + Functor::MaskInput2< + typename TInputImage::PixelType, + typename TMaskImage::PixelType, + typename TOutputImage::PixelType > + > Superclass; + + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkTypeMacro(MaskImageFilter2, + BinaryFunctorImageFilter); + + /** Typedefs **/ + typedef TMaskImage MaskImageType; + + /** Set/Get the mask image. Pixels set in the mask image will retain + * the original value of the input image while pixels not set in + * the mask will be set to the "OutsideValue". + */ + void SetMaskImage(const MaskImageType *maskImage) + { + // Process object is not const-correct so the const casting is required. + this->SetNthInput( 1, const_cast< MaskImageType * >( maskImage ) ); + } + const MaskImageType * GetMaskImage() + { + return static_cast(this->ProcessObject::GetInput(1)); + } + + /** Method to explicitly set the outside value of the mask. Defaults to 0 */ + void SetOutsideValue(const typename TOutputImage::PixelType & outsideValue) + { + if ( this->GetOutsideValue() != outsideValue ) + { + this->Modified(); + this->GetFunctor().SetOutsideValue(outsideValue); + } + } + + const typename TOutputImage::PixelType & GetOutsideValue() const + { + return this->GetFunctor().GetOutsideValue(); + } + + /** Method to explicitly set the masking value of the mask. Defaults to 0 */ + void SetMaskingValue(const typename TMaskImage::PixelType & maskingValue) + { + if ( this->GetMaskingValue() != maskingValue ) + { + this->Modified(); + this->GetFunctor().SetMaskingValue(maskingValue); + } + } + + /** Method to get the masking value of the mask. */ + const typename TMaskImage::PixelType & GetMaskingValue() const + { + return this->GetFunctor().GetMaskingValue(); + } + + void BeforeThreadedGenerateData() + { + typedef typename TOutputImage::PixelType PixelType; + this->CheckOutsideValue( static_cast(ITK_NULLPTR) ); + } + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro( MaskEqualityComparableCheck, + ( Concept::EqualityComparable< typename TMaskImage::PixelType > ) ); + itkConceptMacro( InputConvertibleToOutputCheck, + ( Concept::Convertible< typename TInputImage::PixelType, + typename TOutputImage::PixelType > ) ); + // End concept checking +#endif + +protected: + MaskImageFilter2() {} + virtual ~MaskImageFilter2() {} + + void PrintSelf(std::ostream & os, Indent indent) const + { + Superclass::PrintSelf(os, indent); + os << indent << "OutsideValue: " << this->GetOutsideValue() << std::endl; + } + +private: + MaskImageFilter2(const Self &); //purposely not implemented + void operator=(const Self &); //purposely not implemented + + template < typename TPixelType > + void CheckOutsideValue( const TPixelType * ) {} + + template < typename TValue > + void CheckOutsideValue( const VariableLengthVector< TValue > * ) + { + // Check to see if the outside value contains only zeros. If so, + // resize it to have the same number of zeros as the output + // image. Otherwise, check that the number of components in the + // outside value is the same as the number of components in the + // output image. If not, throw an exception. + VariableLengthVector< TValue > currentValue = + this->GetFunctor().GetOutsideValue(); + VariableLengthVector< TValue > zeroVector( currentValue.GetSize() ); + zeroVector.Fill( NumericTraits< TValue >::ZeroValue() ); + + if ( currentValue == zeroVector ) + { + zeroVector.SetSize( this->GetOutput()->GetVectorLength() ); + zeroVector.Fill( NumericTraits< TValue >::ZeroValue() ); + this->GetFunctor().SetOutsideValue( zeroVector ); + } + else if ( this->GetFunctor().GetOutsideValue().GetSize() != + this->GetOutput()->GetVectorLength() ) + { + itkExceptionMacro( + << "Number of components in OutsideValue: " + << this->GetFunctor().GetOutsideValue().GetSize() + << " is not the same as the " + << "number of components in the image: " + << this->GetOutput()->GetVectorLength()); + } + } + +}; +} // end namespace itk + +#endif + diff --git a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h b/Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.h similarity index 100% copy from Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h copy to Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.h diff --git a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx b/Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.hxx similarity index 99% copy from Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx copy to Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.hxx index 42865e425c..b7e26546b7 100644 --- a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx +++ b/Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.hxx @@ -1,324 +1,323 @@ /*=================================================================== 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 _mitkExtendedLabelStatisticsImageFilter_hxx #define _mitkExtendedLabelStatisticsImageFilter_hxx #include "mitkExtendedLabelStatisticsImageFilter.h" #include "itkImageRegionConstIteratorWithIndex.h" #include "itkImageRegionConstIterator.h" #include #include #include "mitkNumericConstants.h" #include "mitkLogMacros.h" namespace itk { template< class TInputImage , class TLabelImage> ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::ExtendedLabelStatisticsImageFilter() : LabelStatisticsImageFilter< TInputImage, TLabelImage >() { CalculateSettingsForLabels(); } template< class TInputImage, class TLabelImage > std::list ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetRelevantLabels() const { return m_RelevantLabels; } template< class TInputImage, class TLabelImage > bool ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetMaskingNonEmpty() const { return m_MaskNonEmpty; } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetUniformity(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Uniformity; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetEntropy(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Entropy; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetUPP(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_UPP; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetMPP(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_MPP; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetKurtosis(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Kurtosis; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetSkewness(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Skewness; } } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: CalculateSettingsForLabels() { LabelPixelType i; m_MaskNonEmpty = false; for ( i = 1; i < 4096; ++i ) { if ( this->HasLabel( i ) ) { m_RelevantLabels.push_back( i ); m_MaskNonEmpty = true; m_LabelStatisticsCoefficients.insert( std::make_pair(i, CoefficientsClass()) ); } } } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: ComputeEntropyUniformityAndUPP() { - double baseChange = std::log10(2); RealType partialProbability( 0.0 ); RealType uniformity( 0.0 ); RealType entropy( 0.0 ); RealType upp( 0.0 ); LabelPixelType i; if ( m_MaskNonEmpty ) { typename std::list< int >::const_iterator it; for ( it = m_RelevantLabels.cbegin(), i = 0; it != m_RelevantLabels.cend(); ++it, ++i ) { HistogramType::Pointer histogramForEntropy = this->GetHistogram(*it); for (int i = 0; i < histogramForEntropy->Size(); i++) { partialProbability = histogramForEntropy->GetFrequency(i,0) / double ( histogramForEntropy->GetTotalFrequency() ) ; if( partialProbability != 0) { entropy -= partialProbability *( std::log10(partialProbability) / std::log10(2) ) ; uniformity += std::pow(partialProbability,2); if(histogramForEntropy->GetMeasurement(i,0) > 0) { upp += std::pow(partialProbability,2); } } } m_LabelStatisticsCoefficients[*it].m_Entropy = entropy; m_LabelStatisticsCoefficients[*it].m_Uniformity = uniformity; m_LabelStatisticsCoefficients[*it].m_UPP = upp; } } } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: ComputeSkewnessKurtosisAndMPP() { typename TLabelImage::RegionType Subregion; RealType baseOfSkewnessAndCurtosis( 0.0 ); RealType kurtosis( 0.0 ); RealType skewness( 0.0 ); RealType mpp( 0.0 ); RealType currentPixel( 0.0 ); std::list< LabelPixelType> relevantLabels; LabelPixelType i; if ( m_MaskNonEmpty ) { typename std::list< int >::const_iterator it; for ( it = m_RelevantLabels.cbegin(), i = 0; it != m_RelevantLabels.cend(); ++it ) { RealType sigma = this->GetSigma( *it ); RealType mean = this->GetMean( *it ); Subregion = Superclass::GetRegion(*it); int count( this->GetCount(*it) ); if ( count == 0 || sigma < mitk::eps) { throw std::logic_error( "Empty segmentation" ); } if ( fabs( sigma ) < mitk::sqrteps ) { throw std::logic_error( "Sigma == 0" ); } ImageRegionConstIteratorWithIndex< TInputImage > it1 (this->GetInput(), Subregion); ImageRegionConstIterator< TLabelImage > labelIt (this->GetLabelInput(), Subregion); for (it1.GoToBegin(); !it1.IsAtEnd(); ++it1, ++labelIt) { if (labelIt.Get() == *it) { currentPixel = it1.Get(); baseOfSkewnessAndCurtosis = (currentPixel -mean) / sigma; kurtosis += std::pow( baseOfSkewnessAndCurtosis, 4.0 ); skewness += std::pow( baseOfSkewnessAndCurtosis, 3.0 ); if(currentPixel > 0) { mpp+= currentPixel; } } } m_LabelStatisticsCoefficients[*it].m_Skewness = RealType(skewness/count); m_LabelStatisticsCoefficients[*it].m_Kurtosis = RealType(kurtosis/count); m_LabelStatisticsCoefficients[*it].m_MPP = RealType(mpp/count); } } } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: AfterThreadedGenerateData() { Superclass::AfterThreadedGenerateData(); CalculateSettingsForLabels(); ComputeSkewnessKurtosisAndMPP(); if(this->GetUseHistograms()) { ComputeEntropyUniformityAndUPP(); } else { MITK_WARN << "Cannot compute coefficients UPP,Entropy,Uniformity because of missing histogram"; } } } // end namespace itk #endif diff --git a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h b/Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.h similarity index 100% copy from Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h copy to Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.h diff --git a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx b/Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.hxx similarity index 99% copy from Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx copy to Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.hxx index 004cf1a90b..3f3d810edb 100644 --- a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx +++ b/Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.hxx @@ -1,344 +1,343 @@ /*=================================================================== 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 __mitkExtendedStatisticsImageFilter_hxx #define __mitkExtendedStatisticsImageFilter_hxx #include "mitkExtendedStatisticsImageFilter.h" namespace itk { template< class TInputImage > ExtendedStatisticsImageFilter< TInputImage >::ExtendedStatisticsImageFilter() : StatisticsImageFilter< TInputImage >() { /* * add the Skewness,Kurtosis,Entropy,Uniformity,MPP, UPP, Median to the other statistical calculated Values * of the mitkStatisticsImageFilter as the 7th to the 13th Output */ for ( int i = 7; i < 14; ++i ) { typename RealObjectType::Pointer output = static_cast< RealObjectType * >( this->MakeOutput(i).GetPointer() ); this->ProcessObject::SetNthOutput( i, output.GetPointer() ); } this->GetSkewnessOutput()->Set( 0.0 ); this->GetKurtosisOutput()->Set( 0.0 ); this->GetEntropyOutput()->Set( -1.0 ); this->GetUniformityOutput()->Set( 0.0 ); this->GetUPPOutput()->Set( 0.0 ); this->GetMPPOutput()->Set( 0.0 ); this->GetMedianOutput()->Set( 0.0 ); this->m_HistogramCalculated = false; this->m_HistogramGenerator = HistogramGeneratorType::New(); } template< class TInputImage > DataObject::Pointer ExtendedStatisticsImageFilter< TInputImage >::MakeOutput( ProcessObject::DataObjectPointerArraySizeType output) { switch ( output ) { case 7: case 8: case 9: case 10: case 11: case 12: { return RealObjectType::New().GetPointer(); break; } case 13: { return RealObjectType::New().GetPointer(); break; } default: { // might as well make an image return Superclass::MakeOutput( output ); break; } } } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::SetBinSize( int size ) { m_BinSize = size; } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetSkewnessOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(7) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetSkewnessOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(7) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetKurtosisOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(8) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetKurtosisOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(8) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUniformityOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(9) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUniformityOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(9) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetEntropyOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(10) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetEntropyOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(10) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUPPOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(11) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUPPOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(11) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMPPOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(12) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMPPOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(12) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMedianOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(13) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMedianOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(13) ); } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::AfterThreadedGenerateData() { Superclass::AfterThreadedGenerateData(); ComputeSkewnessKurtosisAndMPP(); CalculateHistogram(); if(m_HistogramCalculated == true) { ComputeEntropyUniformityMedianAndUPP(); } else { MITK_WARN << "Cannot compute coefficients UPP,Entropy,Uniformity because of missing histogram"; } } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::ComputeSkewnessKurtosisAndMPP() { RealType mean = this->GetMean(); RealType sigma = this->GetSigma(); RealType baseOfSkewnessAndKurtosis; RealType kurtosis(0.0); RealType skewness(0.0); RealType mpp(0.0); RealType currentPixel(0.0); if ( sigma < mitk::eps ) { throw std::logic_error( "Empty segmentation" ); } ImageRegionConstIterator< TInputImage > it (this->GetInput(), this->GetInput()->GetLargestPossibleRegion() ); int counter = 0; for (it.GoToBegin(); !it.IsAtEnd(); ++it) { currentPixel = it.Get(); baseOfSkewnessAndKurtosis = (currentPixel - mean) / sigma ; kurtosis += std::pow( baseOfSkewnessAndKurtosis, 4.0 ); skewness += std::pow( baseOfSkewnessAndKurtosis, 3.0 ); if(currentPixel > 0) { mpp+= currentPixel; } counter++; } if ( counter == 0 ) { throw std::logic_error( "Empty segmentation" ); } kurtosis = kurtosis / counter; skewness = skewness / counter; mpp = mpp/counter; this->GetKurtosisOutput()->Set( kurtosis ); this->GetSkewnessOutput()->Set( skewness ); this->GetMPPOutput()->Set( mpp ); } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::CalculateHistogram() { m_HistogramGenerator->SetInput( this->GetInput() ); m_HistogramGenerator->SetMarginalScale( 100 ); m_HistogramGenerator->SetNumberOfBins( m_BinSize ); m_HistogramGenerator->SetHistogramMin( this->GetMinimum() ); m_HistogramGenerator->SetHistogramMax( this->GetMaximum() ); m_HistogramGenerator->Compute(); m_HistogramCalculated = true; } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::ComputeEntropyUniformityMedianAndUPP() { - double baseChange = std::log10(2); RealType partialProbability( 0.0 ); RealType uniformity( 0.0 ); RealType entropy( 0.0 ); RealType upp( 0.0 ); RealType median( 0.0 ); const typename HistogramGeneratorType::HistogramType* histogramForEntropy = GetHistogram(); double cumulativeProbability = 0.0; bool medianFound = false; for (int i = 0; i < histogramForEntropy->Size(); i++) { partialProbability = histogramForEntropy->GetFrequency(i,0) / double ( histogramForEntropy->GetTotalFrequency() ) ; cumulativeProbability += double ( partialProbability ); if( partialProbability != 0) { entropy -= partialProbability *( std::log10(partialProbability) / std::log10(2) ) ; uniformity += std::pow(partialProbability,2); if(histogramForEntropy->GetMeasurement(i,0) > 0) { upp += std::pow(partialProbability,2); } } if( cumulativeProbability >= 0.5 && !medianFound ) { RealType binMin = histogramForEntropy->GetBinMin( 0, i ); RealType binMax = histogramForEntropy->GetBinMax( 0, i ); median = ( binMin + binMax ) / 2.0; medianFound = true; } } this->GetEntropyOutput()->Set( entropy ); this->GetUniformityOutput()->Set( uniformity ); this->GetUPPOutput()->Set( upp ); this->GetMedianOutput()->Set( median ); } } #endif diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp b/Modules/ImageStatistics/old/mitkImageStatisticsCalculator.cpp similarity index 98% copy from Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp copy to Modules/ImageStatistics/old/mitkImageStatisticsCalculator.cpp index b88432606f..612aeb71f6 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp +++ b/Modules/ImageStatistics/old/mitkImageStatisticsCalculator.cpp @@ -1,2249 +1,2246 @@ /*=================================================================== 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 "mitkImageStatisticsCalculator.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkExtractImageFilter.h" #include "mitkImageTimeSelector.h" #include "mitkITKImageImport.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "itkImage.h" + //#define DEBUG_HOTSPOTSEARCH #define _USE_MATH_DEFINES #include #include "vtkLassoStencilSource.h" namespace mitk { ImageStatisticsCalculator::ImageStatisticsCalculator() : m_MaskingMode( MASKING_MODE_NONE ), m_MaskingModeChanged( false ), m_IgnorePixelValue(0.0), m_DoIgnorePixelValue(false), m_IgnorePixelValueChanged(false), m_PlanarFigureAxis (0), m_PlanarFigureSlice (0), m_PlanarFigureCoordinate0 (0), m_PlanarFigureCoordinate1 (0), m_HistogramBinSize(1.0), m_UseDefaultBinSize(true), m_UseBinSizeBasedOnVOIRegion(false), m_HotspotRadiusInMM(6.2035049089940), // radius of a 1cm3 sphere in mm m_CalculateHotspot(false), m_HotspotRadiusInMMChanged(false), m_HotspotMustBeCompletelyInsideImage(true) { m_EmptyHistogram = HistogramType::New(); m_EmptyHistogram->SetMeasurementVectorSize(1); HistogramType::SizeType histogramSize(1); histogramSize.Fill( 256 ); m_EmptyHistogram->Initialize( histogramSize ); m_EmptyStatistics.Reset(); } ImageStatisticsCalculator::~ImageStatisticsCalculator() { } void ImageStatisticsCalculator::SetUseDefaultBinSize(bool useDefault) { m_UseDefaultBinSize = useDefault; } ImageStatisticsCalculator::Statistics::Statistics(bool withHotspotStatistics) :m_HotspotStatistics(withHotspotStatistics ? new Statistics(false) : nullptr) { Reset(); } ImageStatisticsCalculator::Statistics::Statistics(const Statistics& other) :m_HotspotStatistics( nullptr) { this->SetLabel( other.GetLabel() ); this->SetN( other.GetN() ); this->SetMin( other.GetMin() ); this->SetMax( other.GetMax() ); this->SetMedian( other.GetMedian() ); this->SetMean( other.GetMean() ); this->SetVariance( other.GetVariance() ); this->SetKurtosis( other.GetKurtosis() ); this->SetSkewness( other.GetSkewness() ); this->SetUniformity( other.GetUniformity() ); this->SetEntropy( other.GetEntropy() ); this->SetUPP( other.GetUPP() ); this->SetMPP( other.GetMPP() ); this->SetSigma( other.GetSigma() ); this->SetRMS( other.GetRMS() ); this->SetMaxIndex( other.GetMaxIndex() ); this->SetMinIndex( other.GetMinIndex() ); this->SetHotspotIndex( other.GetHotspotIndex() ); if (other.m_HotspotStatistics) { this->m_HotspotStatistics = new Statistics(false); *this->m_HotspotStatistics = *other.m_HotspotStatistics; } } bool ImageStatisticsCalculator::Statistics::HasHotspotStatistics() const { return m_HotspotStatistics != nullptr; } void ImageStatisticsCalculator::Statistics::SetHasHotspotStatistics(bool hasHotspotStatistics) { m_HasHotspotStatistics = hasHotspotStatistics; } ImageStatisticsCalculator::Statistics::~Statistics() { delete m_HotspotStatistics; } double ImageStatisticsCalculator::Statistics::GetVariance() const { return this->Variance; } void ImageStatisticsCalculator::Statistics::SetVariance( const double value ) { if( this->Variance != value ) { if( value < 0.0 ) { this->Variance = 0.0; // if given value is negative set variance to 0.0 } else { this->Variance = value; } } } double ImageStatisticsCalculator::Statistics::GetSigma() const { return this->Sigma; } void ImageStatisticsCalculator::Statistics::SetSigma( const double value ) { if( this->Sigma != value ) { // for some compiler the value != value works to check for NaN but not for all // but we can always be sure that the standard deviation is a positive value if( value != value || value < 0.0 ) { // if standard deviation is NaN we just assume 0.0 this->Sigma = 0.0; } else { this->Sigma = value; } } } void ImageStatisticsCalculator::Statistics::Reset(unsigned int dimension) { SetLabel(0); SetN( 0 ); SetMin( 0.0 ); SetMax( 0.0 ); SetMedian( 0.0 ); SetVariance( 0.0 ); SetMean( 0.0 ); SetSigma( 0.0 ); SetRMS( 0.0 ); SetSkewness( 0.0 ); SetKurtosis( 0.0 ); SetUniformity( 0.0 ); SetEntropy( 0.0 ); SetMPP( 0.0 ); SetUPP( 0.0 ); vnl_vector zero; zero.set_size(dimension); for(unsigned int i = 0; i < dimension; ++i) { zero[i] = 0; } SetMaxIndex(zero); SetMinIndex(zero); SetHotspotIndex(zero); if (m_HotspotStatistics != nullptr) { m_HotspotStatistics->Reset(dimension); } } const ImageStatisticsCalculator::Statistics& ImageStatisticsCalculator::Statistics::GetHotspotStatistics() const { if (m_HotspotStatistics) { return *m_HotspotStatistics; } else { throw std::logic_error("Object has no hostspot statistics, see HasHotspotStatistics()"); } } ImageStatisticsCalculator::Statistics& ImageStatisticsCalculator::Statistics::GetHotspotStatistics() { if (m_HotspotStatistics) { return *m_HotspotStatistics; } else { throw std::logic_error("Object has no hostspot statistics, see HasHotspotStatistics()"); } } ImageStatisticsCalculator::Statistics& ImageStatisticsCalculator::Statistics::operator=(ImageStatisticsCalculator::Statistics const& other) { if (this == &other) return *this; this->SetLabel( other.GetLabel() ); this->SetN( other.GetN() ); this->SetMin( other.GetMin() ); this->SetMax( other.GetMax() ); this->SetMean( other.GetMean() ); this->SetMedian( other.GetMedian() ); this->SetVariance( other.GetVariance() ); this->SetSigma( other.GetSigma() ); this->SetRMS( other.GetRMS() ); this->SetMinIndex( other.GetMinIndex() ); this->SetMaxIndex( other.GetMaxIndex() ); this->SetHotspotIndex( other.GetHotspotIndex() ); this->SetSkewness( other.GetSkewness() ); this->SetKurtosis( other.GetKurtosis() ); this->SetUniformity( other.GetUniformity() ); this->SetEntropy( other.GetEntropy() ); this->SetUPP( other.GetUPP() ); this->SetMPP( other.GetMPP() ); delete this->m_HotspotStatistics; this->m_HotspotStatistics = nullptr; if (other.m_HotspotStatistics) { this->m_HotspotStatistics = new Statistics(false); *this->m_HotspotStatistics = *other.m_HotspotStatistics; } return *this; } void ImageStatisticsCalculator::SetImage( const mitk::Image *image ) { if ( m_Image != image ) { m_Image = image; this->Modified(); unsigned int numberOfTimeSteps = image->GetTimeSteps(); // Initialize vectors to time-size of this image m_ImageHistogramVector.resize( numberOfTimeSteps ); m_MaskedImageHistogramVector.resize( numberOfTimeSteps ); m_PlanarFigureHistogramVector.resize( numberOfTimeSteps ); m_ImageStatisticsVector.resize( numberOfTimeSteps ); m_MaskedImageStatisticsVector.resize( numberOfTimeSteps ); m_PlanarFigureStatisticsVector.resize( numberOfTimeSteps ); m_ImageStatisticsTimeStampVector.resize( numberOfTimeSteps ); m_MaskedImageStatisticsTimeStampVector.resize( numberOfTimeSteps ); m_PlanarFigureStatisticsTimeStampVector.resize( numberOfTimeSteps ); m_ImageStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); m_MaskedImageStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); m_PlanarFigureStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); for ( unsigned int t = 0; t < image->GetTimeSteps(); ++t ) { m_ImageStatisticsTimeStampVector[t].Modified(); m_ImageStatisticsCalculationTriggerVector[t] = true; } } } void ImageStatisticsCalculator::SetImageMask( const mitk::Image *imageMask ) { if ( m_Image.IsNull() ) { itkExceptionMacro( << "Image needs to be set first!" ); } if ( m_ImageMask != imageMask ) { m_ImageMask = imageMask; this->Modified(); for ( unsigned int t = 0; t < m_Image->GetTimeSteps(); ++t ) { m_MaskedImageStatisticsTimeStampVector[t].Modified(); m_MaskedImageStatisticsCalculationTriggerVector[t] = true; } } } void ImageStatisticsCalculator::SetPlanarFigure( mitk::PlanarFigure *planarFigure ) { if ( m_Image.IsNull() ) { itkExceptionMacro( << "Image needs to be set first!" ); } if ( m_PlanarFigure != planarFigure ) { m_PlanarFigure = planarFigure; this->Modified(); for ( unsigned int t = 0; t < m_Image->GetTimeSteps(); ++t ) { m_PlanarFigureStatisticsTimeStampVector[t].Modified(); m_PlanarFigureStatisticsCalculationTriggerVector[t] = true; } } } void ImageStatisticsCalculator::SetMaskingMode( unsigned int mode ) { if ( m_MaskingMode != mode ) { m_MaskingMode = mode; m_MaskingModeChanged = true; this->Modified(); } } void ImageStatisticsCalculator::SetMaskingModeToNone() { if ( m_MaskingMode != MASKING_MODE_NONE ) { m_MaskingMode = MASKING_MODE_NONE; m_MaskingModeChanged = true; this->Modified(); } } void ImageStatisticsCalculator::SetMaskingModeToImage() { if ( m_MaskingMode != MASKING_MODE_IMAGE ) { m_MaskingMode = MASKING_MODE_IMAGE; m_MaskingModeChanged = true; this->Modified(); } } void ImageStatisticsCalculator::SetMaskingModeToPlanarFigure() { if ( m_MaskingMode != MASKING_MODE_PLANARFIGURE ) { m_MaskingMode = MASKING_MODE_PLANARFIGURE; m_MaskingModeChanged = true; this->Modified(); } } void ImageStatisticsCalculator::SetIgnorePixelValue(double value) { if ( m_IgnorePixelValue != value ) { m_IgnorePixelValue = value; if(m_DoIgnorePixelValue) { m_IgnorePixelValueChanged = true; } this->Modified(); } } double ImageStatisticsCalculator::GetIgnorePixelValue() { return m_IgnorePixelValue; } void ImageStatisticsCalculator::SetDoIgnorePixelValue(bool value) { if ( m_DoIgnorePixelValue != value ) { m_DoIgnorePixelValue = value; m_IgnorePixelValueChanged = true; this->Modified(); } } bool ImageStatisticsCalculator::GetDoIgnorePixelValue() { return m_DoIgnorePixelValue; } void ImageStatisticsCalculator::SetHistogramBinSize(double size) { this->m_HistogramBinSize = size; } double ImageStatisticsCalculator::GetHistogramBinSize() { return this->m_HistogramBinSize; } void ImageStatisticsCalculator::SetHotspotRadiusInMM(double value) { if ( m_HotspotRadiusInMM != value ) { m_HotspotRadiusInMM = value; if(m_CalculateHotspot) { m_HotspotRadiusInMMChanged = true; //MITK_INFO <<"Hotspot radius changed, new convolution required"; } this->Modified(); } } double ImageStatisticsCalculator::GetHotspotRadiusInMM() { return m_HotspotRadiusInMM; } void ImageStatisticsCalculator::SetCalculateHotspot(bool on) { if ( m_CalculateHotspot != on ) { m_CalculateHotspot = on; m_HotspotRadiusInMMChanged = true; //MITK_INFO <<"Hotspot calculation changed, new convolution required"; this->Modified(); } } bool ImageStatisticsCalculator::IsHotspotCalculated() { return m_CalculateHotspot; } void ImageStatisticsCalculator::SetHotspotMustBeCompletlyInsideImage(bool hotspotMustBeCompletelyInsideImage, bool warn) { m_HotspotMustBeCompletelyInsideImage = hotspotMustBeCompletelyInsideImage; if (!m_HotspotMustBeCompletelyInsideImage && warn) { MITK_WARN << "Hotspot calculation will extrapolate pixels at image borders. Be aware of the consequences for the hotspot location."; } } bool ImageStatisticsCalculator::GetHotspotMustBeCompletlyInsideImage() const { return m_HotspotMustBeCompletelyInsideImage; } /* Implementation of the min max values for setting the range of the histogram */ template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::GetMinAndMaxValue( double &min, double &max, int &counter, double &sigma, const itk::Image< TPixel, VImageDimension > *InputImage, itk::Image< unsigned short, VImageDimension > *MaskedImage ) { typedef itk::Image< unsigned short, VImageDimension > MaskImageType; typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::ImageRegionConstIteratorWithIndex Imageie; typedef itk::ImageRegionConstIteratorWithIndex Imageie2; Imageie2 labelIterator2( MaskedImage, MaskedImage->GetRequestedRegion() ); Imageie labelIterator3( InputImage, InputImage->GetRequestedRegion() ); max = 0; min = 0; counter = 0; sigma = 0; double SumOfSquares = 0; double sumSquared = 0; double actualPielValue = 0; int counterOfPixelsInROI = 0; for( labelIterator2.GoToBegin(); !labelIterator2.IsAtEnd(); ++labelIterator2, ++labelIterator3) { if( labelIterator2.Value()== 1.0) { counter++; counterOfPixelsInROI++; actualPielValue = labelIterator3.Value(); sumSquared = sumSquared + actualPielValue; SumOfSquares = SumOfSquares + std::pow(actualPielValue,2); if(counterOfPixelsInROI == 1) { max = actualPielValue; min = actualPielValue; } if(actualPielValue >= max) { max = actualPielValue; } else if(actualPielValue <= min) { min = actualPielValue; } } } if (counter > 1) { sigma = ( SumOfSquares - std::pow( sumSquared, 2) / counter ) / ( counter-1 ); } else { sigma = 0; } } bool ImageStatisticsCalculator::ComputeStatistics( unsigned int timeStep ) { if (m_Image.IsNull() ) { mitkThrow() << "Image not set!"; } if (!m_Image->IsInitialized()) { mitkThrow() << "Image not initialized!"; } if ( m_Image->GetReferenceCount() == 1 ) { // Image no longer valid; we are the only ones to still hold a reference on it return false; } if ( timeStep >= m_Image->GetTimeSteps() ) { throw std::runtime_error( "Error: invalid time step!" ); } // If a mask was set but we are the only ones to still hold a reference on // it, delete it. if ( m_ImageMask.IsNotNull() && (m_ImageMask->GetReferenceCount() == 1) ) { m_ImageMask = nullptr; } // Check if statistics is already up-to-date unsigned long imageMTime = m_ImageStatisticsTimeStampVector[timeStep].GetMTime(); unsigned long maskedImageMTime = m_MaskedImageStatisticsTimeStampVector[timeStep].GetMTime(); unsigned long planarFigureMTime = m_PlanarFigureStatisticsTimeStampVector[timeStep].GetMTime(); bool imageStatisticsCalculationTrigger = m_ImageStatisticsCalculationTriggerVector[timeStep]; bool maskedImageStatisticsCalculationTrigger = m_MaskedImageStatisticsCalculationTriggerVector[timeStep]; bool planarFigureStatisticsCalculationTrigger = m_PlanarFigureStatisticsCalculationTriggerVector[timeStep]; if ( !m_IgnorePixelValueChanged && !m_HotspotRadiusInMMChanged && ((m_MaskingMode != MASKING_MODE_NONE) || (imageMTime > m_Image->GetMTime() && !imageStatisticsCalculationTrigger)) && ((m_MaskingMode != MASKING_MODE_IMAGE) || (maskedImageMTime > m_ImageMask->GetMTime() && !maskedImageStatisticsCalculationTrigger)) && ((m_MaskingMode != MASKING_MODE_PLANARFIGURE) || (planarFigureMTime > m_PlanarFigure->GetMTime() && !planarFigureStatisticsCalculationTrigger)) ) { // Statistics is up to date! if ( m_MaskingModeChanged ) { m_MaskingModeChanged = false; } else { return false; } } // Reset state changed flag m_MaskingModeChanged = false; m_IgnorePixelValueChanged = false; // Depending on masking mode, extract and/or generate the required image // and mask data from the user input this->ExtractImageAndMask( timeStep ); StatisticsContainer *statisticsContainer; HistogramContainer *histogramContainer; switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: if(!m_DoIgnorePixelValue) { statisticsContainer = &m_ImageStatisticsVector[timeStep]; histogramContainer = &m_ImageHistogramVector[timeStep]; m_ImageStatisticsTimeStampVector[timeStep].Modified(); m_ImageStatisticsCalculationTriggerVector[timeStep] = false; } else { statisticsContainer = &m_MaskedImageStatisticsVector[timeStep]; histogramContainer = &m_MaskedImageHistogramVector[timeStep]; m_MaskedImageStatisticsTimeStampVector[timeStep].Modified(); m_MaskedImageStatisticsCalculationTriggerVector[timeStep] = false; } break; case MASKING_MODE_IMAGE: statisticsContainer = &m_MaskedImageStatisticsVector[timeStep]; histogramContainer = &m_MaskedImageHistogramVector[timeStep]; m_MaskedImageStatisticsTimeStampVector[timeStep].Modified(); m_MaskedImageStatisticsCalculationTriggerVector[timeStep] = false; break; case MASKING_MODE_PLANARFIGURE: statisticsContainer = &m_PlanarFigureStatisticsVector[timeStep]; histogramContainer = &m_PlanarFigureHistogramVector[timeStep]; m_PlanarFigureStatisticsTimeStampVector[timeStep].Modified(); m_PlanarFigureStatisticsCalculationTriggerVector[timeStep] = false; break; } // Calculate statistics and histogram(s) if ( m_InternalImage->GetDimension() == 3 ) { if ( m_MaskingMode == MASKING_MODE_NONE && !m_DoIgnorePixelValue ) { AccessFixedDimensionByItk_2( m_InternalImage, InternalCalculateStatisticsUnmasked, 3, statisticsContainer, histogramContainer ); } else { AccessFixedDimensionByItk_3( m_InternalImage, InternalCalculateStatisticsMasked, 3, m_InternalImageMask3D.GetPointer(), statisticsContainer, histogramContainer ); } } else if ( m_InternalImage->GetDimension() == 2 ) { if ( m_MaskingMode == MASKING_MODE_NONE && !m_DoIgnorePixelValue ) { AccessFixedDimensionByItk_2( m_InternalImage, InternalCalculateStatisticsUnmasked, 2, statisticsContainer, histogramContainer ); } else { AccessFixedDimensionByItk_3( m_InternalImage, InternalCalculateStatisticsMasked, 2, m_InternalImageMask2D.GetPointer(), statisticsContainer, histogramContainer ); } } else { MITK_ERROR << "ImageStatistics: Image dimension not supported!"; } // Release unused image smart pointers to free memory m_InternalImage = mitk::Image::ConstPointer(); m_InternalImageMask3D = MaskImage3DType::Pointer(); m_InternalImageMask2D = MaskImage2DType::Pointer(); return true; } ImageStatisticsCalculator::BinFrequencyType ImageStatisticsCalculator::GetBinsAndFreuqencyForHistograms( unsigned int timeStep , unsigned int label ) const { const HistogramType *binsAndFrequencyToCalculate = this->GetHistogram(0); // ToDo: map should be created on stack not on heap std::map returnedHistogramMap; unsigned int size = binsAndFrequencyToCalculate->Size(); for( unsigned int bin=0; bin < size; ++bin ) { double frequency = binsAndFrequencyToCalculate->GetFrequency( bin, 0 ); //if( frequency > mitk::eps ) { returnedHistogramMap.insert( std::pair(binsAndFrequencyToCalculate->GetMeasurement( bin, 0 ), binsAndFrequencyToCalculate->GetFrequency( bin, 0 ) ) ); } } return returnedHistogramMap; } const ImageStatisticsCalculator::HistogramType * ImageStatisticsCalculator::GetHistogram( unsigned int timeStep, unsigned int label ) const { if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) { return nullptr; } switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: { if(m_DoIgnorePixelValue) return m_MaskedImageHistogramVector[timeStep][label]; return m_ImageHistogramVector[timeStep][label]; } case MASKING_MODE_IMAGE: return m_MaskedImageHistogramVector[timeStep][label]; case MASKING_MODE_PLANARFIGURE: return m_PlanarFigureHistogramVector[timeStep][label]; } } const ImageStatisticsCalculator::HistogramContainer & ImageStatisticsCalculator::GetHistogramVector( unsigned int timeStep ) const { if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) { return m_EmptyHistogramContainer; } switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: { if(m_DoIgnorePixelValue) return m_MaskedImageHistogramVector[timeStep]; return m_ImageHistogramVector[timeStep]; } case MASKING_MODE_IMAGE: return m_MaskedImageHistogramVector[timeStep]; case MASKING_MODE_PLANARFIGURE: return m_PlanarFigureHistogramVector[timeStep]; } } const ImageStatisticsCalculator::Statistics & ImageStatisticsCalculator::GetStatistics( unsigned int timeStep, unsigned int label ) const { if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) { return m_EmptyStatistics; } switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: { if(m_DoIgnorePixelValue) return m_MaskedImageStatisticsVector[timeStep][label]; return m_ImageStatisticsVector[timeStep][label]; } case MASKING_MODE_IMAGE: return m_MaskedImageStatisticsVector[timeStep][label]; case MASKING_MODE_PLANARFIGURE: return m_PlanarFigureStatisticsVector[timeStep][label]; } } const ImageStatisticsCalculator::StatisticsContainer & ImageStatisticsCalculator::GetStatisticsVector( unsigned int timeStep ) const { if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) { return m_EmptyStatisticsContainer; } switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: { if(m_DoIgnorePixelValue) return m_MaskedImageStatisticsVector[timeStep]; return m_ImageStatisticsVector[timeStep]; } case MASKING_MODE_IMAGE: return m_MaskedImageStatisticsVector[timeStep]; case MASKING_MODE_PLANARFIGURE: return m_PlanarFigureStatisticsVector[timeStep]; } } void ImageStatisticsCalculator::ExtractImageAndMask( unsigned int timeStep ) { if ( m_Image.IsNull() ) { throw std::runtime_error( "Error: image empty!" ); } if ( timeStep >= m_Image->GetTimeSteps() ) { throw std::runtime_error( "Error: invalid time step!" ); } ImageTimeSelector::Pointer imageTimeSelector = ImageTimeSelector::New(); imageTimeSelector->SetInput( m_Image ); imageTimeSelector->SetTimeNr( timeStep ); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image *timeSliceImage = imageTimeSelector->GetOutput(); switch ( m_MaskingMode ) { case MASKING_MODE_NONE: { m_InternalImage = timeSliceImage; m_InternalImageMask2D = nullptr; m_InternalImageMask3D = nullptr; if(m_DoIgnorePixelValue) { if( m_InternalImage->GetDimension() == 3 ) { if(itk::ImageIOBase::USHORT != timeSliceImage->GetPixelType().GetComponentType()) CastToItkImage( timeSliceImage, m_InternalImageMask3D ); else CastToItkImage( timeSliceImage->Clone(), m_InternalImageMask3D ); m_InternalImageMask3D->FillBuffer(1); } if( m_InternalImage->GetDimension() == 2 ) { if(itk::ImageIOBase::USHORT != timeSliceImage->GetPixelType().GetComponentType()) CastToItkImage( timeSliceImage, m_InternalImageMask2D ); else CastToItkImage( timeSliceImage->Clone(), m_InternalImageMask2D ); m_InternalImageMask2D->FillBuffer(1); } } break; } case MASKING_MODE_IMAGE: { if ( m_ImageMask.IsNotNull() && (m_ImageMask->GetReferenceCount() > 1) ) { if ( timeStep >= m_ImageMask->GetTimeSteps() ) { // Use the last mask time step in case the current time step is bigger than the total // number of mask time steps. // It makes more sense setting this to the last mask time step than to 0. // For instance if you have a mask with 2 time steps and an image with 5: // If time step 0 is selected, the mask will use time step 0. // If time step 1 is selected, the mask will use time step 1. // If time step 2+ is selected, the mask will use time step 1. // If you have a mask with only one time step instead, this will always default to 0. timeStep = m_ImageMask->GetTimeSteps() - 1; } ImageTimeSelector::Pointer maskedImageTimeSelector = ImageTimeSelector::New(); maskedImageTimeSelector->SetInput( m_ImageMask ); maskedImageTimeSelector->SetTimeNr( timeStep ); maskedImageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image *timeSliceMaskedImage = maskedImageTimeSelector->GetOutput(); m_InternalImage = timeSliceImage; CastToItkImage( timeSliceMaskedImage, m_InternalImageMask3D ); } else { throw std::runtime_error( "Error: image mask empty!" ); } break; } case MASKING_MODE_PLANARFIGURE: { m_InternalImageMask2D = nullptr; if ( m_PlanarFigure.IsNull() ) { throw std::runtime_error( "Error: planar figure empty!" ); } if ( !m_PlanarFigure->IsClosed() ) { throw std::runtime_error( "Masking not possible for non-closed figures" ); } const BaseGeometry *imageGeometry = timeSliceImage->GetGeometry(); if ( imageGeometry == nullptr ) { throw std::runtime_error( "Image geometry invalid!" ); } const PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); if ( planarFigurePlaneGeometry == nullptr ) { throw std::runtime_error( "Planar-Figure not yet initialized!" ); } const PlaneGeometry *planarFigureGeometry = dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); if ( planarFigureGeometry == nullptr ) { throw std::runtime_error( "Non-planar planar figures not supported!" ); } // Find principal direction of PlanarFigure in input image unsigned int axis; if ( !this->GetPrincipalAxis( imageGeometry, planarFigureGeometry->GetNormal(), axis ) ) { throw std::runtime_error( "Non-aligned planar figures not supported!" ); } m_PlanarFigureAxis = axis; // Find slice number corresponding to PlanarFigure in input image MaskImage3DType::IndexType index; imageGeometry->WorldToIndex( planarFigureGeometry->GetOrigin(), index ); unsigned int slice = index[axis]; m_PlanarFigureSlice = slice; // Extract slice with given position and direction from image unsigned int dimension = timeSliceImage->GetDimension(); if (dimension != 2) { ExtractImageFilter::Pointer imageExtractor = ExtractImageFilter::New(); imageExtractor->SetInput( timeSliceImage ); imageExtractor->SetSliceDimension( axis ); imageExtractor->SetSliceIndex( slice ); imageExtractor->Update(); m_InternalImage = imageExtractor->GetOutput(); } else { m_InternalImage = timeSliceImage; } // Compute mask from PlanarFigure AccessFixedDimensionByItk_1( m_InternalImage, InternalCalculateMaskFromPlanarFigure, 2, axis ); } } if(m_DoIgnorePixelValue) { if ( m_InternalImage->GetDimension() == 3 ) { AccessFixedDimensionByItk_1( m_InternalImage, InternalMaskIgnoredPixels, 3, m_InternalImageMask3D.GetPointer() ); } else if ( m_InternalImage->GetDimension() == 2 ) { AccessFixedDimensionByItk_1( m_InternalImage, InternalMaskIgnoredPixels, 2, m_InternalImageMask2D.GetPointer() ); } } } bool ImageStatisticsCalculator::GetPrincipalAxis( const BaseGeometry *geometry, Vector3D vector, unsigned int &axis ) { vector.Normalize(); for ( unsigned int i = 0; i < 3; ++i ) { Vector3D axisVector = geometry->GetAxisVector( i ); axisVector.Normalize(); if ( fabs( fabs( axisVector * vector ) - 1.0) < mitk::eps ) { axis = i; return true; } } return false; } unsigned int ImageStatisticsCalculator::calcNumberOfBins(mitk::ScalarType min, mitk::ScalarType max) { return std::ceil( ( (max - min ) / m_HistogramBinSize) ); } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsUnmasked( const itk::Image< TPixel, VImageDimension > *image, StatisticsContainer *statisticsContainer, HistogramContainer* histogramContainer ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef typename ImageType::IndexType IndexType; typedef itk::Statistics::ScalarImageToHistogramGenerator< ImageType > HistogramGeneratorType; statisticsContainer->clear(); histogramContainer->clear(); // Progress listening... typedef itk::SimpleMemberCommand< ImageStatisticsCalculator > ITKCommandType; ITKCommandType::Pointer progressListener; progressListener = ITKCommandType::New(); progressListener->SetCallbackFunction( this, &ImageStatisticsCalculator::UnmaskedStatisticsProgressUpdate ); // Issue 100 artificial progress events since ScalarIMageToHistogramGenerator // does not (yet?) support progress reporting this->InvokeEvent( itk::StartEvent() ); for ( unsigned int i = 0; i < 100; ++i ) { this->UnmaskedStatisticsProgressUpdate(); } // Calculate statistics (separate filter) typedef itk::ExtendedStatisticsImageFilter< ImageType > StatisticsFilterType; typename StatisticsFilterType::Pointer statisticsFilter = StatisticsFilterType::New(); statisticsFilter->SetInput( image ); statisticsFilter->SetBinSize( 100 ); statisticsFilter->SetCoordinateTolerance( 0.001 ); statisticsFilter->SetDirectionTolerance( 0.001 ); unsigned long observerTag = statisticsFilter->AddObserver( itk::ProgressEvent(), progressListener ); try { statisticsFilter->Update(); } catch (const itk::ExceptionObject& e) { mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } catch( const std::exception& e ) { //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } statisticsFilter->RemoveObserver( observerTag ); this->InvokeEvent( itk::EndEvent() ); // Calculate minimum and maximum typedef itk::MinimumMaximumImageCalculator< ImageType > MinMaxFilterType; typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); minMaxFilter->SetImage( image ); unsigned long observerTag2 = minMaxFilter->AddObserver( itk::ProgressEvent(), progressListener ); minMaxFilter->Compute(); minMaxFilter->RemoveObserver( observerTag2 ); this->InvokeEvent( itk::EndEvent() ); Statistics statistics; statistics.Reset(); statistics.SetLabel(1); statistics.SetN(image->GetBufferedRegion().GetNumberOfPixels()); statistics.SetMin(statisticsFilter->GetMinimum()); statistics.SetMax(statisticsFilter->GetMaximum()); statistics.SetMean(statisticsFilter->GetMean()); statistics.SetMedian(statisticsFilter->GetMedian()); statistics.SetVariance(statisticsFilter->GetVariance()); statistics.SetSkewness(statisticsFilter->GetSkewness()); statistics.SetKurtosis(statisticsFilter->GetKurtosis()); statistics.SetUniformity( statisticsFilter->GetUniformity()); statistics.SetEntropy( statisticsFilter->GetEntropy()); statistics.SetUPP( statisticsFilter->GetUPP()); statistics.SetMPP( statisticsFilter->GetMPP()); statistics.SetSigma(statisticsFilter->GetSigma()); statistics.SetRMS(sqrt( statistics.GetMean() * statistics.GetMean() + statistics.GetSigma() * statistics.GetSigma() )); statistics.GetMinIndex().set_size(image->GetImageDimension()); statistics.GetMaxIndex().set_size(image->GetImageDimension()); - vnl_vector maxIndex; - vnl_vector minIndex; - - maxIndex.set_size( VImageDimension ); - minIndex.set_size( VImageDimension ); + vnl_vector tmpMaxIndex; + vnl_vector tmpMinIndex; - typename MinMaxFilterType::IndexType tempMaxIndex = minMaxFilter->GetIndexOfMaximum(); - typename MinMaxFilterType::IndexType tempMinIndex = minMaxFilter->GetIndexOfMinimum(); + tmpMaxIndex.set_size(image->GetImageDimension() ); + tmpMinIndex.set_size(image->GetImageDimension() ); - for (unsigned int i=0; i GetIndexOfMaximum()[i]; + tmpMinIndex[i] = minMaxFilter->GetIndexOfMinimum()[i]; } - statistics.SetMaxIndex(maxIndex); - statistics.SetMinIndex(minIndex); + statistics.SetMinIndex(tmpMaxIndex); + statistics.SetMinIndex(tmpMinIndex); if( IsHotspotCalculated() && VImageDimension == 3 ) { typedef itk::Image< unsigned short, VImageDimension > MaskImageType; typename MaskImageType::Pointer nullMask; bool isHotspotDefined(false); Statistics hotspotStatistics = this->CalculateHotspotStatistics(image, nullMask.GetPointer(), m_HotspotRadiusInMM, isHotspotDefined, 0 ); if (isHotspotDefined) { statistics.SetHasHotspotStatistics(true); statistics.GetHotspotStatistics() = hotspotStatistics; } else { statistics.SetHasHotspotStatistics(false); } if(statistics.GetHotspotStatistics().HasHotspotStatistics() ) { MITK_DEBUG << "Hotspot statistics available"; statistics.SetHotspotIndex(hotspotStatistics.GetHotspotIndex()); } else { MITK_ERROR << "No hotspot statistics available!"; } } statisticsContainer->push_back( statistics ); // Calculate histogram // calculate bin size or number of bins unsigned int numberOfBins = 200; // default number of bins if (m_UseDefaultBinSize) { m_HistogramBinSize = std::ceil( (statistics.GetMax() - statistics.GetMin() + 1)/numberOfBins ); } else { numberOfBins = calcNumberOfBins(statistics.GetMin(), statistics.GetMax()); } typename HistogramGeneratorType::Pointer histogramGenerator = HistogramGeneratorType::New(); histogramGenerator->SetInput( image ); histogramGenerator->SetMarginalScale( 100 ); histogramGenerator->SetNumberOfBins( numberOfBins ); histogramGenerator->SetHistogramMin( statistics.GetMin() ); histogramGenerator->SetHistogramMax( statistics.GetMax() ); histogramGenerator->Compute(); histogramContainer->push_back( histogramGenerator->GetOutput() ); } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalMaskIgnoredPixels( const itk::Image< TPixel, VImageDimension > *image, itk::Image< unsigned short, VImageDimension > *maskImage ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::Image< unsigned short, VImageDimension > MaskImageType; itk::ImageRegionIterator itmask(maskImage, maskImage->GetLargestPossibleRegion()); itk::ImageRegionConstIterator itimage(image, image->GetLargestPossibleRegion()); itmask.GoToBegin(); itimage.GoToBegin(); while( !itmask.IsAtEnd() ) { if(m_IgnorePixelValue == itimage.Get()) { itmask.Set(0); } ++itmask; ++itimage; } } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsMasked( const itk::Image< TPixel, VImageDimension > *image, itk::Image< unsigned short, VImageDimension > *maskImage, StatisticsContainer* statisticsContainer, HistogramContainer* histogramContainer ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::Image< unsigned short, VImageDimension > MaskImageType; typedef typename ImageType::IndexType IndexType; typedef typename ImageType::PointType PointType; typedef typename ImageType::SpacingType SpacingType; typedef typename ImageType::Pointer ImagePointer; typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, MaskImageType > LabelStatisticsFilterType; typedef itk::ChangeInformationImageFilter< MaskImageType > ChangeInformationFilterType; typedef itk::ExtractImageFilter< ImageType, ImageType > ExtractImageFilterType; statisticsContainer->clear(); histogramContainer->clear(); // Make sure that mask is set if ( maskImage == nullptr ) { itkExceptionMacro( << "Mask image needs to be set!" ); } // Make sure that spacing of mask and image are the same //SpacingType imageSpacing = image->GetSpacing(); //SpacingType maskSpacing = maskImage->GetSpacing(); //PointType zeroPoint; zeroPoint.Fill( 0.0 ); //if ( (zeroPoint + imageSpacing).SquaredEuclideanDistanceTo( (zeroPoint + maskSpacing) ) > mitk::eps ) //{ // itkExceptionMacro( << "Mask needs to have same spacing as image! (Image spacing: " << imageSpacing << "; Mask spacing: " << maskSpacing << ")" ); //} // Make sure that orientation of mask and image are the same typedef typename ImageType::DirectionType DirectionType; DirectionType imageDirection = image->GetDirection(); DirectionType maskDirection = maskImage->GetDirection(); for( int i = 0; i < imageDirection.ColumnDimensions; ++i ) { for( int j = 0; j < imageDirection.ColumnDimensions; ++j ) { double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; if ( fabs( differenceDirection ) > mitk::eps ) { double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; if ( fabs( differenceDirection ) > 0.001 /*mitk::eps*/ ) // TODO: temp fix (bug 17121) { itkExceptionMacro( << "Mask needs to have same direction as image! (Image direction: " << imageDirection << "; Mask direction: " << maskDirection << ")" ); } } } } // Make sure that the voxels of mask and image are correctly "aligned", i.e., voxel boundaries are the same in both images PointType imageOrigin = image->GetOrigin(); PointType maskOrigin = maskImage->GetOrigin(); long offset[ImageType::ImageDimension]; typedef itk::ContinuousIndex ContinousIndexType; ContinousIndexType maskOriginContinousIndex, imageOriginContinousIndex; image->TransformPhysicalPointToContinuousIndex(maskOrigin, maskOriginContinousIndex); image->TransformPhysicalPointToContinuousIndex(imageOrigin, imageOriginContinousIndex); for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) { double misalignment = maskOriginContinousIndex[i] - floor( maskOriginContinousIndex[i] + 0.5 ); if ( fabs( misalignment ) > mitk::eps ) { itkWarningMacro( << "Pixels/voxels of mask and image are not sufficiently aligned! (Misalignment: " << misalignment << ")" ); } double indexCoordDistance = maskOriginContinousIndex[i] - imageOriginContinousIndex[i]; offset[i] = int( indexCoordDistance + image->GetBufferedRegion().GetIndex()[i] + 0.5 ); } // Adapt the origin and region (index/size) of the mask so that the origin of both are the same typename ChangeInformationFilterType::Pointer adaptMaskFilter; adaptMaskFilter = ChangeInformationFilterType::New(); adaptMaskFilter->ChangeOriginOn(); adaptMaskFilter->ChangeRegionOn(); adaptMaskFilter->SetInput( maskImage ); adaptMaskFilter->SetOutputOrigin( image->GetOrigin() ); adaptMaskFilter->SetOutputOffset( offset ); adaptMaskFilter->SetCoordinateTolerance( 0.001 ); adaptMaskFilter->SetDirectionTolerance( 0.001 ); typename MaskImageType::Pointer adaptedMaskImage; try { adaptMaskFilter->Update(); adaptedMaskImage = adaptMaskFilter->GetOutput(); } catch( const itk::ExceptionObject &e) { mitkThrow() << "Attempt to adapt shifted origin of the mask image failed due to ITK Exception: \n" << e.what(); } catch( const std::exception& e ) { //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } // Make sure that mask region is contained within image region if ( adaptedMaskImage.IsNotNull() && !image->GetLargestPossibleRegion().IsInside( adaptedMaskImage->GetLargestPossibleRegion() ) ) { itkWarningMacro( << "Mask region needs to be inside of image region! (Image region: " << image->GetLargestPossibleRegion() << "; Mask region: " << adaptedMaskImage->GetLargestPossibleRegion() << ")" ); } // If mask region is smaller than image region, extract the sub-sampled region from the original image typename ImageType::SizeType imageSize = image->GetBufferedRegion().GetSize(); typename ImageType::SizeType maskSize = maskImage->GetBufferedRegion().GetSize(); bool maskSmallerImage = false; for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) { if ( maskSize[i] < imageSize[i] ) { maskSmallerImage = true; } } typename ImageType::ConstPointer adaptedImage; if ( maskSmallerImage ) { typename ExtractImageFilterType::Pointer extractImageFilter = ExtractImageFilterType::New(); extractImageFilter->SetInput( image ); extractImageFilter->SetExtractionRegion( adaptedMaskImage->GetBufferedRegion() ); extractImageFilter->SetCoordinateTolerance( 0.001 ); extractImageFilter->SetDirectionTolerance( 0.001 ); extractImageFilter->Update(); adaptedImage = extractImageFilter->GetOutput(); } else { adaptedImage = image; } // Initialize Filter typedef itk::StatisticsImageFilter< ImageType > StatisticsFilterType; typename StatisticsFilterType::Pointer statisticsFilter = StatisticsFilterType::New(); statisticsFilter->SetInput( adaptedImage ); try { statisticsFilter->Update(); } catch( const itk::ExceptionObject& e) { mitkThrow() << "Image statistics initialization computation failed with ITK Exception: \n " << e.what(); } catch( const std::exception& e ) { //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } // Calculate bin size or number of bins - unsigned int numberOfBins = 200; // default number of bins + unsigned int numberOfBins = m_HistogramBinSize; // default number of bins double maximum = 0.0; double minimum = 0.0; if (m_UseBinSizeBasedOnVOIRegion) { maximum = statisticsFilter->GetMaximum(); minimum = statisticsFilter->GetMinimum(); if (m_UseDefaultBinSize) { m_HistogramBinSize = std::ceil( static_cast((statisticsFilter->GetMaximum() - statisticsFilter->GetMinimum() + 1)/numberOfBins) ); } else { numberOfBins = calcNumberOfBins(statisticsFilter->GetMinimum(), statisticsFilter->GetMaximum()); } } else { double sig = 0.0; int counter = 0; //Find the min and max values for the Roi to set the range for the histogram GetMinAndMaxValue( minimum, maximum, counter, sig, image, maskImage); - numberOfBins = maximum - minimum; - if(maximum - minimum <= 10) - { - numberOfBins = 100; - } +// numberOfBins = maximum - minimum; +// if(maximum - minimum <= 10) +// { +// numberOfBins = 100; +// } } typename LabelStatisticsFilterType::Pointer labelStatisticsFilter = LabelStatisticsFilterType::New(); labelStatisticsFilter->SetInput( adaptedImage ); labelStatisticsFilter->SetLabelInput( adaptedMaskImage ); labelStatisticsFilter->SetCoordinateTolerance( 0.001 ); labelStatisticsFilter->SetDirectionTolerance( 0.001 ); labelStatisticsFilter->UseHistogramsOn(); labelStatisticsFilter->SetHistogramParameters( numberOfBins, floor(minimum), ceil(maximum) ); //statisticsFilter->GetMinimum() statisticsFilter->GetMaximum() // Add progress listening typedef itk::SimpleMemberCommand< ImageStatisticsCalculator > ITKCommandType; ITKCommandType::Pointer progressListener; progressListener = ITKCommandType::New(); progressListener->SetCallbackFunction( this, &ImageStatisticsCalculator::MaskedStatisticsProgressUpdate ); unsigned long observerTag = labelStatisticsFilter->AddObserver( itk::ProgressEvent(), progressListener ); // Execute filter this->InvokeEvent( itk::StartEvent() ); // Make sure that only the mask region is considered (otherwise, if the mask region is smaller // than the image region, the Update() would result in an exception). labelStatisticsFilter->GetOutput()->SetRequestedRegion( adaptedMaskImage->GetLargestPossibleRegion() ); // Execute the filter try { labelStatisticsFilter->Update(); } catch( const itk::ExceptionObject& e) { mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } catch( const std::exception& e ) { //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } this->InvokeEvent( itk::EndEvent() ); if( observerTag ) labelStatisticsFilter->RemoveObserver( observerTag ); // Find all relevant labels of mask (other than 0) std::list< int > relevantLabels = labelStatisticsFilter->GetRelevantLabels(); unsigned int i; if ( labelStatisticsFilter->GetMaskingNonEmpty() ) { std::list< int >::iterator it; for ( it = relevantLabels.begin(), i = 0; it != relevantLabels.end(); ++it, ++i ) { Statistics statistics; // restore previous code labelStatisticsFilter->GetHistogram(*it) ; histogramContainer->push_back( HistogramType::ConstPointer( labelStatisticsFilter->GetHistogram( (*it) ) ) ); statistics.SetLabel (*it); statistics.SetN(labelStatisticsFilter->GetCount( *it )); statistics.SetMin(labelStatisticsFilter->GetMinimum( *it )); statistics.SetMax(labelStatisticsFilter->GetMaximum( *it )); statistics.SetMean(labelStatisticsFilter->GetMean( *it )); statistics.SetMedian(labelStatisticsFilter->GetMedian( *it)); statistics.SetMedian(labelStatisticsFilter->GetMedian( *it )); statistics.SetVariance(labelStatisticsFilter->GetVariance( *it )); statistics.SetSigma(labelStatisticsFilter->GetSigma( *it )); statistics.SetSkewness(labelStatisticsFilter->GetSkewness( *it )); statistics.SetKurtosis(labelStatisticsFilter->GetKurtosis( *it )); statistics.SetUniformity( labelStatisticsFilter->GetUniformity( *it )); statistics.SetEntropy( labelStatisticsFilter->GetEntropy( *it )); statistics.SetUPP( labelStatisticsFilter->GetUPP( *it)); statistics.SetMPP( labelStatisticsFilter->GetMPP( *it)); statistics.SetRMS(sqrt( statistics.GetMean() * statistics.GetMean() + statistics.GetSigma() * statistics.GetSigma() )); // restrict image to mask area for min/max index calculation typedef itk::MaskImageFilter< ImageType, MaskImageType, ImageType > MaskImageFilterType; typename MaskImageFilterType::Pointer masker = MaskImageFilterType::New(); bool isMinAndMaxSameValue = (statistics.GetMin() == statistics.GetMax()); // bug 17962: following is a workaround for the case when min and max are the same, we can probably find a nicer way here double outsideValue = (isMinAndMaxSameValue ? (statistics.GetMax()/2) : (statistics.GetMin()+statistics.GetMax())/2); masker->SetOutsideValue( outsideValue ); masker->SetInput1(adaptedImage); masker->SetInput2(adaptedMaskImage); masker->SetCoordinateTolerance( 0.001 ); masker->SetDirectionTolerance( 0.001 ); masker->Update(); // get index of minimum and maximum typedef itk::MinimumMaximumImageCalculator< ImageType > MinMaxFilterType; typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); minMaxFilter->SetImage( masker->GetOutput() ); unsigned long observerTag2 = minMaxFilter->AddObserver( itk::ProgressEvent(), progressListener ); minMaxFilter->Compute(); minMaxFilter->RemoveObserver( observerTag2 ); this->InvokeEvent( itk::EndEvent() ); typename MinMaxFilterType::IndexType tempMaxIndex = minMaxFilter->GetIndexOfMaximum(); // bug 17962: following is a workaround for the case when min and max are the same, we can probably find a nicer way here typename MinMaxFilterType::IndexType tempMinIndex = (isMinAndMaxSameValue ? minMaxFilter->GetIndexOfMaximum() : minMaxFilter->GetIndexOfMinimum()); // FIX BUG 14644 //If a PlanarFigure is used for segmentation the //adaptedImage is a single slice (2D). Adding the // 3. dimension. - // FIX Bug 19625 pt. 1 - // m_Image will yield 4 coordinates if it has 4 Dimensions, the min max value is however only searched for in one timeSliceImage (3D) - // the 3D min/max coordinates are then set and the 4th coordinate is blank, causing random numbers to appear in the GUI - // (Fix = replace m_Image with VImageDimension (Dimension of image)) - vnl_vector maxIndex; vnl_vector minIndex; - unsigned int imageDimension = m_Image->GetDimension(); - unsigned int maxDimensionsToDisplay = 3; // we do not want to display the time step as part of the coordinates - maxIndex.set_size(std::min(imageDimension, maxDimensionsToDisplay)); - minIndex.set_size(std::min(imageDimension, maxDimensionsToDisplay)); + maxIndex.set_size(m_Image->GetDimension()); + minIndex.set_size(m_Image->GetDimension()); - if (m_MaskingMode == MASKING_MODE_PLANARFIGURE && imageDimension == 3) + if (m_MaskingMode == MASKING_MODE_PLANARFIGURE && m_Image->GetDimension()==3) { maxIndex[m_PlanarFigureCoordinate0] = tempMaxIndex[0]; maxIndex[m_PlanarFigureCoordinate1] = tempMaxIndex[1]; maxIndex[m_PlanarFigureAxis] = m_PlanarFigureSlice; minIndex[m_PlanarFigureCoordinate0] = tempMinIndex[0] ; minIndex[m_PlanarFigureCoordinate1] = tempMinIndex[1]; minIndex[m_PlanarFigureAxis] = m_PlanarFigureSlice; } else { for (unsigned int i = 0; ipush_back( statistics ); } } else { histogramContainer->push_back( HistogramType::ConstPointer( m_EmptyHistogram ) ); statisticsContainer->push_back( Statistics() ); } } template ImageStatisticsCalculator::ImageExtrema ImageStatisticsCalculator::CalculateExtremaWorld( const itk::Image *inputImage, itk::Image *maskImage, double neccessaryDistanceToImageBorderInMM, unsigned int label) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::Image< unsigned short, VImageDimension > MaskImageType; typedef itk::ImageRegionConstIteratorWithIndex MaskImageIteratorType; typedef itk::ImageRegionConstIteratorWithIndex InputImageIndexIteratorType; typename ImageType::SpacingType spacing = inputImage->GetSpacing(); ImageExtrema minMax; minMax.Defined = false; minMax.MaxIndex.set_size(VImageDimension); minMax.MaxIndex.set_size(VImageDimension); typename ImageType::RegionType allowedExtremaRegion = inputImage->GetLargestPossibleRegion(); bool keepDistanceToImageBorders( neccessaryDistanceToImageBorderInMM > 0 ); if (keepDistanceToImageBorders) { long distanceInPixels[VImageDimension]; for(unsigned short dimension = 0; dimension < VImageDimension; ++dimension) { // To confirm that the whole hotspot is inside the image we have to keep a specific distance to the image-borders, which is as long as // the radius. To get the amount of indices we divide the radius by spacing and add 0.5 because voxels are center based: // For example with a radius of 2.2 and a spacing of 1 two indices are enough because 2.2 / 1 + 0.5 = 2.7 => 2. // But with a radius of 2.7 we need 3 indices because 2.7 / 1 + 0.5 = 3.2 => 3 distanceInPixels[dimension] = int( neccessaryDistanceToImageBorderInMM / spacing[dimension] + 0.5); } allowedExtremaRegion.ShrinkByRadius(distanceInPixels); } InputImageIndexIteratorType imageIndexIt(inputImage, allowedExtremaRegion); float maxValue = itk::NumericTraits::min(); float minValue = itk::NumericTraits::max(); typename ImageType::IndexType maxIndex; typename ImageType::IndexType minIndex; for(unsigned short i = 0; i < VImageDimension; ++i) { maxIndex[i] = 0; minIndex[i] = 0; } if (maskImage != nullptr) { MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); typename ImageType::IndexType imageIndex; typename ImageType::PointType worldPosition; typename ImageType::IndexType maskIndex; for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { imageIndex = maskIndex = maskIt.GetIndex(); if(maskIt.Get() == label) { if( allowedExtremaRegion.IsInside(imageIndex) ) { imageIndexIt.SetIndex( imageIndex ); double value = imageIndexIt.Get(); minMax.Defined = true; //Calculate minimum, maximum and corresponding index-values if( value > maxValue ) { maxIndex = imageIndexIt.GetIndex(); maxValue = value; } if(value < minValue ) { minIndex = imageIndexIt.GetIndex(); minValue = value; } } } } } else { for(imageIndexIt.GoToBegin(); !imageIndexIt.IsAtEnd(); ++imageIndexIt) { double value = imageIndexIt.Get(); minMax.Defined = true; //Calculate minimum, maximum and corresponding index-values if( value > maxValue ) { maxIndex = imageIndexIt.GetIndex(); maxValue = value; } if(value < minValue ) { minIndex = imageIndexIt.GetIndex(); minValue = value; } } } minMax.MaxIndex.set_size(VImageDimension); minMax.MinIndex.set_size(VImageDimension); for(unsigned int i = 0; i < minMax.MaxIndex.size(); ++i) { minMax.MaxIndex[i] = maxIndex[i]; } for(unsigned int i = 0; i < minMax.MinIndex.size(); ++i) { minMax.MinIndex[i] = minIndex[i]; } minMax.Max = maxValue; minMax.Min = minValue; return minMax; } template itk::Size ImageStatisticsCalculator ::CalculateConvolutionKernelSize(double spacing[VImageDimension], double radiusInMM) { typedef itk::Image< float, VImageDimension > KernelImageType; typedef typename KernelImageType::SizeType SizeType; SizeType maskSize; for(unsigned int i = 0; i < VImageDimension; ++i) { maskSize[i] = static_cast( 2 * radiusInMM / spacing[i]); // We always want an uneven size to have a clear center point in the convolution mask if(maskSize[i] % 2 == 0 ) { ++maskSize[i]; } } return maskSize; } template itk::SmartPointer< itk::Image > ImageStatisticsCalculator ::GenerateHotspotSearchConvolutionKernel(double mmPerPixel[VImageDimension], double radiusInMM) { std::stringstream ss; for (unsigned int i = 0; i < VImageDimension; ++i) { ss << mmPerPixel[i]; if (i < VImageDimension -1) ss << ","; } MITK_DEBUG << "Update convolution kernel for spacing (" << ss.str() << ") and radius " << radiusInMM << "mm"; double radiusInMMSquared = radiusInMM * radiusInMM; typedef itk::Image< float, VImageDimension > KernelImageType; typename KernelImageType::Pointer convolutionKernel = KernelImageType::New(); // Calculate size and allocate mask image typedef typename KernelImageType::SizeType SizeType; SizeType maskSize = this->CalculateConvolutionKernelSize(mmPerPixel, radiusInMM); Point3D convolutionMaskCenterIndex; convolutionMaskCenterIndex.Fill(0.0); for(unsigned int i = 0; i < VImageDimension; ++i) { convolutionMaskCenterIndex[i] = 0.5 * (double)(maskSize[i]-1); } typedef typename KernelImageType::IndexType IndexType; IndexType maskIndex; maskIndex.Fill(0); typedef typename KernelImageType::RegionType RegionType; RegionType maskRegion; maskRegion.SetSize(maskSize); maskRegion.SetIndex(maskIndex); convolutionKernel->SetRegions(maskRegion); convolutionKernel->SetSpacing(mmPerPixel); convolutionKernel->Allocate(); // Fill mask image values by subsampling the image grid typedef itk::ImageRegionIteratorWithIndex MaskIteratorType; MaskIteratorType maskIt(convolutionKernel,maskRegion); int numberOfSubVoxelsPerDimension = 2; // per dimension! int numberOfSubVoxels = ::pow( static_cast(numberOfSubVoxelsPerDimension), static_cast(VImageDimension) ); double subVoxelSizeInPixels = 1.0 / (double)numberOfSubVoxelsPerDimension; double valueOfOneSubVoxel = 1.0 / (double)numberOfSubVoxels; double maskValue = 0.0; Point3D subVoxelIndexPosition; double distanceSquared = 0.0; typedef itk::ContinuousIndex ContinuousIndexType; for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { ContinuousIndexType indexPoint(maskIt.GetIndex()); Point3D voxelPosition; for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) { voxelPosition[dimension] = indexPoint[dimension]; } maskValue = 0.0; Vector3D subVoxelOffset; subVoxelOffset.Fill(0.0); // iterate sub-voxels by iterating all possible offsets for (subVoxelOffset[0] = -0.5 + subVoxelSizeInPixels / 2.0; subVoxelOffset[0] < +0.5; subVoxelOffset[0] += subVoxelSizeInPixels) { for (subVoxelOffset[1] = -0.5 + subVoxelSizeInPixels / 2.0; subVoxelOffset[1] < +0.5; subVoxelOffset[1] += subVoxelSizeInPixels) { for (subVoxelOffset[2] = -0.5 + subVoxelSizeInPixels / 2.0; subVoxelOffset[2] < +0.5; subVoxelOffset[2] += subVoxelSizeInPixels) { subVoxelIndexPosition = voxelPosition + subVoxelOffset; // this COULD be integrated into the for-loops if neccessary (add voxelPosition to initializer and end condition) distanceSquared = (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] * (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] + (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] * (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] + (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2] * (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2]; if (distanceSquared <= radiusInMMSquared) { maskValue += valueOfOneSubVoxel; } } } } maskIt.Set( maskValue ); } return convolutionKernel; } template itk::SmartPointer > ImageStatisticsCalculator::GenerateConvolutionImage( const itk::Image* inputImage ) { double mmPerPixel[VImageDimension]; for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) { mmPerPixel[dimension] = inputImage->GetSpacing()[dimension]; } // update convolution kernel typedef itk::Image< float, VImageDimension > KernelImageType; typename KernelImageType::Pointer convolutionKernel = this->GenerateHotspotSearchConvolutionKernel(mmPerPixel, m_HotspotRadiusInMM); // update convolution image typedef itk::Image< TPixel, VImageDimension > InputImageType; typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; typedef itk::FFTConvolutionImageFilter ConvolutionFilterType; typename ConvolutionFilterType::Pointer convolutionFilter = ConvolutionFilterType::New(); typedef itk::ConstantBoundaryCondition BoundaryConditionType; BoundaryConditionType boundaryCondition; boundaryCondition.SetConstant(0.0); if (GetHotspotMustBeCompletlyInsideImage()) { // overwrite default boundary condition convolutionFilter->SetBoundaryCondition(&boundaryCondition); } convolutionFilter->SetInput(inputImage); convolutionFilter->SetKernelImage(convolutionKernel); convolutionFilter->SetNormalize(true); MITK_DEBUG << "Update Convolution image for hotspot search"; convolutionFilter->UpdateLargestPossibleRegion(); typename ConvolutionImageType::Pointer convolutionImage = convolutionFilter->GetOutput(); convolutionImage->SetSpacing( inputImage->GetSpacing() ); // only workaround because convolution filter seems to ignore spacing of input image m_HotspotRadiusInMMChanged = false; return convolutionImage; } template < typename TPixel, unsigned int VImageDimension> void ImageStatisticsCalculator ::FillHotspotMaskPixels( itk::Image* maskImage, itk::Point sphereCenter, double sphereRadiusInMM) { typedef itk::Image< TPixel, VImageDimension > MaskImageType; typedef itk::ImageRegionIteratorWithIndex MaskImageIteratorType; MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); typename MaskImageType::IndexType maskIndex; typename MaskImageType::PointType worldPosition; for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { maskIndex = maskIt.GetIndex(); maskImage->TransformIndexToPhysicalPoint(maskIndex, worldPosition); maskIt.Set( worldPosition.EuclideanDistanceTo(sphereCenter) <= sphereRadiusInMM ? 1 : 0 ); } } template < typename TPixel, unsigned int VImageDimension> ImageStatisticsCalculator::Statistics ImageStatisticsCalculator::CalculateHotspotStatistics( const itk::Image* inputImage, itk::Image* maskImage, double radiusInMM, bool& isHotspotDefined, unsigned int label) { // get convolution image (updated in GenerateConvolutionImage()) typedef itk::Image< TPixel, VImageDimension > InputImageType; typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; typedef itk::Image< float, VImageDimension > KernelImageType; typedef itk::Image< unsigned short, VImageDimension > MaskImageType; //typename ConvolutionImageType::Pointer convolutionImage = dynamic_cast(this->GenerateConvolutionImage(inputImage)); typename ConvolutionImageType::Pointer convolutionImage = this->GenerateConvolutionImage(inputImage); if (convolutionImage.IsNull()) { MITK_ERROR << "Empty convolution image in CalculateHotspotStatistics(). We should never reach this state (logic error)."; throw std::logic_error("Empty convolution image in CalculateHotspotStatistics()"); } // find maximum in convolution image, given the current mask double requiredDistanceToBorder = m_HotspotMustBeCompletelyInsideImage ? m_HotspotRadiusInMM : -1.0; ImageExtrema convolutionImageInformation = CalculateExtremaWorld(convolutionImage.GetPointer(), maskImage, requiredDistanceToBorder, label); isHotspotDefined = convolutionImageInformation.Defined; if (!isHotspotDefined) { m_EmptyStatistics.Reset(VImageDimension); MITK_ERROR << "No origin of hotspot-sphere was calculated! Returning empty statistics"; return m_EmptyStatistics; } else { // create a binary mask around the "hotspot" region, fill the shape of a sphere around our hotspot center typedef itk::ImageDuplicator< InputImageType > DuplicatorType; typename DuplicatorType::Pointer copyMachine = DuplicatorType::New(); copyMachine->SetInputImage(inputImage); copyMachine->Update(); typedef itk::CastImageFilter< InputImageType, MaskImageType > CastFilterType; typename CastFilterType::Pointer caster = CastFilterType::New(); caster->SetInput( copyMachine->GetOutput() ); caster->Update(); typename MaskImageType::Pointer hotspotMaskITK = caster->GetOutput(); typedef typename InputImageType::IndexType IndexType; IndexType maskCenterIndex; for (unsigned int d =0; d< VImageDimension;++d) maskCenterIndex[d]=convolutionImageInformation.MaxIndex[d]; typename ConvolutionImageType::PointType maskCenter; inputImage->TransformIndexToPhysicalPoint(maskCenterIndex,maskCenter); this->FillHotspotMaskPixels(hotspotMaskITK.GetPointer(), maskCenter, radiusInMM); // calculate statistics within the binary mask typedef itk::ExtendedLabelStatisticsImageFilter< InputImageType, MaskImageType> LabelStatisticsFilterType; typename LabelStatisticsFilterType::Pointer labelStatisticsFilter; labelStatisticsFilter = LabelStatisticsFilterType::New(); labelStatisticsFilter->SetInput( inputImage ); labelStatisticsFilter->SetLabelInput( hotspotMaskITK ); labelStatisticsFilter->SetCoordinateTolerance( 0.001 ); labelStatisticsFilter->SetDirectionTolerance( 0.001 ); labelStatisticsFilter->Update(); Statistics hotspotStatistics; hotspotStatistics.SetHotspotIndex(convolutionImageInformation.MaxIndex); hotspotStatistics.SetMean(convolutionImageInformation.Max); if ( labelStatisticsFilter->HasLabel( 1 ) ) { hotspotStatistics.SetLabel (1); hotspotStatistics.SetN(labelStatisticsFilter->GetCount(1)); hotspotStatistics.SetMin(labelStatisticsFilter->GetMinimum(1)); hotspotStatistics.SetMax(labelStatisticsFilter->GetMaximum(1)); hotspotStatistics.SetMedian(labelStatisticsFilter->GetMedian(1)); hotspotStatistics.SetVariance(labelStatisticsFilter->GetVariance(1)); hotspotStatistics.SetSigma(labelStatisticsFilter->GetSigma(1)); hotspotStatistics.SetRMS(sqrt( hotspotStatistics.GetMean() * hotspotStatistics.GetMean() + hotspotStatistics.GetSigma() * hotspotStatistics.GetSigma() )); MITK_DEBUG << "Statistics for inside hotspot: Mean " << hotspotStatistics.GetMean() << ", SD " << hotspotStatistics.GetSigma() << ", Max " << hotspotStatistics.GetMax() << ", Min " << hotspotStatistics.GetMin(); } else { MITK_ERROR << "Uh oh! Unable to calculate statistics for hotspot region..."; return m_EmptyStatistics; } return hotspotStatistics; } } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateMaskFromPlanarFigure( const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::CastImageFilter< ImageType, MaskImage2DType > CastFilterType; // Generate mask image as new image with same header as input image and // initialize with 1. typename CastFilterType::Pointer castFilter = CastFilterType::New(); castFilter->SetInput( image ); castFilter->Update(); castFilter->GetOutput()->FillBuffer( 1 ); // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. // These points are used by the vtkLassoStencilSource to create // a vtkImageStencil. const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); const mitk::BaseGeometry *imageGeometry3D = m_Image->GetGeometry( 0 ); // If there is a second poly line in a closed planar figure, treat it as a hole. PlanarFigure::PolyLineType planarFigureHolePolyline; if (m_PlanarFigure->GetPolyLinesSize() == 2) planarFigureHolePolyline = m_PlanarFigure->GetPolyLine(1); // Determine x- and y-dimensions depending on principal axis + // TODO use plane geometry normal to determine that automatically, then check whether the PF is aligned with one of the three principal axis int i0, i1; switch ( axis ) { case 0: i0 = 1; i1 = 2; break; case 1: i0 = 0; i1 = 2; break; case 2: default: i0 = 0; i1 = 1; break; } m_PlanarFigureCoordinate0= i0; m_PlanarFigureCoordinate1= i1; // store the polyline contour as vtkPoints object bool outOfBounds = false; vtkSmartPointer points = vtkSmartPointer::New(); typename PlanarFigure::PolyLineType::const_iterator it; for ( it = planarFigurePolyline.begin(); it != planarFigurePolyline.end(); ++it ) { Point3D point3D; // Convert 2D point back to the local index coordinates of the selected // image + // Fabian: From PlaneGeometry documentation: + // Converts a 2D point given in mm (pt2d_mm) relative to the upper-left corner of the geometry into the corresponding world-coordinate (a 3D point in mm, pt3d_mm). + // To convert a 2D point given in units (e.g., pixels in case of an image) into a 2D point given in mm (as required by this method), use IndexToWorld. planarFigurePlaneGeometry->Map( *it, point3D ); // Polygons (partially) outside of the image bounds can not be processed // further due to a bug in vtkPolyDataToImageStencil if ( !imageGeometry3D->IsInside( point3D ) ) { outOfBounds = true; } + // Fabian: Why convert to index coordinates? imageGeometry3D->WorldToIndex( point3D, point3D ); points->InsertNextPoint( point3D[i0], point3D[i1], 0 ); } vtkSmartPointer holePoints = nullptr; if (!planarFigureHolePolyline.empty()) { holePoints = vtkSmartPointer::New(); Point3D point3D; PlanarFigure::PolyLineType::const_iterator end = planarFigureHolePolyline.end(); for (it = planarFigureHolePolyline.begin(); it != end; ++it) { + // Fabian: same as above planarFigurePlaneGeometry->Map(*it, point3D); imageGeometry3D->WorldToIndex(point3D, point3D); holePoints->InsertNextPoint(point3D[i0], point3D[i1], 0); } } // mark a malformed 2D planar figure ( i.e. area = 0 ) as out of bounds // this can happen when all control points of a rectangle lie on the same line = two of the three extents are zero double bounds[6] = {0, 0, 0, 0, 0, 0}; points->GetBounds( bounds ); bool extent_x = (fabs(bounds[0] - bounds[1])) < mitk::eps; bool extent_y = (fabs(bounds[2] - bounds[3])) < mitk::eps; bool extent_z = (fabs(bounds[4] - bounds[5])) < mitk::eps; // throw an exception if a closed planar figure is deformed, i.e. has only one non-zero extent if ( m_PlanarFigure->IsClosed() && ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) { mitkThrow() << "Figure has a zero area and cannot be used for masking."; } if ( outOfBounds ) { throw std::runtime_error( "Figure at least partially outside of image bounds!" ); } // create a vtkLassoStencilSource and set the points of the Polygon vtkSmartPointer lassoStencil = vtkSmartPointer::New(); lassoStencil->SetShapeToPolygon(); lassoStencil->SetPoints( points ); vtkSmartPointer holeLassoStencil = nullptr; if (holePoints.GetPointer() != nullptr) { holeLassoStencil = vtkSmartPointer::New(); holeLassoStencil->SetShapeToPolygon(); holeLassoStencil->SetPoints(holePoints); } // Export from ITK to VTK (to use a VTK filter) typedef itk::VTKImageImport< MaskImage2DType > ImageImportType; typedef itk::VTKImageExport< MaskImage2DType > ImageExportType; typename ImageExportType::Pointer itkExporter = ImageExportType::New(); itkExporter->SetInput( castFilter->GetOutput() ); vtkSmartPointer vtkImporter = vtkSmartPointer::New(); this->ConnectPipelines( itkExporter, vtkImporter ); // Apply the generated image stencil to the input image vtkSmartPointer imageStencilFilter = vtkSmartPointer::New(); imageStencilFilter->SetInputConnection( vtkImporter->GetOutputPort() ); imageStencilFilter->SetStencilConnection(lassoStencil->GetOutputPort()); imageStencilFilter->ReverseStencilOff(); imageStencilFilter->SetBackgroundValue( 0 ); imageStencilFilter->Update(); vtkSmartPointer holeStencilFilter = nullptr; if (holeLassoStencil.GetPointer() != nullptr) { holeStencilFilter = vtkSmartPointer::New(); holeStencilFilter->SetInputConnection(imageStencilFilter->GetOutputPort()); holeStencilFilter->SetStencilConnection(holeLassoStencil->GetOutputPort()); holeStencilFilter->ReverseStencilOn(); holeStencilFilter->SetBackgroundValue(0); holeStencilFilter->Update(); } // Export from VTK back to ITK vtkSmartPointer vtkExporter = vtkSmartPointer::New(); vtkExporter->SetInputConnection( holeStencilFilter.GetPointer() == nullptr ? imageStencilFilter->GetOutputPort() : holeStencilFilter->GetOutputPort()); vtkExporter->Update(); typename ImageImportType::Pointer itkImporter = ImageImportType::New(); this->ConnectPipelines( vtkExporter, itkImporter ); itkImporter->Update(); typedef itk::ImageDuplicator< ImageImportType::OutputImageType > DuplicatorType; DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage( itkImporter->GetOutput() ); duplicator->Update(); // Store mask m_InternalImageMask2D = duplicator->GetOutput(); } void ImageStatisticsCalculator::UnmaskedStatisticsProgressUpdate() { // Need to throw away every second progress event to reach a final count of // 100 since two consecutive filters are used in this case static int updateCounter = 0; if ( updateCounter++ % 2 == 0 ) { this->InvokeEvent( itk::ProgressEvent() ); } } void ImageStatisticsCalculator::MaskedStatisticsProgressUpdate() { this->InvokeEvent( itk::ProgressEvent() ); } } diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.h b/Modules/ImageStatistics/old/mitkImageStatisticsCalculator.h similarity index 100% copy from Modules/ImageStatistics/mitkImageStatisticsCalculator.h copy to Modules/ImageStatistics/old/mitkImageStatisticsCalculator.h diff --git a/Modules/MaskGenerator/CMakeLists.txt b/Modules/MaskGenerator/CMakeLists.txt new file mode 100644 index 0000000000..d073a4481d --- /dev/null +++ b/Modules/MaskGenerator/CMakeLists.txt @@ -0,0 +1,13 @@ +MITK_CREATE_MODULE( + DEPENDS MitkImageStatistics + #PACKAGE_DEPENDS + # PUBLIC ITK|ITKIOXML + # PRIVATE ITK|ITKVTK+ITKConvolution + # WARNINGS_AS_ERRORS +) + +if(BUILD_TESTING) + + # add_subdirectory(Testing) + +endif(BUILD_TESTING) diff --git a/Modules/MaskGenerator/files.cmake b/Modules/MaskGenerator/files.cmake new file mode 100644 index 0000000000..36e7a3e616 --- /dev/null +++ b/Modules/MaskGenerator/files.cmake @@ -0,0 +1,7 @@ +set(CPP_FILES + +) + +set(H_FILES + +) diff --git a/Modules/MaskGenerator/mitkDeleteMe.cpp b/Modules/MaskGenerator/mitkDeleteMe.cpp new file mode 100644 index 0000000000..8f5253af72 --- /dev/null +++ b/Modules/MaskGenerator/mitkDeleteMe.cpp @@ -0,0 +1,6 @@ +#include + +void deleteMe::poebelMalHerum() +{ + std::cout << "was fuer ein dummes Modul" << std::endl; +} \ No newline at end of file diff --git a/Modules/MaskGenerator/mitkDeleteMe.h b/Modules/MaskGenerator/mitkDeleteMe.h new file mode 100644 index 0000000000..e2b9473478 --- /dev/null +++ b/Modules/MaskGenerator/mitkDeleteMe.h @@ -0,0 +1,7 @@ +#include + +class deleteMe +{ +public: +void poebelMalHerum(); +}; diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index 282fbb5391..fe7e334497 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,81 +1,81 @@ # The entries in the mitk_modules list must be # ordered according to their dependencies. set(mitk_modules Core CommandLine AppUtil DCMTesting RDF LegacyIO DataTypesExt Overlays LegacyGL AlgorithmsExt MapperExt DICOMReader DICOMReaderServices DICOMTesting SceneSerializationBase PlanarFigure ImageDenoising ImageExtraction - ImageStatistics LegacyAdaptors SceneSerialization Gizmo GraphAlgorithms Multilabel + ImageStatistics ContourModel SurfaceInterpolation Segmentation PlanarFigureSegmentation OpenViewCore QtWidgets QtWidgetsExt - QtWidgetsWeb + C3js QmlItems SegmentationUI DiffusionImaging GPGPU OpenIGTLink IGTBase IGT CameraCalibration RigidRegistration RigidRegistrationUI DeformableRegistration DeformableRegistrationUI OpenCL OpenCVVideoSupport QtOverlays ToFHardware ToFProcessing ToFUI US USUI DicomUI Simulation Remeshing Python QtPython Persistence OpenIGTLinkUI IGTUI VtkShaders DicomRT RTUI IOExt XNAT TubeGraph BiophotonicsHardware Classification TumorInvasionAnalysis MatchPointRegistration MatchPointRegistrationUI BoundingShape ) if(MITK_ENABLE_PIC_READER) list(APPEND mitk_modules IpPicSupportIO) endif() diff --git a/Modules/QtWidgetsExt/files.cmake b/Modules/QtWidgetsExt/files.cmake index 2c1d9dbcff..e8bd9dd1f6 100644 --- a/Modules/QtWidgetsExt/files.cmake +++ b/Modules/QtWidgetsExt/files.cmake @@ -1,108 +1,106 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES qclickablelabel.cpp QmitkAboutDialog.cpp QmitkBasePropertyView.cpp QmitkBoolPropertyWidget.cpp QmitkBoundingObjectWidget.cpp QmitkCallbackFromGUIThread.cpp QmitkColorPropertyEditor.cpp QmitkColorPropertyView.cpp QmitkColorTransferFunctionCanvas.cpp QmitkCrossWidget.cpp QmitkEditPointDialog.cpp QmitkEnumerationPropertyWidget.cpp QmitkFFmpegWriter.cpp QmitkFileChooser.cpp QmitkGnuplotWidget.cpp QmitkHistogram.cpp - QmitkHistogramWidget.cpp QmitkHotkeyLineEdit.cpp QmitkModulesDialog.cpp QmitkModuleTableModel.cpp QmitkNumberPropertyEditor.cpp QmitkNumberPropertySlider.cpp QmitkNumberPropertyView.cpp QmitkPiecewiseFunctionCanvas.cpp QmitkPlotDialog.cpp QmitkPlotWidget.cpp QmitkPointListModel.cpp QmitkPointListView.cpp QmitkPointListViewWidget.cpp QmitkPointListWidget.cpp QmitkPrimitiveMovieNavigatorWidget.cpp QmitkPropertyViewFactory.cpp QmitkSelectableGLWidget.cpp QmitkSliceWidget.cpp QmitkSliderNavigatorWidget.cpp QmitkStandardViews.cpp QmitkStepperAdapter.cpp QmitkStringPropertyEditor.cpp QmitkStringPropertyOnDemandEdit.cpp QmitkStringPropertyView.cpp QmitkTransferFunctionCanvas.cpp QmitkTransferFunctionGeneratorWidget.cpp QmitkTransferFunctionWidget.cpp QmitkUGCombinedRepresentationPropertyWidget.cpp QmitkVideoBackground.cpp QtWidgetsExtRegisterClasses.cpp ) set(MOC_H_FILES include/qclickablelabel.h include/QmitkAboutDialog.h include/QmitkBasePropertyView.h include/QmitkBoolPropertyWidget.h include/QmitkBoundingObjectWidget.h include/QmitkCallbackFromGUIThread.h include/QmitkColorPropertyEditor.h include/QmitkColorPropertyView.h include/QmitkColorTransferFunctionCanvas.h include/QmitkCrossWidget.h include/QmitkEditPointDialog.h include/QmitkEnumerationPropertyWidget.h include/QmitkFFmpegWriter.h include/QmitkFileChooser.h include/QmitkGnuplotWidget.h - include/QmitkHistogramWidget.h include/QmitkHotkeyLineEdit.h include/QmitkNumberPropertyEditor.h include/QmitkNumberPropertySlider.h include/QmitkNumberPropertyView.h include/QmitkPiecewiseFunctionCanvas.h include/QmitkPlotWidget.h include/QmitkPointListModel.h include/QmitkPointListView.h include/QmitkPointListViewWidget.h include/QmitkPointListWidget.h include/QmitkPrimitiveMovieNavigatorWidget.h include/QmitkSelectableGLWidget.h include/QmitkSliceWidget.h include/QmitkSliderNavigatorWidget.h include/QmitkStandardViews.h include/QmitkStepperAdapter.h include/QmitkStringPropertyEditor.h include/QmitkStringPropertyOnDemandEdit.h include/QmitkStringPropertyView.h include/QmitkTransferFunctionCanvas.h include/QmitkTransferFunctionGeneratorWidget.h include/QmitkTransferFunctionWidget.h include/QmitkUGCombinedRepresentationPropertyWidget.h include/QmitkVideoBackground.h ) set(UI_FILES src/QmitkAboutDialogGUI.ui src/QmitkGnuplotWidget.ui src/QmitkPrimitiveMovieNavigatorWidget.ui src/QmitkSelectableGLWidget.ui src/QmitkSliceWidget.ui src/QmitkSliderNavigator.ui src/QmitkTransferFunctionGeneratorWidget.ui src/QmitkTransferFunctionWidget.ui ) set(QRC_FILES resource/QtWidgetsExt.qrc ) diff --git a/Modules/QtWidgetsExt/include/QmitkHistogramWidget.h b/Modules/QtWidgetsExt/include/QmitkHistogramWidget.h deleted file mode 100644 index 069a6100e6..0000000000 --- a/Modules/QtWidgetsExt/include/QmitkHistogramWidget.h +++ /dev/null @@ -1,83 +0,0 @@ -/*=================================================================== - -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 QMITKHistogramWidget_H_ -#define QMITKHistogramWidget_H_ - -#include "MitkQtWidgetsExtExports.h" - -#include - -#include "mitkImage.h" - -class QTextEdit; - -class QwtPlot; -class QwtPlotMarker; -class QwtPlotPicker; -class QwtPlotZoomer; - -class QmitkHistogram; - -/** - \brief Shows a histogram in a Qwt_Plot and a report (selectable) in a QTextEdit - Makes the data Values from a itk::histogram suitable for a Qwt_Plot. - Offers zoom possibility. - A marker can be set and shows the frequency at his position. - */ -class MITKQTWIDGETSEXT_EXPORT QmitkHistogramWidget : public QDialog -{ - Q_OBJECT - -public: - // overloaded constructor to choose between showing only the histogram or the histogram and the report - QmitkHistogramWidget(QWidget *parent = nullptr, bool showreport = false); - virtual ~QmitkHistogramWidget(); - - typedef mitk::Image::HistogramType HistogramType; - - /** - - gets the data values from either a itk histogram or a mitk image - - copies them into data arrays suitable for qwt - - creates a plot dialog, fill it with the data and shows it - */ - void SetHistogram(HistogramType::ConstPointer histogram); - void SetHistogram(mitk::Image *mitkImage); - - void SetReport(std::string report); - - double GetMarkerPosition(); - -protected slots: - - void OnSelect(const QPointF &pos); - -protected: - // convenience fct. for rounding doubles to integral numbers - double Round(double val); - - void InitializeMarker(); - void InitializeZoomer(); - - QwtPlot *m_Plot; - QTextEdit *m_Textedit; - QwtPlotMarker *m_Marker; - QwtPlotPicker *m_Picker; - QwtPlotZoomer *m_Zoomer; - QmitkHistogram *m_Histogram; -}; - -#endif diff --git a/Modules/QtWidgetsExt/src/QmitkHistogramWidget.cpp b/Modules/QtWidgetsExt/src/QmitkHistogramWidget.cpp deleted file mode 100644 index 5a1e33bdfa..0000000000 --- a/Modules/QtWidgetsExt/src/QmitkHistogramWidget.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/*=================================================================== - -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 "QmitkHistogramWidget.h" - -#include - -#include "mitkImageStatisticsHolder.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -QmitkHistogramWidget::QmitkHistogramWidget(QWidget *parent, bool showreport) - : QDialog(parent), - m_Plot(nullptr), - m_Textedit(nullptr), - m_Marker(nullptr), - m_Picker(nullptr), - m_Zoomer(nullptr), - m_Histogram(nullptr) -{ - QBoxLayout *layout = new QVBoxLayout(this); - - //***histogram*** - - QGroupBox *hgroupbox = new QGroupBox("", this); - - hgroupbox->setMinimumSize(900, 400); - - m_Plot = new QwtPlot(hgroupbox); - m_Plot->setCanvasBackground(QColor(Qt::white)); - m_Plot->setTitle("Histogram"); - QwtText text = m_Plot->titleLabel()->text(); - text.setFont(QFont("Helvetica", 12, QFont::Normal)); - - auto grid = new QwtPlotGrid; - grid->enableXMin(true); - grid->enableYMin(true); - grid->setMajorPen(QPen(Qt::black, 0, Qt::DotLine)); - grid->setMinorPen(QPen(Qt::gray, 0, Qt::DotLine)); - grid->attach(m_Plot); - - layout->addWidget(hgroupbox); - - layout->setSpacing(20); - - if (showreport == true) - { - //***report*** - QGroupBox *rgroupbox = new QGroupBox("", this); - - rgroupbox->setMinimumSize(900, 400); - - QLabel *label = new QLabel("Gray Value Analysis", rgroupbox); - label->setAlignment(Qt::AlignHCenter); - label->setFont(QFont("Helvetica", 14, QFont::Bold)); - - m_Textedit = new QTextEdit(rgroupbox); - m_Textedit->setFont(QFont("Helvetica", 12, QFont::Normal)); - m_Textedit->setReadOnly(true); - - layout->addWidget(rgroupbox); - } - - m_Picker = new QwtPlotPicker( - QwtPlot::xBottom, QwtPlot::yLeft, QwtPlotPicker::NoRubberBand, QwtPicker::AlwaysOn, m_Plot->canvas()); - // the m_PickerMachine pointer is managed by the m_Picker instance - m_Picker->setStateMachine(new QwtPickerClickPointMachine()); - - connect(m_Picker, SIGNAL(selected(const QwtDoublePoint &)), SLOT(OnSelect(const QwtDoublePoint &))); -} - -QmitkHistogramWidget::~QmitkHistogramWidget() -{ -} - -void QmitkHistogramWidget::SetHistogram(HistogramType::ConstPointer itkHistogram) -{ - HistogramType::SizeType size = itkHistogram->GetSize(); - HistogramType::IndexType index; - HistogramType::MeasurementVectorType currentMeasurementVector; - - QVector intervalSeries(size[0]); - - for (unsigned int i = 0; i < size[0]; ++i) - { - index[0] = static_cast(i); - currentMeasurementVector = itkHistogram->GetMeasurementVector(index); - if (currentMeasurementVector[0] != 0.0) - { - intervalSeries[i] = QwtIntervalSample(static_cast(itkHistogram->GetFrequency(index)), - Round(currentMeasurementVector[0] - 1), - Round(currentMeasurementVector[0])); - } - } - - // rebuild the plot - m_Plot->detachItems(); - - m_Histogram = new QmitkHistogram(); - m_Histogram->setColor(Qt::darkCyan); - m_Histogram->setData(QwtIntervalSeriesData(intervalSeries)); - m_Histogram->attach(m_Plot); - - this->InitializeMarker(); - this->InitializeZoomer(); - - m_Plot->replot(); -} - -void QmitkHistogramWidget::SetHistogram(mitk::Image *mitkImage) -{ - this->SetHistogram(mitkImage->GetStatistics()->GetScalarHistogram()); -} - -void QmitkHistogramWidget::SetReport(std::string report) -{ - m_Textedit->setText(report.c_str()); -} - -void QmitkHistogramWidget::InitializeMarker() -{ - m_Marker = new QwtPlotMarker(); - m_Marker->setXValue(0.); - m_Marker->setLineStyle(QwtPlotMarker::VLine); - m_Marker->setLabelAlignment(Qt::AlignHCenter | Qt::AlignRight); - m_Marker->setLinePen(QPen(QColor(200, 150, 0), 3, Qt::SolidLine)); - m_Marker->setSymbol(new QwtSymbol(QwtSymbol::Diamond, QColor(Qt::red), QColor(Qt::red), QSize(10, 10))); - m_Marker->attach(m_Plot); -} - -void QmitkHistogramWidget::InitializeZoomer() -{ - m_Zoomer = new QwtPlotZoomer(m_Plot->xBottom, m_Plot->yLeft, m_Plot->canvas()); - m_Zoomer->setRubberBandPen(QPen(Qt::red, 2, Qt::DotLine)); - m_Zoomer->setTrackerPen(QPen(Qt::red)); -} - -void QmitkHistogramWidget::OnSelect(const QPointF &pos) -{ - m_Marker->setXValue(this->Round(pos.x())); - QString str = QString("%1").arg((int)(this->Round(pos.x())), 0, 10); - QwtText text(str); - text.setBackgroundBrush(QColor(200, 150, 0)); - text.setFont(QFont("Helvetica", 14, QFont::Bold)); - m_Marker->setLabel(text); - m_Plot->replot(); -} - -double QmitkHistogramWidget::GetMarkerPosition() -{ - return m_Marker->xValue(); -} - -double QmitkHistogramWidget::Round(double val) -{ - double ival = (double)(int)val; - if ((val - ival) > 0.5) - return ival + 1; - else - return ival; -} diff --git a/Modules/QtWidgetsWeb/CMakeLists.txt b/Modules/QtWidgetsWeb/CMakeLists.txt deleted file mode 100644 index fb1bfc1b91..0000000000 --- a/Modules/QtWidgetsWeb/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -if(MITK_USE_QT_WEBENGINE) - MITK_CREATE_MODULE( - DEPENDS MitkQtWidgetsExt - PACKAGE_DEPENDS - PUBLIC Qt5|WebEngineWidgets - WARNINGS_AS_ERRORS - ) -endif() diff --git a/Modules/QtWidgetsWeb/files.cmake b/Modules/QtWidgetsWeb/files.cmake deleted file mode 100644 index d20b9bf1f6..0000000000 --- a/Modules/QtWidgetsWeb/files.cmake +++ /dev/null @@ -1,11 +0,0 @@ -set(CPP_FILES - QmitkHistogramJSWidget.cpp -) - -set(MOC_H_FILES - include/QmitkHistogramJSWidget.h -) - -set(QRC_FILES - resource/QtWidgetsWeb.qrc -) diff --git a/Modules/QtWidgetsWeb/include/QmitkHistogramJSWidget.h b/Modules/QtWidgetsWeb/include/QmitkHistogramJSWidget.h deleted file mode 100644 index 042a53cfbe..0000000000 --- a/Modules/QtWidgetsWeb/include/QmitkHistogramJSWidget.h +++ /dev/null @@ -1,273 +0,0 @@ -/*=================================================================== - -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 "MitkQtWidgetsWebExports.h" -#include "mitkImage.h" -#include "mitkPlanarFigure.h" -#include -#include -#include -#include -#include -#include - -/** -* \brief Widget which shows a histogram using JavaScript. -* -* This class is a QWebView. It shows the histogram for a selected image -* or segmentation. It also can display an intensity profile for -* path elements, which lais over an image. -*/ -class MITKQTWIDGETSWEB_EXPORT QmitkHistogramJSWidget : public QWebEngineView -{ - Q_OBJECT - - /** - * \brief Measurement property. - * - * This property is used in JavaScript as member of the current object. - * It holds a QList, containing the measurements of the current histogram. - * @see GetMeasurement() - */ - Q_PROPERTY(QList measurement READ GetMeasurement) - - /** - * \brief Frequency property. - * - * This property is used in JavaScript as member of the current object. - * It holds a QList, containing the frequencies of the current histogram. - * @see GetFrequency() - */ - Q_PROPERTY(QList frequency READ GetFrequency) - - /** - * \brief Line graph property. - * - * This property is used in JavaScript as member of the current object. - * It holds a boolean, which sais wether to use a line or not. - * @see GetUseLineGraph() - */ - Q_PROPERTY(bool useLineGraph READ GetUseLineGraph) - - /** - * @brief intensity profile property. - * - * This property is used in JavaScript as member of the current object. - * It holds a boolean, which says whether to use an intensity profile or not. - * @see GetIntensityProfile() - */ - 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; - typedef mitk::PlanarFigure::PolyLineType VertexContainerType; - - explicit QmitkHistogramJSWidget(QWidget *parent = nullptr); - - ~QmitkHistogramJSWidget(); - - /** - * \brief Event which notifies a change of the widget size. - * - * Reimplemented from QWebView::resizeEvent(), - * reloads the webframe - */ - void resizeEvent(QResizeEvent *resizeEvent) override; - - /** - * \brief Calculates the histogram. - * - * This function removes all frequencies of 0 until the first bin and behind the last bin. - * It writes the measurement and frequency, which are given from the HistogramType, into - * m_Measurement and m_Frequency. - * The SignalDataChanged is called, to update the information, which is displayed in the webframe. - */ - void ComputeHistogram(HistogramType *histogram); - - /** - * \brief Calculates the intensityprofile. - * - * If an image and a pathelement are set, this function - * calculates an intensity profile for a pathelement which lies over an image. - * Sets m_IntensityProfile and m_UseLineGraph to true. - * The SignalDataChanged is called, to update the information, which is displayed in the webframe. - */ - void ComputeIntensityProfile(unsigned int timeStep = 0, bool computeStatistics = false); - - /** - * \brief Clears the Histogram. - * - * This function clears the data and calls SignalDataChanged to update - * the displayed information in the webframe. - */ - void ClearHistogram(); - - /** - * \brief Getter for measurement. - * - * @return List of measurements. - */ - QList GetMeasurement(); - - /** - * \brief Getter for frequency. - * - * @return List of frequencies. - */ - QList GetFrequency(); - - /** - * \brief Getter for uselineGraph. - * - * @return True if a linegraph should be used. - */ - bool GetUseLineGraph(); - - /** - * \brief Getter for intensity profile. - * - * @return True if current histogram is an intensityprofile - */ - bool GetIntensityProfile(); - - mitk::ImageStatisticsCalculator::Statistics &GetStatistics() { return m_Statistics; }; - /** - * \brief Setter for reference image. - * - * @param image The corresponding image for an intensity profile. - */ - void SetImage(mitk::Image *image); - - /** - * \brief Setter for planarFigure. - * - * @param planarFigure The pathelement for an intensity profile. - */ - void SetPlanarFigure(const mitk::PlanarFigure *planarFigure); - -private: - /** - * \brief List of frequencies. - * - * A QList which holds the frequencies of the current histogram - * or holds the intensities of current intensity profile. - */ - QList m_Frequency; - - /** - * \brief List of measurements. - * - * A QList which holds the measurements of the current histogram - * or holds the distances of current intensity profile. - */ - QList m_Measurement; - - mitk::ImageStatisticsCalculator::Statistics m_Statistics; - - /** - * \brief Reference image. - * - * Holds the image to calculate an intensity profile. - */ - mitk::Image::Pointer m_Image; - - /** - * \brief Pathelement. - * - * Holds a not closed planar figure to calculate an intensity profile. - */ - mitk::PlanarFigure::ConstPointer m_PlanarFigure; - - bool m_UseLineGraph; - bool m_IntensityProfile; - - /** - * Holds the current histogram - */ - HistogramType::ConstPointer m_Histogram; - - /** - * Path derived either form user-specified path or from PlanarFigure-generated - * path - */ - PathType::ConstPointer m_DerivedPath; - - /** - * Parametric path as generated from PlanarFigure - */ - ParametricPathType::Pointer m_ParametricPath; - - /** - * \brief Clears data. - * - * Clears the QLists m_Measurement and m_Frequency - */ - void ClearData(); - QWebEnginePage *m_Page; - -private slots: - - /** - * \brief Adds an object to JavaScript. - * - * Adds an object of the widget to JavaScript. - * By using this object JavaScript can react to the signals of the widget - * and can access the QProperties as members of the object. - */ - void AddJSObject(); - -public slots: - - /** - * \brief Slot for radiobutton m_barRadioButton. - * - * Sets m_UseLineGraph to false. - * Calls signal GraphChanged to update the graph in the webframe. - */ - void OnBarRadioButtonSelected(); - - /** - * \brief Slot for radiobutton m_lineRadioButton. - * - * Sets m_UseLineGraph to true. - * Calls signal GraphChanged to update the graph in the webframe. - */ - void OnLineRadioButtonSelected(); - -signals: - - /** - * \brief Signal data has changed. - * - * It has to be called when the data of the histogram or intensity profile has changed. - */ - void SignalDataChanged(); - - /** - * \brief Signal graph has changed. - * - * It has to be called when the graph changed from barchart to linegraph. Vice versa. - */ - void SignalGraphChanged(); -}; - -#endif diff --git a/Modules/QtWidgetsWeb/resource/Histogram.css b/Modules/QtWidgetsWeb/resource/Histogram.css deleted file mode 100644 index 88eb703c20..0000000000 --- a/Modules/QtWidgetsWeb/resource/Histogram.css +++ /dev/null @@ -1,35 +0,0 @@ -body { - font: 10px sans-serif; - background-color: rgb(240,240,240) -} - -.bar { - fill: rgb(0,71,185); - shape-rendering: crispEdges; -} - -.line { - fill: none; - stroke: rgb(0,71,185); - stroke-width: 1; -} - -.axis path, .axis line { - fill: none; - stroke: #000; - shape-rendering: crispEdges; -} - -.infobox { - background-color: rgba(255, 255, 255, 0.75); - padding: 10px; - position: absolute; - width: auto; - display: none; -} - -circle { - stroke: rgb(0,71,185); - stroke-width: 1; - fill-opacity: 0; -} diff --git a/Modules/QtWidgetsWeb/resource/Histogram.html b/Modules/QtWidgetsWeb/resource/Histogram.html deleted file mode 100644 index 09eaf6a574..0000000000 --- a/Modules/QtWidgetsWeb/resource/Histogram.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - Histogram - - - - -
-

-

-
- - - diff --git a/Modules/QtWidgetsWeb/resource/Histogram.js b/Modules/QtWidgetsWeb/resource/Histogram.js deleted file mode 100644 index d70501a0b5..0000000000 --- a/Modules/QtWidgetsWeb/resource/Histogram.js +++ /dev/null @@ -1,562 +0,0 @@ -/*=================================================================== - -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 = 10; -var min; -var max; - -/* - * Connecting signals from qt side with JavaScript methods. - */ -if (!connected) -{ - connected = true; - histogramData.SignalDataChanged.connect(updateHistogram); - histogramData.SignalGraphChanged.connect(updateHistogram); -} - -function disconnectSignals() -{ - histogramData.SignalDataChanged.disconnect(updateHistogram); - histogramData.SignalGraphChanged.disconnect(updateHistogram); - delete histogramData; -} - -/* - * Predefinition of scales. - */ -var xScale = d3.scale.linear() - .domain([d3.min(histogramData.measurement)-binSize/2,d3.max(histogramData.measurement)+binSize/2]) - .range([0,width]); - -var yScale = d3.scale.linear() - .domain([d3.min(histogramData.frequency),d3.max(histogramData.frequency)]) - .range([height,margin.top]); - -/* - * Predefinition of axis elements. - */ -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")); - -/* - * Predefinition of the zoom. - */ -var zoombie = d3.behavior.zoom().x(xScale).scaleExtent([1, 50]).on("zoom", zoom); - -/* - * Creation of the svg element, which holds the complete histogram. - */ -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); - -/* - * Appending a rectangle to the svg, to guarantee the possibility - * of zooming on the whole histogram. - */ -svg.append("rect") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .attr("opacity", 0); - -/* - * Appending a second svg to main svg, which holds only the graph. - */ -var vis = svg.append("svg") - .attr("width", width) - .attr("height", height); - -/* - * Predefinition of the lines. - */ - -var line = d3.svg.line() - .interpolate("linear") - .x(function(d,i) { - if (!histogramData.useLineGraph) - { - return xScale(histogramData.measurement[i]-binSize/2); - } - else if (histogramData.useLineGraph) - { - return xScale(histogramData.measurement[i]); - } - }) - .y(function(d) { - return yScale(d); - }); - -var linenull = d3.svg.line() - .interpolate("linear") - .x(function(d,i) { - return xScale(histogramData.measurement[i]-binSize/2); - }) - .y(function(d) { - return yScale(0); - }); - -updateHistogram(); - -/* - * Method to update the histogram data - * and to change the displayed graph. - */ -function updateHistogram() -{ - calcBinSize(); - if (!histogramData.useLineGraph) - { - barChart(); - } - else if (histogramData.useLineGraph) - { - linePlot() - } -} - -/* - * Calculation of the bin size. - */ -function calcBinSize() -{ - if (1 < histogramData.measurement.length) - { - min = d3.min(histogramData.measurement); - max = d3.max(histogramData.measurement); - binSize = ((max - min) / (histogramData.measurement.length-1)); - } - else - { - binSize = 10; - } -} - -/* - * Method to display histogram as a barchart. - */ -function barChart() -{ - definition(); - - /* - * Change zoom to a fixed y-axis. - */ - zoombie = d3.behavior.zoom().x(xScale).scaleExtent([1, 50]).on("zoom", zoom); - - svg.call(zoombie); - - /* - * Element to animate transition from linegraph to barchart. - */ - vis.selectAll("path.line").remove(); - vis.selectAll("circle").remove(); - - /* - * Definition of the bar elements. - */ - var bar = vis.selectAll("rect.bar").data(histogramData.frequency); - - /* - * Definition how to handle new bar elements. - */ - 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) - - /* - * Definition how to handle changed bar elements. - */ - bar.transition() - .duration(dur) - .attr("x", function(d,i) { - return xScale(histogramData.measurement[i]-binSize/2); - }) - .attr("y", myYPostion) - .attr("height", barHeight) - .attr("width", barWidth); - - /* - * Definition how to handle bar elements which doesn't exist anymore.' - */ - bar.exit() - .transition() - .duration(dur) - .attr("y", height) - .attr("height", 0) - .remove(); - - /* - * Update of axis elements. - * First delete old ones, then generate new. - */ - svg.selectAll("g") - .remove(); - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis); - - svg.append("g") - .attr("class", "y axis") - .call(yAxis); -} - -/* - * Method to display histogram as a linegraph. - */ -function linePlot() -{ - definition(); - - /* - * Change zoom to a zoomable y-axis. - */ - zoombie = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 50]).on("zoom", zoom); - - svg.call(zoombie); - /* - * Elements to animate transitions from barchart to linegraph. - * Different transition when an intensity profile is generated. - */ - if(!histogramData.intensityProfile) - { - vis.selectAll("rect.bar") - .transition() - .duration(dur) - .attr("height", 0) - .remove(); - } - else - { - vis.selectAll("rect.bar") - .transition() - .duration(dur) - .attr("y", height) // <-- - .attr("height", 0) - .remove(); - } - - /* - * Creating circle elements, when an intensity profile is generated to show tooltips. - * Due performance losses tooltips are not supported for line histograms. - */ - if(histogramData.intensityProfile) - { - var circles = vis.selectAll("circle").data(histogramData.frequency); - - /* - * Definition how to handle new circle elements. - */ - circles.enter() - .append("circle") - .on("mouseover", myMouseOverLine) - .on("mouseout", myMouseOutLine) - .attr("cx", function(d,i) { - return xScale(histogramData.measurement[i]-binSize/2); - }) - .attr("cy", function (d) { - return yScale(d) - }) - .attr("r", 5) - .attr("opacity", 0) - .style("stroke", "red") - .style("stroke-width", 1) - .style("fill-opacity", 0); - - /* - * Definition how to handle bar elements which doesn't exist anymore. - */ - circles.exit().remove(); - } - else - { - /* - * Removing of all circle elements if a line histogram is generated. - */ - vis.selectAll("circle").remove(); - } - - /* - * Creating a new path element. - */ - var graph = vis.selectAll("path.line") - .data([histogramData.frequency]); - -/* - * Definition how to handle a new path element, using predefined lines. - */ - graph.enter() - .append("path") - .attr("class", "line") - .transition() - .duration(dur) - .attr("d", line); - -/* - * Definition how to handle change points in an existing path element. - */ - graph.transition() - .duration(dur) - .attr("d", line); - - /* - * Update of axis elements. - * First delete old ones, then generate new. - */ - svg.selectAll("g") - .remove(); - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis); - - svg.append("g") - .attr("class", "y axis") - .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]); - -/* - * Match axes to current scale - */ - 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 calculate barwidth in px. - */ -function barWidth(d, i) -{ - var bw; - if (i != (histogramData.measurement.length-1)) - { - bw =(xScale(histogramData.measurement[i + 1]) - xScale(histogramData.measurement[i])) * (histogramData.frequency.length / (histogramData.frequency.length + 1)) - 1; - } - else - { - bw =(xScale(histogramData.measurement[i]) - xScale(histogramData.measurement[i - 1])) * (histogramData.frequency.length / (histogramData.frequency.length + 1)) - 1; - } - /* - * Ensure barwidth is not smaller than 1px. - */ - bw = bw > 1 ? bw : 1; - return bw; -} - -/* - * Method to calculate barheight in px. - * Ensure barheight is not smaller than 1px. - */ -function barHeight(d) -{ - var bh; - bh = height - yScale(d); - bh = bh >=2 ? bh : 2; - return bh; -} - -/* - * Method to calculate dynamical y positions. - */ -function myYPostion(d) -{ - var myy = yScale(d); - myy = (height-myy) > 2 ? myy : (height-2); - if (d == 0) - { - return height; - } - return myy; -} - -/* - * Method to fit parameters of bars/line when zoomed. - * Update axes elements. - * Resets the view if scale is 1. - */ -function zoom() -{ - if (zoombie.scale() == 1) - { - zoombie.translate([0,0]); - xScale.domain([d3.min(histogramData.measurement)-binSize/2,d3.max(histogramData.measurement)+binSize/2]); - 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()); - vis.selectAll("circle") - .attr("cx", function(d, i) { - return xScale(histogramData.measurement[i]-binSize/2); - }) - .attr("cy", function(d) { - return yScale(d); - }); - } -} - -/* - * 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"); - if ((min >= 0) && (max <= 2)) //tooltip for float images - { - d3.select(".measurement").text("Greayvalue: " + (Math.round(x*1000)/1000)); - } - else - { - d3.select(".measurement").text("Greyvalue: " + (Math.round(x*10)/10) + " ... " + (Math.round((x+binSize)*10)/10)); - } - d3.select(".frequency").text("Frequency: " + y); -} - -/* - * Hide infobox, when mouse not over a bin. - */ -function myMouseOut() -{ - var myBar = d3.select(this); - myBar.style("fill", d3.rgb(0,71,185)); - d3.select(".infobox").style("display", "none"); -} - -/* - * Show tooltip, while mouse is over a circle in an intensity profile. - */ -function myMouseOverLine() -{ - var myCircle = d3.select(this) - var reScale = d3.scale.linear() - .domain(xScale.range()) - .range(xScale.domain()); - var y = myCircle.data(); - var x = reScale(myCircle.attr("cx")); - - x = x >= 0 ? x : 0; - - myCircle.attr("opacity", 1); - d3.select(".infobox").style("display", "block"); - d3.select(".measurement").text("Distance: " + (Math.round(x*100)/100) + " mm"); - d3.select(".frequency").text("Intensity: " + y); -} - -/* - * Hide infobox, when mouse not over a circle. - */ -function myMouseOutLine() -{ - var myCircle = d3.select(this); - myCircle.attr("opacity", 0); - d3.select(".infobox").style("display", "none"); -} - -/* - * Update mousecoordinates when mouse is moved. - * Tooltip is shown on the right side of the mouse until it reaches the right boundary, - * the it switches to the left side. - */ -function myMouseMove() -{ - var infobox = d3.select(".infobox"); - var coords = d3.mouse(this); - if ((coords[0]+120)<(width-margin.right)) - { - infobox.style("left", coords[0] + 75 + "px"); - infobox.style("top", coords[1] + "px"); - } - else - { - infobox.style("left", coords[0] - 90 + "px"); - infobox.style("top",coords[1] + "px"); - } -} diff --git a/Modules/QtWidgetsWeb/resource/QtWidgetsWeb.qrc b/Modules/QtWidgetsWeb/resource/QtWidgetsWeb.qrc deleted file mode 100644 index 66aee359c0..0000000000 --- a/Modules/QtWidgetsWeb/resource/QtWidgetsWeb.qrc +++ /dev/null @@ -1,8 +0,0 @@ - - - d3.v2.js - Histogram.css - Histogram.html - Histogram.js - - diff --git a/Modules/QtWidgetsWeb/resource/d3.v2.js b/Modules/QtWidgetsWeb/resource/d3.v2.js deleted file mode 100644 index fc0503c984..0000000000 --- a/Modules/QtWidgetsWeb/resource/d3.v2.js +++ /dev/null @@ -1,7033 +0,0 @@ -(function() { - function d3_class(ctor, properties) { - try { - for (var key in properties) { - Object.defineProperty(ctor.prototype, key, { - value: properties[key], - enumerable: false - }); - } - } catch (e) { - ctor.prototype = properties; - } - } - function d3_arrayCopy(pseudoarray) { - var i = -1, n = pseudoarray.length, array = []; - while (++i < n) array.push(pseudoarray[i]); - return array; - } - function d3_arraySlice(pseudoarray) { - return Array.prototype.slice.call(pseudoarray); - } - function d3_Map() {} - function d3_identity(d) { - return d; - } - function d3_this() { - return this; - } - function d3_true() { - return true; - } - function d3_functor(v) { - return typeof v === "function" ? v : function() { - return v; - }; - } - function d3_rebind(target, source, method) { - return function() { - var value = method.apply(source, arguments); - return arguments.length ? target : value; - }; - } - function d3_number(x) { - return x != null && !isNaN(x); - } - function d3_zipLength(d) { - return d.length; - } - function d3_splitter(d) { - return d == null; - } - function d3_collapse(s) { - return s.trim().replace(/\s+/g, " "); - } - function d3_range_integerScale(x) { - var k = 1; - while (x * k % 1) k *= 10; - return k; - } - function d3_dispatch() {} - function d3_dispatch_event(dispatch) { - function event() { - var z = listeners, i = -1, n = z.length, l; - while (++i < n) if (l = z[i].on) l.apply(this, arguments); - return dispatch; - } - var listeners = [], listenerByName = new d3_Map; - event.on = function(name, listener) { - var l = listenerByName.get(name), i; - if (arguments.length < 2) return l && l.on; - if (l) { - l.on = null; - listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); - listenerByName.remove(name); - } - if (listener) listeners.push(listenerByName.set(name, { - on: listener - })); - return dispatch; - }; - return event; - } - function d3_format_precision(x, p) { - return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1); - } - function d3_format_typeDefault(x) { - return x + ""; - } - function d3_format_group(value) { - var i = value.lastIndexOf("."), f = i >= 0 ? value.substring(i) : (i = value.length, ""), t = []; - while (i > 0) t.push(value.substring(i -= 3, i + 3)); - return t.reverse().join(",") + f; - } - function d3_formatPrefix(d, i) { - var k = Math.pow(10, Math.abs(8 - i) * 3); - return { - scale: i > 8 ? function(d) { - return d / k; - } : function(d) { - return d * k; - }, - symbol: d - }; - } - function d3_ease_clamp(f) { - return function(t) { - return t <= 0 ? 0 : t >= 1 ? 1 : f(t); - }; - } - function d3_ease_reverse(f) { - return function(t) { - return 1 - f(1 - t); - }; - } - function d3_ease_reflect(f) { - return function(t) { - return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); - }; - } - function d3_ease_identity(t) { - return t; - } - function d3_ease_poly(e) { - return function(t) { - return Math.pow(t, e); - }; - } - function d3_ease_sin(t) { - return 1 - Math.cos(t * Math.PI / 2); - } - function d3_ease_exp(t) { - return Math.pow(2, 10 * (t - 1)); - } - function d3_ease_circle(t) { - return 1 - Math.sqrt(1 - t * t); - } - function d3_ease_elastic(a, p) { - var s; - if (arguments.length < 2) p = .45; - if (arguments.length < 1) { - a = 1; - s = p / 4; - } else s = p / (2 * Math.PI) * Math.asin(1 / a); - return function(t) { - return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Math.PI / p); - }; - } - function d3_ease_back(s) { - if (!s) s = 1.70158; - return function(t) { - return t * t * ((s + 1) * t - s); - }; - } - function d3_ease_bounce(t) { - return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; - } - function d3_eventCancel() { - d3.event.stopPropagation(); - d3.event.preventDefault(); - } - function d3_eventSource() { - var e = d3.event, s; - while (s = e.sourceEvent) e = s; - return e; - } - function d3_eventDispatch(target) { - var dispatch = new d3_dispatch, i = 0, n = arguments.length; - while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); - dispatch.of = function(thiz, argumentz) { - return function(e1) { - try { - var e0 = e1.sourceEvent = d3.event; - e1.target = target; - d3.event = e1; - dispatch[e1.type].apply(thiz, argumentz); - } finally { - d3.event = e0; - } - }; - }; - return dispatch; - } - function d3_transform(m) { - var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; - if (r0[0] * r1[1] < r1[0] * r0[1]) { - r0[0] *= -1; - r0[1] *= -1; - kx *= -1; - kz *= -1; - } - this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_transformDegrees; - this.translate = [ m.e, m.f ]; - this.scale = [ kx, ky ]; - this.skew = ky ? Math.atan2(kz, ky) * d3_transformDegrees : 0; - } - function d3_transformDot(a, b) { - return a[0] * b[0] + a[1] * b[1]; - } - function d3_transformNormalize(a) { - var k = Math.sqrt(d3_transformDot(a, a)); - if (k) { - a[0] /= k; - a[1] /= k; - } - return k; - } - function d3_transformCombine(a, b, k) { - a[0] += k * b[0]; - a[1] += k * b[1]; - return a; - } - function d3_interpolateByName(name) { - return name == "transform" ? d3.interpolateTransform : d3.interpolate; - } - function d3_uninterpolateNumber(a, b) { - b = b - (a = +a) ? 1 / (b - a) : 0; - return function(x) { - return (x - a) * b; - }; - } - function d3_uninterpolateClamp(a, b) { - b = b - (a = +a) ? 1 / (b - a) : 0; - return function(x) { - return Math.max(0, Math.min(1, (x - a) * b)); - }; - } - function d3_rgb(r, g, b) { - return new d3_Rgb(r, g, b); - } - function d3_Rgb(r, g, b) { - this.r = r; - this.g = g; - this.b = b; - } - function d3_rgb_hex(v) { - return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); - } - function d3_rgb_parse(format, rgb, hsl) { - var r = 0, g = 0, b = 0, m1, m2, name; - m1 = /([a-z]+)\((.*)\)/i.exec(format); - if (m1) { - m2 = m1[2].split(","); - switch (m1[1]) { - case "hsl": - { - return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); - } - case "rgb": - { - return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); - } - } - } - if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); - if (format != null && format.charAt(0) === "#") { - if (format.length === 4) { - r = format.charAt(1); - r += r; - g = format.charAt(2); - g += g; - b = format.charAt(3); - b += b; - } else if (format.length === 7) { - r = format.substring(1, 3); - g = format.substring(3, 5); - b = format.substring(5, 7); - } - r = parseInt(r, 16); - g = parseInt(g, 16); - b = parseInt(b, 16); - } - return rgb(r, g, b); - } - function d3_rgb_hsl(r, g, b) { - var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; - if (d) { - s = l < .5 ? d / (max + min) : d / (2 - max - min); - if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; - h *= 60; - } else { - s = h = 0; - } - return d3_hsl(h, s, l); - } - function d3_rgb_lab(r, g, b) { - r = d3_rgb_xyz(r); - g = d3_rgb_xyz(g); - b = d3_rgb_xyz(b); - var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); - return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); - } - function d3_rgb_xyz(r) { - return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); - } - function d3_rgb_parseNumber(c) { - var f = parseFloat(c); - return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; - } - function d3_hsl(h, s, l) { - return new d3_Hsl(h, s, l); - } - function d3_Hsl(h, s, l) { - this.h = h; - this.s = s; - this.l = l; - } - function d3_hsl_rgb(h, s, l) { - function v(h) { - if (h > 360) h -= 360; else if (h < 0) h += 360; - if (h < 60) return m1 + (m2 - m1) * h / 60; - if (h < 180) return m2; - if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; - return m1; - } - function vv(h) { - return Math.round(v(h) * 255); - } - var m1, m2; - h = h % 360; - if (h < 0) h += 360; - s = s < 0 ? 0 : s > 1 ? 1 : s; - l = l < 0 ? 0 : l > 1 ? 1 : l; - m2 = l <= .5 ? l * (1 + s) : l + s - l * s; - m1 = 2 * l - m2; - return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); - } - function d3_hcl(h, c, l) { - return new d3_Hcl(h, c, l); - } - function d3_Hcl(h, c, l) { - this.h = h; - this.c = c; - this.l = l; - } - function d3_hcl_lab(h, c, l) { - return d3_lab(l, Math.cos(h *= Math.PI / 180) * c, Math.sin(h) * c); - } - function d3_lab(l, a, b) { - return new d3_Lab(l, a, b); - } - function d3_Lab(l, a, b) { - this.l = l; - this.a = a; - this.b = b; - } - function d3_lab_rgb(l, a, b) { - var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; - x = d3_lab_xyz(x) * d3_lab_X; - y = d3_lab_xyz(y) * d3_lab_Y; - z = d3_lab_xyz(z) * d3_lab_Z; - return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); - } - function d3_lab_hcl(l, a, b) { - return d3_hcl(Math.atan2(b, a) / Math.PI * 180, Math.sqrt(a * a + b * b), l); - } - function d3_lab_xyz(x) { - return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; - } - function d3_xyz_lab(x) { - return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; - } - function d3_xyz_rgb(r) { - return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); - } - function d3_selection(groups) { - d3_arraySubclass(groups, d3_selectionPrototype); - return groups; - } - function d3_selection_selector(selector) { - return function() { - return d3_select(selector, this); - }; - } - function d3_selection_selectorAll(selector) { - return function() { - return d3_selectAll(selector, this); - }; - } - function d3_selection_attr(name, value) { - function attrNull() { - this.removeAttribute(name); - } - function attrNullNS() { - this.removeAttributeNS(name.space, name.local); - } - function attrConstant() { - this.setAttribute(name, value); - } - function attrConstantNS() { - this.setAttributeNS(name.space, name.local, value); - } - function attrFunction() { - var x = value.apply(this, arguments); - if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); - } - function attrFunctionNS() { - var x = value.apply(this, arguments); - if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); - } - name = d3.ns.qualify(name); - return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; - } - function d3_selection_classedRe(name) { - return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); - } - function d3_selection_classed(name, value) { - function classedConstant() { - var i = -1; - while (++i < n) name[i](this, value); - } - function classedFunction() { - var i = -1, x = value.apply(this, arguments); - while (++i < n) name[i](this, x); - } - name = name.trim().split(/\s+/).map(d3_selection_classedName); - var n = name.length; - return typeof value === "function" ? classedFunction : classedConstant; - } - function d3_selection_classedName(name) { - var re = d3_selection_classedRe(name); - return function(node, value) { - if (c = node.classList) return value ? c.add(name) : c.remove(name); - var c = node.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c; - if (value) { - re.lastIndex = 0; - if (!re.test(cv)) { - cv = d3_collapse(cv + " " + name); - if (cb) c.baseVal = cv; else node.className = cv; - } - } else if (cv) { - cv = d3_collapse(cv.replace(re, " ")); - if (cb) c.baseVal = cv; else node.className = cv; - } - }; - } - function d3_selection_style(name, value, priority) { - function styleNull() { - this.style.removeProperty(name); - } - function styleConstant() { - this.style.setProperty(name, value, priority); - } - function styleFunction() { - var x = value.apply(this, arguments); - if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); - } - return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; - } - function d3_selection_property(name, value) { - function propertyNull() { - delete this[name]; - } - function propertyConstant() { - this[name] = value; - } - function propertyFunction() { - var x = value.apply(this, arguments); - if (x == null) delete this[name]; else this[name] = x; - } - return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; - } - function d3_selection_dataNode(data) { - return { - __data__: data - }; - } - function d3_selection_filter(selector) { - return function() { - return d3_selectMatches(this, selector); - }; - } - function d3_selection_sortComparator(comparator) { - if (!arguments.length) comparator = d3.ascending; - return function(a, b) { - return comparator(a && a.__data__, b && b.__data__); - }; - } - function d3_selection_on(type, listener, capture) { - function onRemove() { - var wrapper = this[name]; - if (wrapper) { - this.removeEventListener(type, wrapper, wrapper.$); - delete this[name]; - } - } - function onAdd() { - function wrapper(e) { - var o = d3.event; - d3.event = e; - args[0] = node.__data__; - try { - listener.apply(node, args); - } finally { - d3.event = o; - } - } - var node = this, args = arguments; - onRemove.call(this); - this.addEventListener(type, this[name] = wrapper, wrapper.$ = capture); - wrapper._ = listener; - } - var name = "__on" + type, i = type.indexOf("."); - if (i > 0) type = type.substring(0, i); - return listener ? onAdd : onRemove; - } - function d3_selection_each(groups, callback) { - for (var j = 0, m = groups.length; j < m; j++) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { - if (node = group[i]) callback(node, i, j); - } - } - return groups; - } - function d3_selection_enter(selection) { - d3_arraySubclass(selection, d3_selection_enterPrototype); - return selection; - } - function d3_transition(groups, id, time) { - d3_arraySubclass(groups, d3_transitionPrototype); - var tweens = new d3_Map, event = d3.dispatch("start", "end"), ease = d3_transitionEase; - groups.id = id; - groups.time = time; - groups.tween = function(name, tween) { - if (arguments.length < 2) return tweens.get(name); - if (tween == null) tweens.remove(name); else tweens.set(name, tween); - return groups; - }; - groups.ease = function(value) { - if (!arguments.length) return ease; - ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments); - return groups; - }; - groups.each = function(type, listener) { - if (arguments.length < 2) return d3_transition_each.call(groups, type); - event.on(type, listener); - return groups; - }; - d3.timer(function(elapsed) { - return d3_selection_each(groups, function(node, i, j) { - function start(elapsed) { - if (lock.active > id) return stop(); - lock.active = id; - tweens.forEach(function(key, value) { - if (value = value.call(node, d, i)) { - tweened.push(value); - } - }); - event.start.call(node, d, i); - if (!tick(elapsed)) d3.timer(tick, 0, time); - return 1; - } - function tick(elapsed) { - if (lock.active !== id) return stop(); - var t = (elapsed - delay) / duration, e = ease(t), n = tweened.length; - while (n > 0) { - tweened[--n].call(node, e); - } - if (t >= 1) { - stop(); - d3_transitionId = id; - event.end.call(node, d, i); - d3_transitionId = 0; - return 1; - } - } - function stop() { - if (!--lock.count) delete node.__transition__; - return 1; - } - var tweened = [], delay = node.delay, duration = node.duration, lock = (node = node.node).__transition__ || (node.__transition__ = { - active: 0, - count: 0 - }), d = node.__data__; - ++lock.count; - delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time); - }); - }, 0, time); - return groups; - } - function d3_transition_each(callback) { - var id = d3_transitionId, ease = d3_transitionEase, delay = d3_transitionDelay, duration = d3_transitionDuration; - d3_transitionId = this.id; - d3_transitionEase = this.ease(); - d3_selection_each(this, function(node, i, j) { - d3_transitionDelay = node.delay; - d3_transitionDuration = node.duration; - callback.call(node = node.node, node.__data__, i, j); - }); - d3_transitionId = id; - d3_transitionEase = ease; - d3_transitionDelay = delay; - d3_transitionDuration = duration; - return this; - } - function d3_tweenNull(d, i, a) { - return a != "" && d3_tweenRemove; - } - function d3_tweenByName(b, name) { - return d3.tween(b, d3_interpolateByName(name)); - } - function d3_timer_step() { - var elapsed, now = Date.now(), t1 = d3_timer_queue; - while (t1) { - elapsed = now - t1.then; - if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); - t1 = t1.next; - } - var delay = d3_timer_flush() - now; - if (delay > 24) { - if (isFinite(delay)) { - clearTimeout(d3_timer_timeout); - d3_timer_timeout = setTimeout(d3_timer_step, delay); - } - d3_timer_interval = 0; - } else { - d3_timer_interval = 1; - d3_timer_frame(d3_timer_step); - } - } - function d3_timer_flush() { - var t0 = null, t1 = d3_timer_queue, then = Infinity; - while (t1) { - if (t1.flush) { - t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; - } else { - then = Math.min(then, t1.then + t1.delay); - t1 = (t0 = t1).next; - } - } - return then; - } - function d3_mousePoint(container, e) { - var svg = container.ownerSVGElement || container; - if (svg.createSVGPoint) { - var point = svg.createSVGPoint(); - if (d3_mouse_bug44083 < 0 && (window.scrollX || window.scrollY)) { - svg = d3.select(document.body).append("svg").style("position", "absolute").style("top", 0).style("left", 0); - var ctm = svg[0][0].getScreenCTM(); - d3_mouse_bug44083 = !(ctm.f || ctm.e); - svg.remove(); - } - if (d3_mouse_bug44083) { - point.x = e.pageX; - point.y = e.pageY; - } else { - point.x = e.clientX; - point.y = e.clientY; - } - point = point.matrixTransform(container.getScreenCTM().inverse()); - return [ point.x, point.y ]; - } - var rect = container.getBoundingClientRect(); - return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; - } - function d3_noop() {} - function d3_scaleExtent(domain) { - var start = domain[0], stop = domain[domain.length - 1]; - return start < stop ? [ start, stop ] : [ stop, start ]; - } - function d3_scaleRange(scale) { - return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); - } - function d3_scale_nice(domain, nice) { - var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; - if (x1 < x0) { - dx = i0, i0 = i1, i1 = dx; - dx = x0, x0 = x1, x1 = dx; - } - if (nice = nice(x1 - x0)) { - domain[i0] = nice.floor(x0); - domain[i1] = nice.ceil(x1); - } - return domain; - } - function d3_scale_niceDefault() { - return Math; - } - function d3_scale_linear(domain, range, interpolate, clamp) { - function rescale() { - var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; - output = linear(domain, range, uninterpolate, interpolate); - input = linear(range, domain, uninterpolate, d3.interpolate); - return scale; - } - function scale(x) { - return output(x); - } - var output, input; - scale.invert = function(y) { - return input(y); - }; - scale.domain = function(x) { - if (!arguments.length) return domain; - domain = x.map(Number); - return rescale(); - }; - scale.range = function(x) { - if (!arguments.length) return range; - range = x; - return rescale(); - }; - scale.rangeRound = function(x) { - return scale.range(x).interpolate(d3.interpolateRound); - }; - scale.clamp = function(x) { - if (!arguments.length) return clamp; - clamp = x; - return rescale(); - }; - scale.interpolate = function(x) { - if (!arguments.length) return interpolate; - interpolate = x; - return rescale(); - }; - scale.ticks = function(m) { - return d3_scale_linearTicks(domain, m); - }; - scale.tickFormat = function(m) { - return d3_scale_linearTickFormat(domain, m); - }; - scale.nice = function() { - d3_scale_nice(domain, d3_scale_linearNice); - return rescale(); - }; - scale.copy = function() { - return d3_scale_linear(domain, range, interpolate, clamp); - }; - return rescale(); - } - function d3_scale_linearRebind(scale, linear) { - return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); - } - function d3_scale_linearNice(dx) { - dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1); - return dx && { - floor: function(x) { - return Math.floor(x / dx) * dx; - }, - ceil: function(x) { - return Math.ceil(x / dx) * dx; - } - }; - } - function d3_scale_linearTickRange(domain, m) { - var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; - if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; - extent[0] = Math.ceil(extent[0] / step) * step; - extent[1] = Math.floor(extent[1] / step) * step + step * .5; - extent[2] = step; - return extent; - } - function d3_scale_linearTicks(domain, m) { - return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); - } - function d3_scale_linearTickFormat(domain, m) { - return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f"); - } - function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { - var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); - return function(x) { - return i(u(x)); - }; - } - function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { - var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; - if (domain[k] < domain[0]) { - domain = domain.slice().reverse(); - range = range.slice().reverse(); - } - while (++j <= k) { - u.push(uninterpolate(domain[j - 1], domain[j])); - i.push(interpolate(range[j - 1], range[j])); - } - return function(x) { - var j = d3.bisect(domain, x, 1, k) - 1; - return i[j](u[j](x)); - }; - } - function d3_scale_log(linear, log) { - function scale(x) { - return linear(log(x)); - } - var pow = log.pow; - scale.invert = function(x) { - return pow(linear.invert(x)); - }; - scale.domain = function(x) { - if (!arguments.length) return linear.domain().map(pow); - log = x[0] < 0 ? d3_scale_logn : d3_scale_logp; - pow = log.pow; - linear.domain(x.map(log)); - return scale; - }; - scale.nice = function() { - linear.domain(d3_scale_nice(linear.domain(), d3_scale_niceDefault)); - return scale; - }; - scale.ticks = function() { - var extent = d3_scaleExtent(linear.domain()), ticks = []; - if (extent.every(isFinite)) { - var i = Math.floor(extent[0]), j = Math.ceil(extent[1]), u = pow(extent[0]), v = pow(extent[1]); - if (log === d3_scale_logn) { - ticks.push(pow(i)); - for (; i++ < j; ) for (var k = 9; k > 0; k--) ticks.push(pow(i) * k); - } else { - for (; i < j; i++) for (var k = 1; k < 10; k++) ticks.push(pow(i) * k); - ticks.push(pow(i)); - } - for (i = 0; ticks[i] < u; i++) {} - for (j = ticks.length; ticks[j - 1] > v; j--) {} - ticks = ticks.slice(i, j); - } - return ticks; - }; - scale.tickFormat = function(n, format) { - if (arguments.length < 2) format = d3_scale_logFormat; - if (arguments.length < 1) return format; - var k = Math.max(.1, n / scale.ticks().length), f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil), e; - return function(d) { - return d / pow(f(log(d) + e)) <= k ? format(d) : ""; - }; - }; - scale.copy = function() { - return d3_scale_log(linear.copy(), log); - }; - return d3_scale_linearRebind(scale, linear); - } - function d3_scale_logp(x) { - return Math.log(x < 0 ? 0 : x) / Math.LN10; - } - function d3_scale_logn(x) { - return -Math.log(x > 0 ? 0 : -x) / Math.LN10; - } - function d3_scale_pow(linear, exponent) { - function scale(x) { - return linear(powp(x)); - } - var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); - scale.invert = function(x) { - return powb(linear.invert(x)); - }; - scale.domain = function(x) { - if (!arguments.length) return linear.domain().map(powb); - linear.domain(x.map(powp)); - return scale; - }; - scale.ticks = function(m) { - return d3_scale_linearTicks(scale.domain(), m); - }; - scale.tickFormat = function(m) { - return d3_scale_linearTickFormat(scale.domain(), m); - }; - scale.nice = function() { - return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice)); - }; - scale.exponent = function(x) { - if (!arguments.length) return exponent; - var domain = scale.domain(); - powp = d3_scale_powPow(exponent = x); - powb = d3_scale_powPow(1 / exponent); - return scale.domain(domain); - }; - scale.copy = function() { - return d3_scale_pow(linear.copy(), exponent); - }; - return d3_scale_linearRebind(scale, linear); - } - function d3_scale_powPow(e) { - return function(x) { - return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); - }; - } - function d3_scale_ordinal(domain, ranger) { - function scale(x) { - return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; - } - function steps(start, step) { - return d3.range(domain.length).map(function(i) { - return start + step * i; - }); - } - var index, range, rangeBand; - scale.domain = function(x) { - if (!arguments.length) return domain; - domain = []; - index = new d3_Map; - var i = -1, n = x.length, xi; - while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); - return scale[ranger.t].apply(scale, ranger.a); - }; - scale.range = function(x) { - if (!arguments.length) return range; - range = x; - rangeBand = 0; - ranger = { - t: "range", - a: arguments - }; - return scale; - }; - scale.rangePoints = function(x, padding) { - if (arguments.length < 2) padding = 0; - var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding); - range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); - rangeBand = 0; - ranger = { - t: "rangePoints", - a: arguments - }; - return scale; - }; - scale.rangeBands = function(x, padding, outerPadding) { - if (arguments.length < 2) padding = 0; - if (arguments.length < 3) outerPadding = padding; - var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); - range = steps(start + step * outerPadding, step); - if (reverse) range.reverse(); - rangeBand = step * (1 - padding); - ranger = { - t: "rangeBands", - a: arguments - }; - return scale; - }; - scale.rangeRoundBands = function(x, padding, outerPadding) { - if (arguments.length < 2) padding = 0; - if (arguments.length < 3) outerPadding = padding; - var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step; - range = steps(start + Math.round(error / 2), step); - if (reverse) range.reverse(); - rangeBand = Math.round(step * (1 - padding)); - ranger = { - t: "rangeRoundBands", - a: arguments - }; - return scale; - }; - scale.rangeBand = function() { - return rangeBand; - }; - scale.rangeExtent = function() { - return d3_scaleExtent(ranger.a[0]); - }; - scale.copy = function() { - return d3_scale_ordinal(domain, ranger); - }; - return scale.domain(domain); - } - function d3_scale_quantile(domain, range) { - function rescale() { - var k = 0, n = domain.length, q = range.length; - thresholds = []; - while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); - return scale; - } - function scale(x) { - if (isNaN(x = +x)) return NaN; - return range[d3.bisect(thresholds, x)]; - } - var thresholds; - scale.domain = function(x) { - if (!arguments.length) return domain; - domain = x.filter(function(d) { - return !isNaN(d); - }).sort(d3.ascending); - return rescale(); - }; - scale.range = function(x) { - if (!arguments.length) return range; - range = x; - return rescale(); - }; - scale.quantiles = function() { - return thresholds; - }; - scale.copy = function() { - return d3_scale_quantile(domain, range); - }; - return rescale(); - } - function d3_scale_quantize(x0, x1, range) { - function scale(x) { - return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; - } - function rescale() { - kx = range.length / (x1 - x0); - i = range.length - 1; - return scale; - } - var kx, i; - scale.domain = function(x) { - if (!arguments.length) return [ x0, x1 ]; - x0 = +x[0]; - x1 = +x[x.length - 1]; - return rescale(); - }; - scale.range = function(x) { - if (!arguments.length) return range; - range = x; - return rescale(); - }; - scale.copy = function() { - return d3_scale_quantize(x0, x1, range); - }; - return rescale(); - } - function d3_scale_threshold(domain, range) { - function scale(x) { - return range[d3.bisect(domain, x)]; - } - scale.domain = function(_) { - if (!arguments.length) return domain; - domain = _; - return scale; - }; - scale.range = function(_) { - if (!arguments.length) return range; - range = _; - return scale; - }; - scale.copy = function() { - return d3_scale_threshold(domain, range); - }; - return scale; - } - function d3_scale_identity(domain) { - function identity(x) { - return +x; - } - identity.invert = identity; - identity.domain = identity.range = function(x) { - if (!arguments.length) return domain; - domain = x.map(identity); - return identity; - }; - identity.ticks = function(m) { - return d3_scale_linearTicks(domain, m); - }; - identity.tickFormat = function(m) { - return d3_scale_linearTickFormat(domain, m); - }; - identity.copy = function() { - return d3_scale_identity(domain); - }; - return identity; - } - function d3_svg_arcInnerRadius(d) { - return d.innerRadius; - } - function d3_svg_arcOuterRadius(d) { - return d.outerRadius; - } - function d3_svg_arcStartAngle(d) { - return d.startAngle; - } - function d3_svg_arcEndAngle(d) { - return d.endAngle; - } - function d3_svg_line(projection) { - function line(data) { - function segment() { - segments.push("M", interpolate(projection(points), tension)); - } - var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); - while (++i < n) { - if (defined.call(this, d = data[i], i)) { - points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); - } else if (points.length) { - segment(); - points = []; - } - } - if (points.length) segment(); - return segments.length ? segments.join("") : null; - } - var x = d3_svg_lineX, y = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; - line.x = function(_) { - if (!arguments.length) return x; - x = _; - return line; - }; - line.y = function(_) { - if (!arguments.length) return y; - y = _; - return line; - }; - line.defined = function(_) { - if (!arguments.length) return defined; - defined = _; - return line; - }; - line.interpolate = function(_) { - if (!arguments.length) return interpolateKey; - if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; - return line; - }; - line.tension = function(_) { - if (!arguments.length) return tension; - tension = _; - return line; - }; - return line; - } - function d3_svg_lineX(d) { - return d[0]; - } - function d3_svg_lineY(d) { - return d[1]; - } - function d3_svg_lineLinear(points) { - return points.join("L"); - } - function d3_svg_lineLinearClosed(points) { - return d3_svg_lineLinear(points) + "Z"; - } - function d3_svg_lineStepBefore(points) { - var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; - while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); - return path.join(""); - } - function d3_svg_lineStepAfter(points) { - var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; - while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); - return path.join(""); - } - function d3_svg_lineCardinalOpen(points, tension) { - return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension)); - } - function d3_svg_lineCardinalClosed(points, tension) { - return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); - } - function d3_svg_lineCardinal(points, tension, closed) { - return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); - } - function d3_svg_lineHermite(points, tangents) { - if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { - return d3_svg_lineLinear(points); - } - var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; - if (quad) { - path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; - p0 = points[1]; - pi = 2; - } - if (tangents.length > 1) { - t = tangents[1]; - p = points[pi]; - pi++; - path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; - for (var i = 2; i < tangents.length; i++, pi++) { - p = points[pi]; - t = tangents[i]; - path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; - } - } - if (quad) { - var lp = points[pi]; - path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; - } - return path; - } - function d3_svg_lineCardinalTangents(points, tension) { - var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; - while (++i < n) { - p0 = p1; - p1 = p2; - p2 = points[i]; - tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); - } - return tangents; - } - function d3_svg_lineBasis(points) { - if (points.length < 3) return d3_svg_lineLinear(points); - var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0 ]; - d3_svg_lineBasisBezier(path, px, py); - while (++i < n) { - pi = points[i]; - px.shift(); - px.push(pi[0]); - py.shift(); - py.push(pi[1]); - d3_svg_lineBasisBezier(path, px, py); - } - i = -1; - while (++i < 2) { - px.shift(); - px.push(pi[0]); - py.shift(); - py.push(pi[1]); - d3_svg_lineBasisBezier(path, px, py); - } - return path.join(""); - } - function d3_svg_lineBasisOpen(points) { - if (points.length < 4) return d3_svg_lineLinear(points); - var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; - while (++i < 3) { - pi = points[i]; - px.push(pi[0]); - py.push(pi[1]); - } - path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); - --i; - while (++i < n) { - pi = points[i]; - px.shift(); - px.push(pi[0]); - py.shift(); - py.push(pi[1]); - d3_svg_lineBasisBezier(path, px, py); - } - return path.join(""); - } - function d3_svg_lineBasisClosed(points) { - var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; - while (++i < 4) { - pi = points[i % n]; - px.push(pi[0]); - py.push(pi[1]); - } - path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; - --i; - while (++i < m) { - pi = points[i % n]; - px.shift(); - px.push(pi[0]); - py.shift(); - py.push(pi[1]); - d3_svg_lineBasisBezier(path, px, py); - } - return path.join(""); - } - function d3_svg_lineBundle(points, tension) { - var n = points.length - 1; - if (n) { - var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; - while (++i <= n) { - p = points[i]; - t = i / n; - p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); - p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); - } - } - return d3_svg_lineBasis(points); - } - function d3_svg_lineDot4(a, b) { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; - } - function d3_svg_lineBasisBezier(path, x, y) { - path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); - } - function d3_svg_lineSlope(p0, p1) { - return (p1[1] - p0[1]) / (p1[0] - p0[0]); - } - function d3_svg_lineFiniteDifferences(points) { - var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); - while (++i < j) { - m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; - } - m[i] = d; - return m; - } - function d3_svg_lineMonotoneTangents(points) { - var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; - while (++i < j) { - d = d3_svg_lineSlope(points[i], points[i + 1]); - if (Math.abs(d) < 1e-6) { - m[i] = m[i + 1] = 0; - } else { - a = m[i] / d; - b = m[i + 1] / d; - s = a * a + b * b; - if (s > 9) { - s = d * 3 / Math.sqrt(s); - m[i] = s * a; - m[i + 1] = s * b; - } - } - } - i = -1; - while (++i <= j) { - s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); - tangents.push([ s || 0, m[i] * s || 0 ]); - } - return tangents; - } - function d3_svg_lineMonotone(points) { - return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); - } - function d3_svg_lineRadial(points) { - var point, i = -1, n = points.length, r, a; - while (++i < n) { - point = points[i]; - r = point[0]; - a = point[1] + d3_svg_arcOffset; - point[0] = r * Math.cos(a); - point[1] = r * Math.sin(a); - } - return points; - } - function d3_svg_area(projection) { - function area(data) { - function segment() { - segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); - } - var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { - return x; - } : d3_functor(x1), fy1 = y0 === y1 ? function() { - return y; - } : d3_functor(y1), x, y; - while (++i < n) { - if (defined.call(this, d = data[i], i)) { - points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); - points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); - } else if (points0.length) { - segment(); - points0 = []; - points1 = []; - } - } - if (points0.length) segment(); - return segments.length ? segments.join("") : null; - } - var x0 = d3_svg_lineX, x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; - area.x = function(_) { - if (!arguments.length) return x1; - x0 = x1 = _; - return area; - }; - area.x0 = function(_) { - if (!arguments.length) return x0; - x0 = _; - return area; - }; - area.x1 = function(_) { - if (!arguments.length) return x1; - x1 = _; - return area; - }; - area.y = function(_) { - if (!arguments.length) return y1; - y0 = y1 = _; - return area; - }; - area.y0 = function(_) { - if (!arguments.length) return y0; - y0 = _; - return area; - }; - area.y1 = function(_) { - if (!arguments.length) return y1; - y1 = _; - return area; - }; - area.defined = function(_) { - if (!arguments.length) return defined; - defined = _; - return area; - }; - area.interpolate = function(_) { - if (!arguments.length) return interpolateKey; - if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; - interpolateReverse = interpolate.reverse || interpolate; - L = interpolate.closed ? "M" : "L"; - return area; - }; - area.tension = function(_) { - if (!arguments.length) return tension; - tension = _; - return area; - }; - return area; - } - function d3_svg_chordSource(d) { - return d.source; - } - function d3_svg_chordTarget(d) { - return d.target; - } - function d3_svg_chordRadius(d) { - return d.radius; - } - function d3_svg_chordStartAngle(d) { - return d.startAngle; - } - function d3_svg_chordEndAngle(d) { - return d.endAngle; - } - function d3_svg_diagonalProjection(d) { - return [ d.x, d.y ]; - } - function d3_svg_diagonalRadialProjection(projection) { - return function() { - var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset; - return [ r * Math.cos(a), r * Math.sin(a) ]; - }; - } - function d3_svg_symbolSize() { - return 64; - } - function d3_svg_symbolType() { - return "circle"; - } - function d3_svg_symbolCircle(size) { - var r = Math.sqrt(size / Math.PI); - return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; - } - function d3_svg_axisX(selection, x) { - selection.attr("transform", function(d) { - return "translate(" + x(d) + ",0)"; - }); - } - function d3_svg_axisY(selection, y) { - selection.attr("transform", function(d) { - return "translate(0," + y(d) + ")"; - }); - } - function d3_svg_axisSubdivide(scale, ticks, m) { - subticks = []; - if (m && ticks.length > 1) { - var extent = d3_scaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v; - while (++i < n) { - for (j = m; --j > 0; ) { - if ((v = +ticks[i] - j * d) >= extent[0]) { - subticks.push(v); - } - } - } - for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1]; ) { - subticks.push(v); - } - } - return subticks; - } - function d3_behavior_zoomDelta() { - if (!d3_behavior_zoomDiv) { - d3_behavior_zoomDiv = d3.select("body").append("div").style("visibility", "hidden").style("top", 0).style("height", 0).style("width", 0).style("overflow-y", "scroll").append("div").style("height", "2000px").node().parentNode; - } - var e = d3.event, delta; - try { - d3_behavior_zoomDiv.scrollTop = 1e3; - d3_behavior_zoomDiv.dispatchEvent(e); - delta = 1e3 - d3_behavior_zoomDiv.scrollTop; - } catch (error) { - delta = e.wheelDelta || -e.detail * 5; - } - return delta; - } - function d3_layout_bundlePath(link) { - var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; - while (start !== lca) { - start = start.parent; - points.push(start); - } - var k = points.length; - while (end !== lca) { - points.splice(k, 0, end); - end = end.parent; - } - return points; - } - function d3_layout_bundleAncestors(node) { - var ancestors = [], parent = node.parent; - while (parent != null) { - ancestors.push(node); - node = parent; - parent = parent.parent; - } - ancestors.push(node); - return ancestors; - } - function d3_layout_bundleLeastCommonAncestor(a, b) { - if (a === b) return a; - var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; - while (aNode === bNode) { - sharedNode = aNode; - aNode = aNodes.pop(); - bNode = bNodes.pop(); - } - return sharedNode; - } - function d3_layout_forceDragstart(d) { - d.fixed |= 2; - } - function d3_layout_forceDragend(d) { - d.fixed &= 1; - } - function d3_layout_forceMouseover(d) { - d.fixed |= 4; - } - function d3_layout_forceMouseout(d) { - d.fixed &= 3; - } - function d3_layout_forceAccumulate(quad, alpha, charges) { - var cx = 0, cy = 0; - quad.charge = 0; - if (!quad.leaf) { - var nodes = quad.nodes, n = nodes.length, i = -1, c; - while (++i < n) { - c = nodes[i]; - if (c == null) continue; - d3_layout_forceAccumulate(c, alpha, charges); - quad.charge += c.charge; - cx += c.charge * c.cx; - cy += c.charge * c.cy; - } - } - if (quad.point) { - if (!quad.leaf) { - quad.point.x += Math.random() - .5; - quad.point.y += Math.random() - .5; - } - var k = alpha * charges[quad.point.index]; - quad.charge += quad.pointCharge = k; - cx += k * quad.point.x; - cy += k * quad.point.y; - } - quad.cx = cx / quad.charge; - quad.cy = cy / quad.charge; - } - function d3_layout_forceLinkDistance(link) { - return 20; - } - function d3_layout_forceLinkStrength(link) { - return 1; - } - function d3_layout_stackX(d) { - return d.x; - } - function d3_layout_stackY(d) { - return d.y; - } - function d3_layout_stackOut(d, y0, y) { - d.y0 = y0; - d.y = y; - } - function d3_layout_stackOrderDefault(data) { - return d3.range(data.length); - } - function d3_layout_stackOffsetZero(data) { - var j = -1, m = data[0].length, y0 = []; - while (++j < m) y0[j] = 0; - return y0; - } - function d3_layout_stackMaxIndex(array) { - var i = 1, j = 0, v = array[0][1], k, n = array.length; - for (; i < n; ++i) { - if ((k = array[i][1]) > v) { - j = i; - v = k; - } - } - return j; - } - function d3_layout_stackReduceSum(d) { - return d.reduce(d3_layout_stackSum, 0); - } - function d3_layout_stackSum(p, d) { - return p + d[1]; - } - function d3_layout_histogramBinSturges(range, values) { - return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); - } - function d3_layout_histogramBinFixed(range, n) { - var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; - while (++x <= n) f[x] = m * x + b; - return f; - } - function d3_layout_histogramRange(values) { - return [ d3.min(values), d3.max(values) ]; - } - function d3_layout_hierarchyRebind(object, hierarchy) { - d3.rebind(object, hierarchy, "sort", "children", "value"); - object.links = d3_layout_hierarchyLinks; - object.nodes = function(d) { - d3_layout_hierarchyInline = true; - return (object.nodes = object)(d); - }; - return object; - } - function d3_layout_hierarchyChildren(d) { - return d.children; - } - function d3_layout_hierarchyValue(d) { - return d.value; - } - function d3_layout_hierarchySort(a, b) { - return b.value - a.value; - } - function d3_layout_hierarchyLinks(nodes) { - return d3.merge(nodes.map(function(parent) { - return (parent.children || []).map(function(child) { - return { - source: parent, - target: child - }; - }); - })); - } - function d3_layout_packSort(a, b) { - return a.value - b.value; - } - function d3_layout_packInsert(a, b) { - var c = a._pack_next; - a._pack_next = b; - b._pack_prev = a; - b._pack_next = c; - c._pack_prev = b; - } - function d3_layout_packSplice(a, b) { - a._pack_next = b; - b._pack_prev = a; - } - function d3_layout_packIntersects(a, b) { - var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; - return dr * dr - dx * dx - dy * dy > .001; - } - function d3_layout_packSiblings(node) { - function bound(node) { - xMin = Math.min(node.x - node.r, xMin); - xMax = Math.max(node.x + node.r, xMax); - yMin = Math.min(node.y - node.r, yMin); - yMax = Math.max(node.y + node.r, yMax); - } - if (!(nodes = node.children) || !(n = nodes.length)) return; - var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; - nodes.forEach(d3_layout_packLink); - a = nodes[0]; - a.x = -a.r; - a.y = 0; - bound(a); - if (n > 1) { - b = nodes[1]; - b.x = b.r; - b.y = 0; - bound(b); - if (n > 2) { - c = nodes[2]; - d3_layout_packPlace(a, b, c); - bound(c); - d3_layout_packInsert(a, c); - a._pack_prev = c; - d3_layout_packInsert(c, b); - b = a._pack_next; - for (i = 3; i < n; i++) { - d3_layout_packPlace(a, b, c = nodes[i]); - var isect = 0, s1 = 1, s2 = 1; - for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { - if (d3_layout_packIntersects(j, c)) { - isect = 1; - break; - } - } - if (isect == 1) { - for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { - if (d3_layout_packIntersects(k, c)) { - break; - } - } - } - if (isect) { - if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); - i--; - } else { - d3_layout_packInsert(a, c); - b = c; - bound(c); - } - } - } - } - var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; - for (i = 0; i < n; i++) { - c = nodes[i]; - c.x -= cx; - c.y -= cy; - cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); - } - node.r = cr; - nodes.forEach(d3_layout_packUnlink); - } - function d3_layout_packLink(node) { - node._pack_next = node._pack_prev = node; - } - function d3_layout_packUnlink(node) { - delete node._pack_next; - delete node._pack_prev; - } - function d3_layout_packTransform(node, x, y, k) { - var children = node.children; - node.x = x += k * node.x; - node.y = y += k * node.y; - node.r *= k; - if (children) { - var i = -1, n = children.length; - while (++i < n) d3_layout_packTransform(children[i], x, y, k); - } - } - function d3_layout_packPlace(a, b, c) { - var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; - if (db && (dx || dy)) { - var da = b.r + c.r, dc = dx * dx + dy * dy; - da *= da; - db *= db; - var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); - c.x = a.x + x * dx + y * dy; - c.y = a.y + x * dy - y * dx; - } else { - c.x = a.x + db; - c.y = a.y; - } - } - function d3_layout_clusterY(children) { - return 1 + d3.max(children, function(child) { - return child.y; - }); - } - function d3_layout_clusterX(children) { - return children.reduce(function(x, child) { - return x + child.x; - }, 0) / children.length; - } - function d3_layout_clusterLeft(node) { - var children = node.children; - return children && children.length ? d3_layout_clusterLeft(children[0]) : node; - } - function d3_layout_clusterRight(node) { - var children = node.children, n; - return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; - } - function d3_layout_treeSeparation(a, b) { - return a.parent == b.parent ? 1 : 2; - } - function d3_layout_treeLeft(node) { - var children = node.children; - return children && children.length ? children[0] : node._tree.thread; - } - function d3_layout_treeRight(node) { - var children = node.children, n; - return children && (n = children.length) ? children[n - 1] : node._tree.thread; - } - function d3_layout_treeSearch(node, compare) { - var children = node.children; - if (children && (n = children.length)) { - var child, n, i = -1; - while (++i < n) { - if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { - node = child; - } - } - } - return node; - } - function d3_layout_treeRightmost(a, b) { - return a.x - b.x; - } - function d3_layout_treeLeftmost(a, b) { - return b.x - a.x; - } - function d3_layout_treeDeepest(a, b) { - return a.depth - b.depth; - } - function d3_layout_treeVisitAfter(node, callback) { - function visit(node, previousSibling) { - var children = node.children; - if (children && (n = children.length)) { - var child, previousChild = null, i = -1, n; - while (++i < n) { - child = children[i]; - visit(child, previousChild); - previousChild = child; - } - } - callback(node, previousSibling); - } - visit(node, null); - } - function d3_layout_treeShift(node) { - var shift = 0, change = 0, children = node.children, i = children.length, child; - while (--i >= 0) { - child = children[i]._tree; - child.prelim += shift; - child.mod += shift; - shift += child.shift + (change += child.change); - } - } - function d3_layout_treeMove(ancestor, node, shift) { - ancestor = ancestor._tree; - node = node._tree; - var change = shift / (node.number - ancestor.number); - ancestor.change += change; - node.change -= change; - node.shift += shift; - node.prelim += shift; - node.mod += shift; - } - function d3_layout_treeAncestor(vim, node, ancestor) { - return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor; - } - function d3_layout_treemapPadNull(node) { - return { - x: node.x, - y: node.y, - dx: node.dx, - dy: node.dy - }; - } - function d3_layout_treemapPad(node, padding) { - var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; - if (dx < 0) { - x += dx / 2; - dx = 0; - } - if (dy < 0) { - y += dy / 2; - dy = 0; - } - return { - x: x, - y: y, - dx: dx, - dy: dy - }; - } - function d3_dsv(delimiter, mimeType) { - function dsv(url, callback) { - d3.text(url, mimeType, function(text) { - callback(text && dsv.parse(text)); - }); - } - function formatRow(row) { - return row.map(formatValue).join(delimiter); - } - function formatValue(text) { - return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; - } - var reParse = new RegExp("\r\n|[" + delimiter + "\r\n]", "g"), reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); - dsv.parse = function(text) { - var header; - return dsv.parseRows(text, function(row, i) { - if (i) { - var o = {}, j = -1, m = header.length; - while (++j < m) o[header[j]] = row[j]; - return o; - } else { - header = row; - return null; - } - }); - }; - dsv.parseRows = function(text, f) { - function token() { - if (reParse.lastIndex >= text.length) return EOF; - if (eol) { - eol = false; - return EOL; - } - var j = reParse.lastIndex; - if (text.charCodeAt(j) === 34) { - var i = j; - while (i++ < text.length) { - if (text.charCodeAt(i) === 34) { - if (text.charCodeAt(i + 1) !== 34) break; - i++; - } - } - reParse.lastIndex = i + 2; - var c = text.charCodeAt(i + 1); - if (c === 13) { - eol = true; - if (text.charCodeAt(i + 2) === 10) reParse.lastIndex++; - } else if (c === 10) { - eol = true; - } - return text.substring(j + 1, i).replace(/""/g, '"'); - } - var m = reParse.exec(text); - if (m) { - eol = m[0].charCodeAt(0) !== delimiterCode; - return text.substring(j, m.index); - } - reParse.lastIndex = text.length; - return text.substring(j); - } - var EOL = {}, EOF = {}, rows = [], n = 0, t, eol; - reParse.lastIndex = 0; - while ((t = token()) !== EOF) { - var a = []; - while (t !== EOL && t !== EOF) { - a.push(t); - t = token(); - } - if (f && !(a = f(a, n++))) continue; - rows.push(a); - } - return rows; - }; - dsv.format = function(rows) { - return rows.map(formatRow).join("\n"); - }; - return dsv; - } - function d3_geo_type(types, defaultValue) { - return function(object) { - return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue; - }; - } - function d3_path_circle(radius) { - return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z"; - } - function d3_geo_bounds(o, f) { - if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f); - } - function d3_geo_boundsFeature(o, f) { - d3_geo_bounds(o.geometry, f); - } - function d3_geo_boundsFeatureCollection(o, f) { - for (var a = o.features, i = 0, n = a.length; i < n; i++) { - d3_geo_bounds(a[i].geometry, f); - } - } - function d3_geo_boundsGeometryCollection(o, f) { - for (var a = o.geometries, i = 0, n = a.length; i < n; i++) { - d3_geo_bounds(a[i], f); - } - } - function d3_geo_boundsLineString(o, f) { - for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { - f.apply(null, a[i]); - } - } - function d3_geo_boundsMultiLineString(o, f) { - for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { - for (var b = a[i], j = 0, m = b.length; j < m; j++) { - f.apply(null, b[j]); - } - } - } - function d3_geo_boundsMultiPolygon(o, f) { - for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { - for (var b = a[i][0], j = 0, m = b.length; j < m; j++) { - f.apply(null, b[j]); - } - } - } - function d3_geo_boundsPoint(o, f) { - f.apply(null, o.coordinates); - } - function d3_geo_boundsPolygon(o, f) { - for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) { - f.apply(null, a[i]); - } - } - function d3_geo_greatArcSource(d) { - return d.source; - } - function d3_geo_greatArcTarget(d) { - return d.target; - } - function d3_geo_greatArcInterpolator() { - function interpolate(t) { - var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; - return [ Math.atan2(y, x) / d3_geo_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians ]; - } - var x0, y0, cy0, sy0, kx0, ky0, x1, y1, cy1, sy1, kx1, ky1, d, k; - interpolate.distance = function() { - if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0))))); - return d; - }; - interpolate.source = function(_) { - var cx0 = Math.cos(x0 = _[0] * d3_geo_radians), sx0 = Math.sin(x0); - cy0 = Math.cos(y0 = _[1] * d3_geo_radians); - sy0 = Math.sin(y0); - kx0 = cy0 * cx0; - ky0 = cy0 * sx0; - d = null; - return interpolate; - }; - interpolate.target = function(_) { - var cx1 = Math.cos(x1 = _[0] * d3_geo_radians), sx1 = Math.sin(x1); - cy1 = Math.cos(y1 = _[1] * d3_geo_radians); - sy1 = Math.sin(y1); - kx1 = cy1 * cx1; - ky1 = cy1 * sx1; - d = null; - return interpolate; - }; - return interpolate; - } - function d3_geo_greatArcInterpolate(a, b) { - var i = d3_geo_greatArcInterpolator().source(a).target(b); - i.distance(); - return i; - } - function d3_geom_contourStart(grid) { - var x = 0, y = 0; - while (true) { - if (grid(x, y)) { - return [ x, y ]; - } - if (x === 0) { - x = y + 1; - y = 0; - } else { - x = x - 1; - y = y + 1; - } - } - } - function d3_geom_hullCCW(i1, i2, i3, v) { - var t, a, b, c, d, e, f; - t = v[i1]; - a = t[0]; - b = t[1]; - t = v[i2]; - c = t[0]; - d = t[1]; - t = v[i3]; - e = t[0]; - f = t[1]; - return (f - b) * (c - a) - (d - b) * (e - a) > 0; - } - function d3_geom_polygonInside(p, a, b) { - return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); - } - function d3_geom_polygonIntersect(c, d, a, b) { - var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], x13 = x1 - x3, x21 = x2 - x1, x43 = x4 - x3, y13 = y1 - y3, y21 = y2 - y1, y43 = y4 - y3, ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); - return [ x1 + ua * x21, y1 + ua * y21 ]; - } - function d3_voronoi_tessellate(vertices, callback) { - var Sites = { - list: vertices.map(function(v, i) { - return { - index: i, - x: v[0], - y: v[1] - }; - }).sort(function(a, b) { - return a.y < b.y ? -1 : a.y > b.y ? 1 : a.x < b.x ? -1 : a.x > b.x ? 1 : 0; - }), - bottomSite: null - }; - var EdgeList = { - list: [], - leftEnd: null, - rightEnd: null, - init: function() { - EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); - EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); - EdgeList.leftEnd.r = EdgeList.rightEnd; - EdgeList.rightEnd.l = EdgeList.leftEnd; - EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); - }, - createHalfEdge: function(edge, side) { - return { - edge: edge, - side: side, - vertex: null, - l: null, - r: null - }; - }, - insert: function(lb, he) { - he.l = lb; - he.r = lb.r; - lb.r.l = he; - lb.r = he; - }, - leftBound: function(p) { - var he = EdgeList.leftEnd; - do { - he = he.r; - } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); - he = he.l; - return he; - }, - del: function(he) { - he.l.r = he.r; - he.r.l = he.l; - he.edge = null; - }, - right: function(he) { - return he.r; - }, - left: function(he) { - return he.l; - }, - leftRegion: function(he) { - return he.edge == null ? Sites.bottomSite : he.edge.region[he.side]; - }, - rightRegion: function(he) { - return he.edge == null ? Sites.bottomSite : he.edge.region[d3_voronoi_opposite[he.side]]; - } - }; - var Geom = { - bisect: function(s1, s2) { - var newEdge = { - region: { - l: s1, - r: s2 - }, - ep: { - l: null, - r: null - } - }; - var dx = s2.x - s1.x, dy = s2.y - s1.y, adx = dx > 0 ? dx : -dx, ady = dy > 0 ? dy : -dy; - newEdge.c = s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * .5; - if (adx > ady) { - newEdge.a = 1; - newEdge.b = dy / dx; - newEdge.c /= dx; - } else { - newEdge.b = 1; - newEdge.a = dx / dy; - newEdge.c /= dy; - } - return newEdge; - }, - intersect: function(el1, el2) { - var e1 = el1.edge, e2 = el2.edge; - if (!e1 || !e2 || e1.region.r == e2.region.r) { - return null; - } - var d = e1.a * e2.b - e1.b * e2.a; - if (Math.abs(d) < 1e-10) { - return null; - } - var xint = (e1.c * e2.b - e2.c * e1.b) / d, yint = (e2.c * e1.a - e1.c * e2.a) / d, e1r = e1.region.r, e2r = e2.region.r, el, e; - if (e1r.y < e2r.y || e1r.y == e2r.y && e1r.x < e2r.x) { - el = el1; - e = e1; - } else { - el = el2; - e = e2; - } - var rightOfSite = xint >= e.region.r.x; - if (rightOfSite && el.side === "l" || !rightOfSite && el.side === "r") { - return null; - } - return { - x: xint, - y: yint - }; - }, - rightOf: function(he, p) { - var e = he.edge, topsite = e.region.r, rightOfSite = p.x > topsite.x; - if (rightOfSite && he.side === "l") { - return 1; - } - if (!rightOfSite && he.side === "r") { - return 0; - } - if (e.a === 1) { - var dyp = p.y - topsite.y, dxp = p.x - topsite.x, fast = 0, above = 0; - if (!rightOfSite && e.b < 0 || rightOfSite && e.b >= 0) { - above = fast = dyp >= e.b * dxp; - } else { - above = p.x + p.y * e.b > e.c; - if (e.b < 0) { - above = !above; - } - if (!above) { - fast = 1; - } - } - if (!fast) { - var dxs = topsite.x - e.region.l.x; - above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b); - if (e.b < 0) { - above = !above; - } - } - } else { - var yl = e.c - e.a * p.x, t1 = p.y - yl, t2 = p.x - topsite.x, t3 = yl - topsite.y; - above = t1 * t1 > t2 * t2 + t3 * t3; - } - return he.side === "l" ? above : !above; - }, - endPoint: function(edge, side, site) { - edge.ep[side] = site; - if (!edge.ep[d3_voronoi_opposite[side]]) return; - callback(edge); - }, - distance: function(s, t) { - var dx = s.x - t.x, dy = s.y - t.y; - return Math.sqrt(dx * dx + dy * dy); - } - }; - var EventQueue = { - list: [], - insert: function(he, site, offset) { - he.vertex = site; - he.ystar = site.y + offset; - for (var i = 0, list = EventQueue.list, l = list.length; i < l; i++) { - var next = list[i]; - if (he.ystar > next.ystar || he.ystar == next.ystar && site.x > next.vertex.x) { - continue; - } else { - break; - } - } - list.splice(i, 0, he); - }, - del: function(he) { - for (var i = 0, ls = EventQueue.list, l = ls.length; i < l && ls[i] != he; ++i) {} - ls.splice(i, 1); - }, - empty: function() { - return EventQueue.list.length === 0; - }, - nextEvent: function(he) { - for (var i = 0, ls = EventQueue.list, l = ls.length; i < l; ++i) { - if (ls[i] == he) return ls[i + 1]; - } - return null; - }, - min: function() { - var elem = EventQueue.list[0]; - return { - x: elem.vertex.x, - y: elem.ystar - }; - }, - extractMin: function() { - return EventQueue.list.shift(); - } - }; - EdgeList.init(); - Sites.bottomSite = Sites.list.shift(); - var newSite = Sites.list.shift(), newIntStar; - var lbnd, rbnd, llbnd, rrbnd, bisector; - var bot, top, temp, p, v; - var e, pm; - while (true) { - if (!EventQueue.empty()) { - newIntStar = EventQueue.min(); - } - if (newSite && (EventQueue.empty() || newSite.y < newIntStar.y || newSite.y == newIntStar.y && newSite.x < newIntStar.x)) { - lbnd = EdgeList.leftBound(newSite); - rbnd = EdgeList.right(lbnd); - bot = EdgeList.rightRegion(lbnd); - e = Geom.bisect(bot, newSite); - bisector = EdgeList.createHalfEdge(e, "l"); - EdgeList.insert(lbnd, bisector); - p = Geom.intersect(lbnd, bisector); - if (p) { - EventQueue.del(lbnd); - EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); - } - lbnd = bisector; - bisector = EdgeList.createHalfEdge(e, "r"); - EdgeList.insert(lbnd, bisector); - p = Geom.intersect(bisector, rbnd); - if (p) { - EventQueue.insert(bisector, p, Geom.distance(p, newSite)); - } - newSite = Sites.list.shift(); - } else if (!EventQueue.empty()) { - lbnd = EventQueue.extractMin(); - llbnd = EdgeList.left(lbnd); - rbnd = EdgeList.right(lbnd); - rrbnd = EdgeList.right(rbnd); - bot = EdgeList.leftRegion(lbnd); - top = EdgeList.rightRegion(rbnd); - v = lbnd.vertex; - Geom.endPoint(lbnd.edge, lbnd.side, v); - Geom.endPoint(rbnd.edge, rbnd.side, v); - EdgeList.del(lbnd); - EventQueue.del(rbnd); - EdgeList.del(rbnd); - pm = "l"; - if (bot.y > top.y) { - temp = bot; - bot = top; - top = temp; - pm = "r"; - } - e = Geom.bisect(bot, top); - bisector = EdgeList.createHalfEdge(e, pm); - EdgeList.insert(llbnd, bisector); - Geom.endPoint(e, d3_voronoi_opposite[pm], v); - p = Geom.intersect(llbnd, bisector); - if (p) { - EventQueue.del(llbnd); - EventQueue.insert(llbnd, p, Geom.distance(p, bot)); - } - p = Geom.intersect(bisector, rrbnd); - if (p) { - EventQueue.insert(bisector, p, Geom.distance(p, bot)); - } - } else { - break; - } - } - for (lbnd = EdgeList.right(EdgeList.leftEnd); lbnd != EdgeList.rightEnd; lbnd = EdgeList.right(lbnd)) { - callback(lbnd.edge); - } - } - function d3_geom_quadtreeNode() { - return { - leaf: true, - nodes: [], - point: null - }; - } - function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { - if (!f(node, x1, y1, x2, y2)) { - var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; - if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); - if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); - if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); - if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); - } - } - function d3_geom_quadtreePoint(p) { - return { - x: p[0], - y: p[1] - }; - } - function d3_time_utc() { - this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); - } - function d3_time_formatAbbreviate(name) { - return name.substring(0, 3); - } - function d3_time_parse(date, template, string, j) { - var c, p, i = 0, n = template.length, m = string.length; - while (i < n) { - if (j >= m) return -1; - c = template.charCodeAt(i++); - if (c == 37) { - p = d3_time_parsers[template.charAt(i++)]; - if (!p || (j = p(date, string, j)) < 0) return -1; - } else if (c != string.charCodeAt(j++)) { - return -1; - } - } - return j; - } - function d3_time_formatRe(names) { - return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); - } - function d3_time_formatLookup(names) { - var map = new d3_Map, i = -1, n = names.length; - while (++i < n) map.set(names[i].toLowerCase(), i); - return map; - } - function d3_time_parseWeekdayAbbrev(date, string, i) { - d3_time_dayAbbrevRe.lastIndex = 0; - var n = d3_time_dayAbbrevRe.exec(string.substring(i)); - return n ? i += n[0].length : -1; - } - function d3_time_parseWeekday(date, string, i) { - d3_time_dayRe.lastIndex = 0; - var n = d3_time_dayRe.exec(string.substring(i)); - return n ? i += n[0].length : -1; - } - function d3_time_parseMonthAbbrev(date, string, i) { - d3_time_monthAbbrevRe.lastIndex = 0; - var n = d3_time_monthAbbrevRe.exec(string.substring(i)); - return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; - } - function d3_time_parseMonth(date, string, i) { - d3_time_monthRe.lastIndex = 0; - var n = d3_time_monthRe.exec(string.substring(i)); - return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; - } - function d3_time_parseLocaleFull(date, string, i) { - return d3_time_parse(date, d3_time_formats.c.toString(), string, i); - } - function d3_time_parseLocaleDate(date, string, i) { - return d3_time_parse(date, d3_time_formats.x.toString(), string, i); - } - function d3_time_parseLocaleTime(date, string, i) { - return d3_time_parse(date, d3_time_formats.X.toString(), string, i); - } - function d3_time_parseFullYear(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 4)); - return n ? (date.y = +n[0], i += n[0].length) : -1; - } - function d3_time_parseYear(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.y = d3_time_expandYear(+n[0]), i += n[0].length) : -1; - } - function d3_time_expandYear(d) { - return d + (d > 68 ? 1900 : 2e3); - } - function d3_time_parseMonthNumber(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.m = n[0] - 1, i += n[0].length) : -1; - } - function d3_time_parseDay(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.d = +n[0], i += n[0].length) : -1; - } - function d3_time_parseHour24(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.H = +n[0], i += n[0].length) : -1; - } - function d3_time_parseMinutes(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.M = +n[0], i += n[0].length) : -1; - } - function d3_time_parseSeconds(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.S = +n[0], i += n[0].length) : -1; - } - function d3_time_parseMilliseconds(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 3)); - return n ? (date.L = +n[0], i += n[0].length) : -1; - } - function d3_time_parseAmPm(date, string, i) { - var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); - return n == null ? -1 : (date.p = n, i); - } - function d3_time_zone(d) { - var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(Math.abs(z) / 60), zm = Math.abs(z) % 60; - return zs + d3_time_zfill2(zh) + d3_time_zfill2(zm); - } - function d3_time_formatIsoNative(date) { - return date.toISOString(); - } - function d3_time_interval(local, step, number) { - function round(date) { - var d0 = local(date), d1 = offset(d0, 1); - return date - d0 < d1 - date ? d0 : d1; - } - function ceil(date) { - step(date = local(new d3_time(date - 1)), 1); - return date; - } - function offset(date, k) { - step(date = new d3_time(+date), k); - return date; - } - function range(t0, t1, dt) { - var time = ceil(t0), times = []; - if (dt > 1) { - while (time < t1) { - if (!(number(time) % dt)) times.push(new Date(+time)); - step(time, 1); - } - } else { - while (time < t1) times.push(new Date(+time)), step(time, 1); - } - return times; - } - function range_utc(t0, t1, dt) { - try { - d3_time = d3_time_utc; - var utc = new d3_time_utc; - utc._ = t0; - return range(utc, t1, dt); - } finally { - d3_time = Date; - } - } - local.floor = local; - local.round = round; - local.ceil = ceil; - local.offset = offset; - local.range = range; - var utc = local.utc = d3_time_interval_utc(local); - utc.floor = utc; - utc.round = d3_time_interval_utc(round); - utc.ceil = d3_time_interval_utc(ceil); - utc.offset = d3_time_interval_utc(offset); - utc.range = range_utc; - return local; - } - function d3_time_interval_utc(method) { - return function(date, k) { - try { - d3_time = d3_time_utc; - var utc = new d3_time_utc; - utc._ = date; - return method(utc, k)._; - } finally { - d3_time = Date; - } - }; - } - function d3_time_scale(linear, methods, format) { - function scale(x) { - return linear(x); - } - scale.invert = function(x) { - return d3_time_scaleDate(linear.invert(x)); - }; - scale.domain = function(x) { - if (!arguments.length) return linear.domain().map(d3_time_scaleDate); - linear.domain(x); - return scale; - }; - scale.nice = function(m) { - return scale.domain(d3_scale_nice(scale.domain(), function() { - return m; - })); - }; - scale.ticks = function(m, k) { - var extent = d3_time_scaleExtent(scale.domain()); - if (typeof m !== "function") { - var span = extent[1] - extent[0], target = span / m, i = d3.bisect(d3_time_scaleSteps, target); - if (i == d3_time_scaleSteps.length) return methods.year(extent, m); - if (!i) return linear.ticks(m).map(d3_time_scaleDate); - if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; - m = methods[i]; - k = m[1]; - m = m[0].range; - } - return m(extent[0], new Date(+extent[1] + 1), k); - }; - scale.tickFormat = function() { - return format; - }; - scale.copy = function() { - return d3_time_scale(linear.copy(), methods, format); - }; - return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); - } - function d3_time_scaleExtent(domain) { - var start = domain[0], stop = domain[domain.length - 1]; - return start < stop ? [ start, stop ] : [ stop, start ]; - } - function d3_time_scaleDate(t) { - return new Date(t); - } - function d3_time_scaleFormat(formats) { - return function(date) { - var i = formats.length - 1, f = formats[i]; - while (!f[1](date)) f = formats[--i]; - return f[0](date); - }; - } - function d3_time_scaleSetYear(y) { - var d = new Date(y, 0, 1); - d.setFullYear(y); - return d; - } - function d3_time_scaleGetYear(d) { - var y = d.getFullYear(), d0 = d3_time_scaleSetYear(y), d1 = d3_time_scaleSetYear(y + 1); - return y + (d - d0) / (d1 - d0); - } - function d3_time_scaleUTCSetYear(y) { - var d = new Date(Date.UTC(y, 0, 1)); - d.setUTCFullYear(y); - return d; - } - function d3_time_scaleUTCGetYear(d) { - var y = d.getUTCFullYear(), d0 = d3_time_scaleUTCSetYear(y), d1 = d3_time_scaleUTCSetYear(y + 1); - return y + (d - d0) / (d1 - d0); - } - if (!Date.now) Date.now = function() { - return +(new Date); - }; - try { - document.createElement("div").style.setProperty("opacity", 0, ""); - } catch (error) { - var d3_style_prototype = CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; - d3_style_prototype.setProperty = function(name, value, priority) { - d3_style_setProperty.call(this, name, value + "", priority); - }; - } - d3 = { - version: "2.10.1" - }; - var d3_array = d3_arraySlice; - try { - d3_array(document.documentElement.childNodes)[0].nodeType; - } catch (e) { - d3_array = d3_arrayCopy; - } - var d3_arraySubclass = [].__proto__ ? function(array, prototype) { - array.__proto__ = prototype; - } : function(array, prototype) { - for (var property in prototype) array[property] = prototype[property]; - }; - d3.map = function(object) { - var map = new d3_Map; - for (var key in object) map.set(key, object[key]); - return map; - }; - d3_class(d3_Map, { - has: function(key) { - return d3_map_prefix + key in this; - }, - get: function(key) { - return this[d3_map_prefix + key]; - }, - set: function(key, value) { - return this[d3_map_prefix + key] = value; - }, - remove: function(key) { - key = d3_map_prefix + key; - return key in this && delete this[key]; - }, - keys: function() { - var keys = []; - this.forEach(function(key) { - keys.push(key); - }); - return keys; - }, - values: function() { - var values = []; - this.forEach(function(key, value) { - values.push(value); - }); - return values; - }, - entries: function() { - var entries = []; - this.forEach(function(key, value) { - entries.push({ - key: key, - value: value - }); - }); - return entries; - }, - forEach: function(f) { - for (var key in this) { - if (key.charCodeAt(0) === d3_map_prefixCode) { - f.call(this, key.substring(1), this[key]); - } - } - } - }); - var d3_map_prefix = "\0", d3_map_prefixCode = d3_map_prefix.charCodeAt(0); - d3.functor = d3_functor; - d3.rebind = function(target, source) { - var i = 1, n = arguments.length, method; - while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); - return target; - }; - d3.ascending = function(a, b) { - return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; - }; - d3.descending = function(a, b) { - return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; - }; - d3.mean = function(array, f) { - var n = array.length, a, m = 0, i = -1, j = 0; - if (arguments.length === 1) { - while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; - } else { - while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; - } - return j ? m : undefined; - }; - d3.median = function(array, f) { - if (arguments.length > 1) array = array.map(f); - array = array.filter(d3_number); - return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; - }; - d3.min = function(array, f) { - var i = -1, n = array.length, a, b; - if (arguments.length === 1) { - while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; - while (++i < n) if ((b = array[i]) != null && a > b) a = b; - } else { - while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; - while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; - } - return a; - }; - d3.max = function(array, f) { - var i = -1, n = array.length, a, b; - if (arguments.length === 1) { - while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; - while (++i < n) if ((b = array[i]) != null && b > a) a = b; - } else { - while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; - while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; - } - return a; - }; - d3.extent = function(array, f) { - var i = -1, n = array.length, a, b, c; - if (arguments.length === 1) { - while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; - while (++i < n) if ((b = array[i]) != null) { - if (a > b) a = b; - if (c < b) c = b; - } - } else { - while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; - while (++i < n) if ((b = f.call(array, array[i], i)) != null) { - if (a > b) a = b; - if (c < b) c = b; - } - } - return [ a, c ]; - }; - d3.random = { - normal: function(µ, σ) { - var n = arguments.length; - if (n < 2) σ = 1; - if (n < 1) µ = 0; - return function() { - var x, y, r; - do { - x = Math.random() * 2 - 1; - y = Math.random() * 2 - 1; - r = x * x + y * y; - } while (!r || r > 1); - return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); - }; - }, - logNormal: function(µ, σ) { - var n = arguments.length; - if (n < 2) σ = 1; - if (n < 1) µ = 0; - var random = d3.random.normal(); - return function() { - return Math.exp(µ + σ * random()); - }; - }, - irwinHall: function(m) { - return function() { - for (var s = 0, j = 0; j < m; j++) s += Math.random(); - return s / m; - }; - } - }; - d3.sum = function(array, f) { - var s = 0, n = array.length, a, i = -1; - if (arguments.length === 1) { - while (++i < n) if (!isNaN(a = +array[i])) s += a; - } else { - while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; - } - return s; - }; - d3.quantile = function(values, p) { - var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h; - return e ? v + e * (values[h] - v) : v; - }; - d3.transpose = function(matrix) { - return d3.zip.apply(d3, matrix); - }; - d3.zip = function() { - if (!(n = arguments.length)) return []; - for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) { - for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) { - zip[j] = arguments[j][i]; - } - } - return zips; - }; - d3.bisector = function(f) { - return { - left: function(a, x, lo, hi) { - if (arguments.length < 3) lo = 0; - if (arguments.length < 4) hi = a.length; - while (lo < hi) { - var mid = lo + hi >>> 1; - if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid; - } - return lo; - }, - right: function(a, x, lo, hi) { - if (arguments.length < 3) lo = 0; - if (arguments.length < 4) hi = a.length; - while (lo < hi) { - var mid = lo + hi >>> 1; - if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1; - } - return lo; - } - }; - }; - var d3_bisector = d3.bisector(function(d) { - return d; - }); - d3.bisectLeft = d3_bisector.left; - d3.bisect = d3.bisectRight = d3_bisector.right; - d3.first = function(array, f) { - var i = 0, n = array.length, a = array[0], b; - if (arguments.length === 1) f = d3.ascending; - while (++i < n) { - if (f.call(array, a, b = array[i]) > 0) { - a = b; - } - } - return a; - }; - d3.last = function(array, f) { - var i = 0, n = array.length, a = array[0], b; - if (arguments.length === 1) f = d3.ascending; - while (++i < n) { - if (f.call(array, a, b = array[i]) <= 0) { - a = b; - } - } - return a; - }; - d3.nest = function() { - function map(array, depth) { - if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; - var i = -1, n = array.length, key = keys[depth++], keyValue, object, valuesByKey = new d3_Map, values, o = {}; - while (++i < n) { - if (values = valuesByKey.get(keyValue = key(object = array[i]))) { - values.push(object); - } else { - valuesByKey.set(keyValue, [ object ]); - } - } - valuesByKey.forEach(function(keyValue, values) { - o[keyValue] = map(values, depth); - }); - return o; - } - function entries(map, depth) { - if (depth >= keys.length) return map; - var a = [], sortKey = sortKeys[depth++], key; - for (key in map) { - a.push({ - key: key, - values: entries(map[key], depth) - }); - } - if (sortKey) a.sort(function(a, b) { - return sortKey(a.key, b.key); - }); - return a; - } - var nest = {}, keys = [], sortKeys = [], sortValues, rollup; - nest.map = function(array) { - return map(array, 0); - }; - nest.entries = function(array) { - return entries(map(array, 0), 0); - }; - nest.key = function(d) { - keys.push(d); - return nest; - }; - nest.sortKeys = function(order) { - sortKeys[keys.length - 1] = order; - return nest; - }; - nest.sortValues = function(order) { - sortValues = order; - return nest; - }; - nest.rollup = function(f) { - rollup = f; - return nest; - }; - return nest; - }; - d3.keys = function(map) { - var keys = []; - for (var key in map) keys.push(key); - return keys; - }; - d3.values = function(map) { - var values = []; - for (var key in map) values.push(map[key]); - return values; - }; - d3.entries = function(map) { - var entries = []; - for (var key in map) entries.push({ - key: key, - value: map[key] - }); - return entries; - }; - d3.permute = function(array, indexes) { - var permutes = [], i = -1, n = indexes.length; - while (++i < n) permutes[i] = array[indexes[i]]; - return permutes; - }; - d3.merge = function(arrays) { - return Array.prototype.concat.apply([], arrays); - }; - d3.split = function(array, f) { - var arrays = [], values = [], value, i = -1, n = array.length; - if (arguments.length < 2) f = d3_splitter; - while (++i < n) { - if (f.call(values, value = array[i], i)) { - values = []; - } else { - if (!values.length) arrays.push(values); - values.push(value); - } - } - return arrays; - }; - d3.range = function(start, stop, step) { - if (arguments.length < 3) { - step = 1; - if (arguments.length < 2) { - stop = start; - start = 0; - } - } - if ((stop - start) / step === Infinity) throw new Error("infinite range"); - var range = [], k = d3_range_integerScale(Math.abs(step)), i = -1, j; - start *= k, stop *= k, step *= k; - if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); - return range; - }; - d3.requote = function(s) { - return s.replace(d3_requote_re, "\\$&"); - }; - var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; - d3.round = function(x, n) { - return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); - }; - d3.xhr = function(url, mime, callback) { - var req = new XMLHttpRequest; - if (arguments.length < 3) callback = mime, mime = null; else if (mime && req.overrideMimeType) req.overrideMimeType(mime); - req.open("GET", url, true); - if (mime) req.setRequestHeader("Accept", mime); - req.onreadystatechange = function() { - if (req.readyState === 4) { - var s = req.status; - callback(!s && req.response || s >= 200 && s < 300 || s === 304 ? req : null); - } - }; - req.send(null); - }; - d3.text = function(url, mime, callback) { - function ready(req) { - callback(req && req.responseText); - } - if (arguments.length < 3) { - callback = mime; - mime = null; - } - d3.xhr(url, mime, ready); - }; - d3.json = function(url, callback) { - d3.text(url, "application/json", function(text) { - callback(text ? JSON.parse(text) : null); - }); - }; - d3.html = function(url, callback) { - d3.text(url, "text/html", function(text) { - if (text != null) { - var range = document.createRange(); - range.selectNode(document.body); - text = range.createContextualFragment(text); - } - callback(text); - }); - }; - d3.xml = function(url, mime, callback) { - function ready(req) { - callback(req && req.responseXML); - } - if (arguments.length < 3) { - callback = mime; - mime = null; - } - d3.xhr(url, mime, ready); - }; - var d3_nsPrefix = { - svg: "http://www.w3.org/2000/svg", - xhtml: "http://www.w3.org/1999/xhtml", - xlink: "http://www.w3.org/1999/xlink", - xml: "http://www.w3.org/XML/1998/namespace", - xmlns: "http://www.w3.org/2000/xmlns/" - }; - d3.ns = { - prefix: d3_nsPrefix, - qualify: function(name) { - var i = name.indexOf(":"), prefix = name; - if (i >= 0) { - prefix = name.substring(0, i); - name = name.substring(i + 1); - } - return d3_nsPrefix.hasOwnProperty(prefix) ? { - space: d3_nsPrefix[prefix], - local: name - } : name; - } - }; - d3.dispatch = function() { - var dispatch = new d3_dispatch, i = -1, n = arguments.length; - while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); - return dispatch; - }; - d3_dispatch.prototype.on = function(type, listener) { - var i = type.indexOf("."), name = ""; - if (i > 0) { - name = type.substring(i + 1); - type = type.substring(0, i); - } - return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); - }; - d3.format = function(specifier) { - var match = d3_format_re.exec(specifier), fill = match[1] || " ", sign = match[3] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false; - if (precision) precision = +precision.substring(1); - if (zfill) { - fill = "0"; - if (comma) width -= Math.floor((width - 1) / 4); - } - switch (type) { - case "n": - comma = true; - type = "g"; - break; - case "%": - scale = 100; - suffix = "%"; - type = "f"; - break; - case "p": - scale = 100; - suffix = "%"; - type = "r"; - break; - case "d": - integer = true; - precision = 0; - break; - case "s": - scale = -1; - type = "r"; - break; - } - if (type == "r" && !precision) type = "g"; - type = d3_format_types.get(type) || d3_format_typeDefault; - return function(value) { - if (integer && value % 1) return ""; - var negative = value < 0 && (value = -value) ? "-" : sign; - if (scale < 0) { - var prefix = d3.formatPrefix(value, precision); - value = prefix.scale(value); - suffix = prefix.symbol; - } else { - value *= scale; - } - value = type(value, precision); - if (zfill) { - var length = value.length + negative.length; - if (length < width) value = (new Array(width - length + 1)).join(fill) + value; - if (comma) value = d3_format_group(value); - value = negative + value; - } else { - if (comma) value = d3_format_group(value); - value = negative + value; - var length = value.length; - if (length < width) value = (new Array(width - length + 1)).join(fill) + value; - } - return value + suffix; - }; - }; - var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; - var d3_format_types = d3.map({ - g: function(x, p) { - return x.toPrecision(p); - }, - e: function(x, p) { - return x.toExponential(p); - }, - f: function(x, p) { - return x.toFixed(p); - }, - r: function(x, p) { - return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); - } - }); - var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "μ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); - d3.formatPrefix = function(value, precision) { - var i = 0; - if (value) { - if (value < 0) value *= -1; - if (precision) value = d3.round(value, d3_format_precision(value, precision)); - i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); - i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); - } - return d3_formatPrefixes[8 + i / 3]; - }; - var d3_ease_quad = d3_ease_poly(2), d3_ease_cubic = d3_ease_poly(3), d3_ease_default = function() { - return d3_ease_identity; - }; - var d3_ease = d3.map({ - linear: d3_ease_default, - poly: d3_ease_poly, - quad: function() { - return d3_ease_quad; - }, - cubic: function() { - return d3_ease_cubic; - }, - sin: function() { - return d3_ease_sin; - }, - exp: function() { - return d3_ease_exp; - }, - circle: function() { - return d3_ease_circle; - }, - elastic: d3_ease_elastic, - back: d3_ease_back, - bounce: function() { - return d3_ease_bounce; - } - }); - var d3_ease_mode = d3.map({ - "in": d3_ease_identity, - out: d3_ease_reverse, - "in-out": d3_ease_reflect, - "out-in": function(f) { - return d3_ease_reflect(d3_ease_reverse(f)); - } - }); - d3.ease = function(name) { - var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; - t = d3_ease.get(t) || d3_ease_default; - m = d3_ease_mode.get(m) || d3_ease_identity; - return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); - }; - d3.event = null; - d3.transform = function(string) { - var g = document.createElementNS(d3.ns.prefix.svg, "g"); - return (d3.transform = function(string) { - g.setAttribute("transform", string); - var t = g.transform.baseVal.consolidate(); - return new d3_transform(t ? t.matrix : d3_transformIdentity); - })(string); - }; - d3_transform.prototype.toString = function() { - return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; - }; - var d3_transformDegrees = 180 / Math.PI, d3_transformIdentity = { - a: 1, - b: 0, - c: 0, - d: 1, - e: 0, - f: 0 - }; - d3.interpolate = function(a, b) { - var i = d3.interpolators.length, f; - while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; - return f; - }; - d3.interpolateNumber = function(a, b) { - b -= a; - return function(t) { - return a + b * t; - }; - }; - d3.interpolateRound = function(a, b) { - b -= a; - return function(t) { - return Math.round(a + b * t); - }; - }; - d3.interpolateString = function(a, b) { - var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o; - d3_interpolate_number.lastIndex = 0; - for (i = 0; m = d3_interpolate_number.exec(b); ++i) { - if (m.index) s.push(b.substring(s0, s1 = m.index)); - q.push({ - i: s.length, - x: m[0] - }); - s.push(null); - s0 = d3_interpolate_number.lastIndex; - } - if (s0 < b.length) s.push(b.substring(s0)); - for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { - o = q[i]; - if (o.x == m[0]) { - if (o.i) { - if (s[o.i + 1] == null) { - s[o.i - 1] += o.x; - s.splice(o.i, 1); - for (j = i + 1; j < n; ++j) q[j].i--; - } else { - s[o.i - 1] += o.x + s[o.i + 1]; - s.splice(o.i, 2); - for (j = i + 1; j < n; ++j) q[j].i -= 2; - } - } else { - if (s[o.i + 1] == null) { - s[o.i] = o.x; - } else { - s[o.i] = o.x + s[o.i + 1]; - s.splice(o.i + 1, 1); - for (j = i + 1; j < n; ++j) q[j].i--; - } - } - q.splice(i, 1); - n--; - i--; - } else { - o.x = d3.interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); - } - } - while (i < n) { - o = q.pop(); - if (s[o.i + 1] == null) { - s[o.i] = o.x; - } else { - s[o.i] = o.x + s[o.i + 1]; - s.splice(o.i + 1, 1); - } - n--; - } - if (s.length === 1) { - return s[0] == null ? q[0].x : function() { - return b; - }; - } - return function(t) { - for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }; - }; - d3.interpolateTransform = function(a, b) { - var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale; - if (ta[0] != tb[0] || ta[1] != tb[1]) { - s.push("translate(", null, ",", null, ")"); - q.push({ - i: 1, - x: d3.interpolateNumber(ta[0], tb[0]) - }, { - i: 3, - x: d3.interpolateNumber(ta[1], tb[1]) - }); - } else if (tb[0] || tb[1]) { - s.push("translate(" + tb + ")"); - } else { - s.push(""); - } - if (ra != rb) { - if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; - q.push({ - i: s.push(s.pop() + "rotate(", null, ")") - 2, - x: d3.interpolateNumber(ra, rb) - }); - } else if (rb) { - s.push(s.pop() + "rotate(" + rb + ")"); - } - if (wa != wb) { - q.push({ - i: s.push(s.pop() + "skewX(", null, ")") - 2, - x: d3.interpolateNumber(wa, wb) - }); - } else if (wb) { - s.push(s.pop() + "skewX(" + wb + ")"); - } - if (ka[0] != kb[0] || ka[1] != kb[1]) { - n = s.push(s.pop() + "scale(", null, ",", null, ")"); - q.push({ - i: n - 4, - x: d3.interpolateNumber(ka[0], kb[0]) - }, { - i: n - 2, - x: d3.interpolateNumber(ka[1], kb[1]) - }); - } else if (kb[0] != 1 || kb[1] != 1) { - s.push(s.pop() + "scale(" + kb + ")"); - } - n = q.length; - return function(t) { - var i = -1, o; - while (++i < n) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }; - }; - d3.interpolateRgb = function(a, b) { - a = d3.rgb(a); - b = d3.rgb(b); - var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; - return function(t) { - return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); - }; - }; - d3.interpolateHsl = function(a, b) { - a = d3.hsl(a); - b = d3.hsl(b); - var h0 = a.h, s0 = a.s, l0 = a.l, h1 = b.h - h0, s1 = b.s - s0, l1 = b.l - l0; - if (h1 > 180) h1 -= 360; else if (h1 < -180) h1 += 360; - return function(t) { - return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t) + ""; - }; - }; - d3.interpolateLab = function(a, b) { - a = d3.lab(a); - b = d3.lab(b); - var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; - return function(t) { - return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; - }; - }; - d3.interpolateHcl = function(a, b) { - a = d3.hcl(a); - b = d3.hcl(b); - var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; - if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; - return function(t) { - return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; - }; - }; - d3.interpolateArray = function(a, b) { - var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; - for (i = 0; i < n0; ++i) x.push(d3.interpolate(a[i], b[i])); - for (; i < na; ++i) c[i] = a[i]; - for (; i < nb; ++i) c[i] = b[i]; - return function(t) { - for (i = 0; i < n0; ++i) c[i] = x[i](t); - return c; - }; - }; - d3.interpolateObject = function(a, b) { - var i = {}, c = {}, k; - for (k in a) { - if (k in b) { - i[k] = d3_interpolateByName(k)(a[k], b[k]); - } else { - c[k] = a[k]; - } - } - for (k in b) { - if (!(k in a)) { - c[k] = b[k]; - } - } - return function(t) { - for (k in i) c[k] = i[k](t); - return c; - }; - }; - var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; - d3.interpolators = [ d3.interpolateObject, function(a, b) { - return b instanceof Array && d3.interpolateArray(a, b); - }, function(a, b) { - return (typeof a === "string" || typeof b === "string") && d3.interpolateString(a + "", b + ""); - }, function(a, b) { - return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); - }, function(a, b) { - return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b); - } ]; - d3.rgb = function(r, g, b) { - return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b); - }; - d3_Rgb.prototype.brighter = function(k) { - k = Math.pow(.7, arguments.length ? k : 1); - var r = this.r, g = this.g, b = this.b, i = 30; - if (!r && !g && !b) return d3_rgb(i, i, i); - if (r && r < i) r = i; - if (g && g < i) g = i; - if (b && b < i) b = i; - return d3_rgb(Math.min(255, Math.floor(r / k)), Math.min(255, Math.floor(g / k)), Math.min(255, Math.floor(b / k))); - }; - d3_Rgb.prototype.darker = function(k) { - k = Math.pow(.7, arguments.length ? k : 1); - return d3_rgb(Math.floor(k * this.r), Math.floor(k * this.g), Math.floor(k * this.b)); - }; - d3_Rgb.prototype.hsl = function() { - return d3_rgb_hsl(this.r, this.g, this.b); - }; - d3_Rgb.prototype.toString = function() { - return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); - }; - var d3_rgb_names = d3.map({ - aliceblue: "#f0f8ff", - antiquewhite: "#faebd7", - aqua: "#00ffff", - aquamarine: "#7fffd4", - azure: "#f0ffff", - beige: "#f5f5dc", - bisque: "#ffe4c4", - black: "#000000", - blanchedalmond: "#ffebcd", - blue: "#0000ff", - blueviolet: "#8a2be2", - brown: "#a52a2a", - burlywood: "#deb887", - cadetblue: "#5f9ea0", - chartreuse: "#7fff00", - chocolate: "#d2691e", - coral: "#ff7f50", - cornflowerblue: "#6495ed", - cornsilk: "#fff8dc", - crimson: "#dc143c", - cyan: "#00ffff", - darkblue: "#00008b", - darkcyan: "#008b8b", - darkgoldenrod: "#b8860b", - darkgray: "#a9a9a9", - darkgreen: "#006400", - darkgrey: "#a9a9a9", - darkkhaki: "#bdb76b", - darkmagenta: "#8b008b", - darkolivegreen: "#556b2f", - darkorange: "#ff8c00", - darkorchid: "#9932cc", - darkred: "#8b0000", - darksalmon: "#e9967a", - darkseagreen: "#8fbc8f", - darkslateblue: "#483d8b", - darkslategray: "#2f4f4f", - darkslategrey: "#2f4f4f", - darkturquoise: "#00ced1", - darkviolet: "#9400d3", - deeppink: "#ff1493", - deepskyblue: "#00bfff", - dimgray: "#696969", - dimgrey: "#696969", - dodgerblue: "#1e90ff", - firebrick: "#b22222", - floralwhite: "#fffaf0", - forestgreen: "#228b22", - fuchsia: "#ff00ff", - gainsboro: "#dcdcdc", - ghostwhite: "#f8f8ff", - gold: "#ffd700", - goldenrod: "#daa520", - gray: "#808080", - green: "#008000", - greenyellow: "#adff2f", - grey: "#808080", - honeydew: "#f0fff0", - hotpink: "#ff69b4", - indianred: "#cd5c5c", - indigo: "#4b0082", - ivory: "#fffff0", - khaki: "#f0e68c", - lavender: "#e6e6fa", - lavenderblush: "#fff0f5", - lawngreen: "#7cfc00", - lemonchiffon: "#fffacd", - lightblue: "#add8e6", - lightcoral: "#f08080", - lightcyan: "#e0ffff", - lightgoldenrodyellow: "#fafad2", - lightgray: "#d3d3d3", - lightgreen: "#90ee90", - lightgrey: "#d3d3d3", - lightpink: "#ffb6c1", - lightsalmon: "#ffa07a", - lightseagreen: "#20b2aa", - lightskyblue: "#87cefa", - lightslategray: "#778899", - lightslategrey: "#778899", - lightsteelblue: "#b0c4de", - lightyellow: "#ffffe0", - lime: "#00ff00", - limegreen: "#32cd32", - linen: "#faf0e6", - magenta: "#ff00ff", - maroon: "#800000", - mediumaquamarine: "#66cdaa", - mediumblue: "#0000cd", - mediumorchid: "#ba55d3", - mediumpurple: "#9370db", - mediumseagreen: "#3cb371", - mediumslateblue: "#7b68ee", - mediumspringgreen: "#00fa9a", - mediumturquoise: "#48d1cc", - mediumvioletred: "#c71585", - midnightblue: "#191970", - mintcream: "#f5fffa", - mistyrose: "#ffe4e1", - moccasin: "#ffe4b5", - navajowhite: "#ffdead", - navy: "#000080", - oldlace: "#fdf5e6", - olive: "#808000", - olivedrab: "#6b8e23", - orange: "#ffa500", - orangered: "#ff4500", - orchid: "#da70d6", - palegoldenrod: "#eee8aa", - palegreen: "#98fb98", - paleturquoise: "#afeeee", - palevioletred: "#db7093", - papayawhip: "#ffefd5", - peachpuff: "#ffdab9", - peru: "#cd853f", - pink: "#ffc0cb", - plum: "#dda0dd", - powderblue: "#b0e0e6", - purple: "#800080", - red: "#ff0000", - rosybrown: "#bc8f8f", - royalblue: "#4169e1", - saddlebrown: "#8b4513", - salmon: "#fa8072", - sandybrown: "#f4a460", - seagreen: "#2e8b57", - seashell: "#fff5ee", - sienna: "#a0522d", - silver: "#c0c0c0", - skyblue: "#87ceeb", - slateblue: "#6a5acd", - slategray: "#708090", - slategrey: "#708090", - snow: "#fffafa", - springgreen: "#00ff7f", - steelblue: "#4682b4", - tan: "#d2b48c", - teal: "#008080", - thistle: "#d8bfd8", - tomato: "#ff6347", - turquoise: "#40e0d0", - violet: "#ee82ee", - wheat: "#f5deb3", - white: "#ffffff", - whitesmoke: "#f5f5f5", - yellow: "#ffff00", - yellowgreen: "#9acd32" - }); - d3_rgb_names.forEach(function(key, value) { - d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); - }); - d3.hsl = function(h, s, l) { - return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l); - }; - d3_Hsl.prototype.brighter = function(k) { - k = Math.pow(.7, arguments.length ? k : 1); - return d3_hsl(this.h, this.s, this.l / k); - }; - d3_Hsl.prototype.darker = function(k) { - k = Math.pow(.7, arguments.length ? k : 1); - return d3_hsl(this.h, this.s, k * this.l); - }; - d3_Hsl.prototype.rgb = function() { - return d3_hsl_rgb(this.h, this.s, this.l); - }; - d3_Hsl.prototype.toString = function() { - return this.rgb().toString(); - }; - d3.hcl = function(h, c, l) { - return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l); - }; - d3_Hcl.prototype.brighter = function(k) { - return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); - }; - d3_Hcl.prototype.darker = function(k) { - return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); - }; - d3_Hcl.prototype.rgb = function() { - return d3_hcl_lab(this.h, this.c, this.l).rgb(); - }; - d3_Hcl.prototype.toString = function() { - return this.rgb() + ""; - }; - d3.lab = function(l, a, b) { - return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b); - }; - var d3_lab_K = 18; - var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; - d3_Lab.prototype.brighter = function(k) { - return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); - }; - d3_Lab.prototype.darker = function(k) { - return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); - }; - d3_Lab.prototype.rgb = function() { - return d3_lab_rgb(this.l, this.a, this.b); - }; - d3_Lab.prototype.toString = function() { - return this.rgb() + ""; - }; - var d3_select = function(s, n) { - return n.querySelector(s); - }, d3_selectAll = function(s, n) { - return n.querySelectorAll(s); - }, d3_selectRoot = document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) { - return d3_selectMatcher.call(n, s); - }; - if (typeof Sizzle === "function") { - d3_select = function(s, n) { - return Sizzle(s, n)[0] || null; - }; - d3_selectAll = function(s, n) { - return Sizzle.uniqueSort(Sizzle(s, n)); - }; - d3_selectMatches = Sizzle.matchesSelector; - } - var d3_selectionPrototype = []; - d3.selection = function() { - return d3_selectionRoot; - }; - d3.selection.prototype = d3_selectionPrototype; - d3_selectionPrototype.select = function(selector) { - var subgroups = [], subgroup, subnode, group, node; - if (typeof selector !== "function") selector = d3_selection_selector(selector); - for (var j = -1, m = this.length; ++j < m; ) { - subgroups.push(subgroup = []); - subgroup.parentNode = (group = this[j]).parentNode; - for (var i = -1, n = group.length; ++i < n; ) { - if (node = group[i]) { - subgroup.push(subnode = selector.call(node, node.__data__, i)); - if (subnode && "__data__" in node) subnode.__data__ = node.__data__; - } else { - subgroup.push(null); - } - } - } - return d3_selection(subgroups); - }; - d3_selectionPrototype.selectAll = function(selector) { - var subgroups = [], subgroup, node; - if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); - for (var j = -1, m = this.length; ++j < m; ) { - for (var group = this[j], i = -1, n = group.length; ++i < n; ) { - if (node = group[i]) { - subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i))); - subgroup.parentNode = node; - } - } - } - return d3_selection(subgroups); - }; - d3_selectionPrototype.attr = function(name, value) { - if (arguments.length < 2) { - if (typeof name === "string") { - var node = this.node(); - name = d3.ns.qualify(name); - return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); - } - for (value in name) this.each(d3_selection_attr(value, name[value])); - return this; - } - return this.each(d3_selection_attr(name, value)); - }; - d3_selectionPrototype.classed = function(name, value) { - if (arguments.length < 2) { - if (typeof name === "string") { - var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1; - if (value = node.classList) { - while (++i < n) if (!value.contains(name[i])) return false; - } else { - value = node.className; - if (value.baseVal != null) value = value.baseVal; - while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; - } - return true; - } - for (value in name) this.each(d3_selection_classed(value, name[value])); - return this; - } - return this.each(d3_selection_classed(name, value)); - }; - d3_selectionPrototype.style = function(name, value, priority) { - var n = arguments.length; - if (n < 3) { - if (typeof name !== "string") { - if (n < 2) value = ""; - for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); - return this; - } - if (n < 2) return window.getComputedStyle(this.node(), null).getPropertyValue(name); - priority = ""; - } - return this.each(d3_selection_style(name, value, priority)); - }; - d3_selectionPrototype.property = function(name, value) { - if (arguments.length < 2) { - if (typeof name === "string") return this.node()[name]; - for (value in name) this.each(d3_selection_property(value, name[value])); - return this; - } - return this.each(d3_selection_property(name, value)); - }; - d3_selectionPrototype.text = function(value) { - return arguments.length < 1 ? this.node().textContent : this.each(typeof value === "function" ? function() { - var v = value.apply(this, arguments); - this.textContent = v == null ? "" : v; - } : value == null ? function() { - this.textContent = ""; - } : function() { - this.textContent = value; - }); - }; - d3_selectionPrototype.html = function(value) { - return arguments.length < 1 ? this.node().innerHTML : this.each(typeof value === "function" ? function() { - var v = value.apply(this, arguments); - this.innerHTML = v == null ? "" : v; - } : value == null ? function() { - this.innerHTML = ""; - } : function() { - this.innerHTML = value; - }); - }; - d3_selectionPrototype.append = function(name) { - function append() { - return this.appendChild(document.createElementNS(this.namespaceURI, name)); - } - function appendNS() { - return this.appendChild(document.createElementNS(name.space, name.local)); - } - name = d3.ns.qualify(name); - return this.select(name.local ? appendNS : append); - }; - d3_selectionPrototype.insert = function(name, before) { - function insert() { - return this.insertBefore(document.createElementNS(this.namespaceURI, name), d3_select(before, this)); - } - function insertNS() { - return this.insertBefore(document.createElementNS(name.space, name.local), d3_select(before, this)); - } - name = d3.ns.qualify(name); - return this.select(name.local ? insertNS : insert); - }; - d3_selectionPrototype.remove = function() { - return this.each(function() { - var parent = this.parentNode; - if (parent) parent.removeChild(this); - }); - }; - d3_selectionPrototype.data = function(value, key) { - function bind(group, groupData) { - var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), n1 = Math.max(n, m), updateNodes = [], enterNodes = [], exitNodes = [], node, nodeData; - if (key) { - var nodeByKeyValue = new d3_Map, keyValues = [], keyValue, j = groupData.length; - for (i = -1; ++i < n; ) { - keyValue = key.call(node = group[i], node.__data__, i); - if (nodeByKeyValue.has(keyValue)) { - exitNodes[j++] = node; - } else { - nodeByKeyValue.set(keyValue, node); - } - keyValues.push(keyValue); - } - for (i = -1; ++i < m; ) { - keyValue = key.call(groupData, nodeData = groupData[i], i); - if (nodeByKeyValue.has(keyValue)) { - updateNodes[i] = node = nodeByKeyValue.get(keyValue); - node.__data__ = nodeData; - enterNodes[i] = exitNodes[i] = null; - } else { - enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; - } - nodeByKeyValue.remove(keyValue); - } - for (i = -1; ++i < n; ) { - if (nodeByKeyValue.has(keyValues[i])) { - exitNodes[i] = group[i]; - } - } - } else { - for (i = -1; ++i < n0; ) { - node = group[i]; - nodeData = groupData[i]; - if (node) { - node.__data__ = nodeData; - updateNodes[i] = node; - enterNodes[i] = exitNodes[i] = null; - } else { - enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; - } - } - for (; i < m; ++i) { - enterNodes[i] = d3_selection_dataNode(groupData[i]); - updateNodes[i] = exitNodes[i] = null; - } - for (; i < n1; ++i) { - exitNodes[i] = group[i]; - enterNodes[i] = updateNodes[i] = null; - } - } - enterNodes.update = updateNodes; - enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; - enter.push(enterNodes); - update.push(updateNodes); - exit.push(exitNodes); - } - var i = -1, n = this.length, group, node; - if (!arguments.length) { - value = new Array(n = (group = this[0]).length); - while (++i < n) { - if (node = group[i]) { - value[i] = node.__data__; - } - } - return value; - } - var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); - if (typeof value === "function") { - while (++i < n) { - bind(group = this[i], value.call(group, group.parentNode.__data__, i)); - } - } else { - while (++i < n) { - bind(group = this[i], value); - } - } - update.enter = function() { - return enter; - }; - update.exit = function() { - return exit; - }; - return update; - }; - d3_selectionPrototype.datum = d3_selectionPrototype.map = function(value) { - return arguments.length < 1 ? this.property("__data__") : this.property("__data__", value); - }; - d3_selectionPrototype.filter = function(filter) { - var subgroups = [], subgroup, group, node; - if (typeof filter !== "function") filter = d3_selection_filter(filter); - for (var j = 0, m = this.length; j < m; j++) { - subgroups.push(subgroup = []); - subgroup.parentNode = (group = this[j]).parentNode; - for (var i = 0, n = group.length; i < n; i++) { - if ((node = group[i]) && filter.call(node, node.__data__, i)) { - subgroup.push(node); - } - } - } - return d3_selection(subgroups); - }; - d3_selectionPrototype.order = function() { - for (var j = -1, m = this.length; ++j < m; ) { - for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { - if (node = group[i]) { - if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); - next = node; - } - } - } - return this; - }; - d3_selectionPrototype.sort = function(comparator) { - comparator = d3_selection_sortComparator.apply(this, arguments); - for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); - return this.order(); - }; - d3_selectionPrototype.on = function(type, listener, capture) { - var n = arguments.length; - if (n < 3) { - if (typeof type !== "string") { - if (n < 2) listener = false; - for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); - return this; - } - if (n < 2) return (n = this.node()["__on" + type]) && n._; - capture = false; - } - return this.each(d3_selection_on(type, listener, capture)); - }; - d3_selectionPrototype.each = function(callback) { - return d3_selection_each(this, function(node, i, j) { - callback.call(node, node.__data__, i, j); - }); - }; - d3_selectionPrototype.call = function(callback) { - callback.apply(this, (arguments[0] = this, arguments)); - return this; - }; - d3_selectionPrototype.empty = function() { - return !this.node(); - }; - d3_selectionPrototype.node = function(callback) { - for (var j = 0, m = this.length; j < m; j++) { - for (var group = this[j], i = 0, n = group.length; i < n; i++) { - var node = group[i]; - if (node) return node; - } - } - return null; - }; - d3_selectionPrototype.transition = function() { - var subgroups = [], subgroup, node; - for (var j = -1, m = this.length; ++j < m; ) { - subgroups.push(subgroup = []); - for (var group = this[j], i = -1, n = group.length; ++i < n; ) { - subgroup.push((node = group[i]) ? { - node: node, - delay: d3_transitionDelay, - duration: d3_transitionDuration - } : null); - } - } - return d3_transition(subgroups, d3_transitionId || ++d3_transitionNextId, Date.now()); - }; - var d3_selectionRoot = d3_selection([ [ document ] ]); - d3_selectionRoot[0].parentNode = d3_selectRoot; - d3.select = function(selector) { - return typeof selector === "string" ? d3_selectionRoot.select(selector) : d3_selection([ [ selector ] ]); - }; - d3.selectAll = function(selector) { - return typeof selector === "string" ? d3_selectionRoot.selectAll(selector) : d3_selection([ d3_array(selector) ]); - }; - var d3_selection_enterPrototype = []; - d3.selection.enter = d3_selection_enter; - d3.selection.enter.prototype = d3_selection_enterPrototype; - d3_selection_enterPrototype.append = d3_selectionPrototype.append; - d3_selection_enterPrototype.insert = d3_selectionPrototype.insert; - d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; - d3_selection_enterPrototype.node = d3_selectionPrototype.node; - d3_selection_enterPrototype.select = function(selector) { - var subgroups = [], subgroup, subnode, upgroup, group, node; - for (var j = -1, m = this.length; ++j < m; ) { - upgroup = (group = this[j]).update; - subgroups.push(subgroup = []); - subgroup.parentNode = group.parentNode; - for (var i = -1, n = group.length; ++i < n; ) { - if (node = group[i]) { - subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i)); - subnode.__data__ = node.__data__; - } else { - subgroup.push(null); - } - } - } - return d3_selection(subgroups); - }; - var d3_transitionPrototype = [], d3_transitionNextId = 0, d3_transitionId = 0, d3_transitionDefaultDelay = 0, d3_transitionDefaultDuration = 250, d3_transitionDefaultEase = d3.ease("cubic-in-out"), d3_transitionDelay = d3_transitionDefaultDelay, d3_transitionDuration = d3_transitionDefaultDuration, d3_transitionEase = d3_transitionDefaultEase; - d3_transitionPrototype.call = d3_selectionPrototype.call; - d3.transition = function(selection) { - return arguments.length ? d3_transitionId ? selection.transition() : selection : d3_selectionRoot.transition(); - }; - d3.transition.prototype = d3_transitionPrototype; - d3_transitionPrototype.select = function(selector) { - var subgroups = [], subgroup, subnode, node; - if (typeof selector !== "function") selector = d3_selection_selector(selector); - for (var j = -1, m = this.length; ++j < m; ) { - subgroups.push(subgroup = []); - for (var group = this[j], i = -1, n = group.length; ++i < n; ) { - if ((node = group[i]) && (subnode = selector.call(node.node, node.node.__data__, i))) { - if ("__data__" in node.node) subnode.__data__ = node.node.__data__; - subgroup.push({ - node: subnode, - delay: node.delay, - duration: node.duration - }); - } else { - subgroup.push(null); - } - } - } - return d3_transition(subgroups, this.id, this.time).ease(this.ease()); - }; - d3_transitionPrototype.selectAll = function(selector) { - var subgroups = [], subgroup, subnodes, node; - if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); - for (var j = -1, m = this.length; ++j < m; ) { - for (var group = this[j], i = -1, n = group.length; ++i < n; ) { - if (node = group[i]) { - subnodes = selector.call(node.node, node.node.__data__, i); - subgroups.push(subgroup = []); - for (var k = -1, o = subnodes.length; ++k < o; ) { - subgroup.push({ - node: subnodes[k], - delay: node.delay, - duration: node.duration - }); - } - } - } - } - return d3_transition(subgroups, this.id, this.time).ease(this.ease()); - }; - d3_transitionPrototype.filter = function(filter) { - var subgroups = [], subgroup, group, node; - if (typeof filter !== "function") filter = d3_selection_filter(filter); - for (var j = 0, m = this.length; j < m; j++) { - subgroups.push(subgroup = []); - for (var group = this[j], i = 0, n = group.length; i < n; i++) { - if ((node = group[i]) && filter.call(node.node, node.node.__data__, i)) { - subgroup.push(node); - } - } - } - return d3_transition(subgroups, this.id, this.time).ease(this.ease()); - }; - d3_transitionPrototype.attr = function(name, value) { - if (arguments.length < 2) { - for (value in name) this.attrTween(value, d3_tweenByName(name[value], value)); - return this; - } - return this.attrTween(name, d3_tweenByName(value, name)); - }; - d3_transitionPrototype.attrTween = function(nameNS, tween) { - function attrTween(d, i) { - var f = tween.call(this, d, i, this.getAttribute(name)); - return f === d3_tweenRemove ? (this.removeAttribute(name), null) : f && function(t) { - this.setAttribute(name, f(t)); - }; - } - function attrTweenNS(d, i) { - var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); - return f === d3_tweenRemove ? (this.removeAttributeNS(name.space, name.local), null) : f && function(t) { - this.setAttributeNS(name.space, name.local, f(t)); - }; - } - var name = d3.ns.qualify(nameNS); - return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); - }; - d3_transitionPrototype.style = function(name, value, priority) { - var n = arguments.length; - if (n < 3) { - if (typeof name !== "string") { - if (n < 2) value = ""; - for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value); - return this; - } - priority = ""; - } - return this.styleTween(name, d3_tweenByName(value, name), priority); - }; - d3_transitionPrototype.styleTween = function(name, tween, priority) { - if (arguments.length < 3) priority = ""; - return this.tween("style." + name, function(d, i) { - var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); - return f === d3_tweenRemove ? (this.style.removeProperty(name), null) : f && function(t) { - this.style.setProperty(name, f(t), priority); - }; - }); - }; - d3_transitionPrototype.text = function(value) { - return this.tween("text", function(d, i) { - this.textContent = typeof value === "function" ? value.call(this, d, i) : value; - }); - }; - d3_transitionPrototype.remove = function() { - return this.each("end.transition", function() { - var p; - if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); - }); - }; - d3_transitionPrototype.delay = function(value) { - return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { - node.delay = value.call(node = node.node, node.__data__, i, j) | 0; - } : (value = value | 0, function(node) { - node.delay = value; - })); - }; - d3_transitionPrototype.duration = function(value) { - return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { - node.duration = Math.max(1, value.call(node = node.node, node.__data__, i, j) | 0); - } : (value = Math.max(1, value | 0), function(node) { - node.duration = value; - })); - }; - d3_transitionPrototype.transition = function() { - return this.select(d3_this); - }; - d3.tween = function(b, interpolate) { - function tweenFunction(d, i, a) { - var v = b.call(this, d, i); - return v == null ? a != "" && d3_tweenRemove : a != v && interpolate(a, v); - } - function tweenString(d, i, a) { - return a != b && interpolate(a, b); - } - return typeof b === "function" ? tweenFunction : b == null ? d3_tweenNull : (b += "", tweenString); - }; - var d3_tweenRemove = {}; - var d3_timer_queue = null, d3_timer_interval, d3_timer_timeout; - d3.timer = function(callback, delay, then) { - var found = false, t0, t1 = d3_timer_queue; - if (arguments.length < 3) { - if (arguments.length < 2) delay = 0; else if (!isFinite(delay)) return; - then = Date.now(); - } - while (t1) { - if (t1.callback === callback) { - t1.then = then; - t1.delay = delay; - found = true; - break; - } - t0 = t1; - t1 = t1.next; - } - if (!found) d3_timer_queue = { - callback: callback, - then: then, - delay: delay, - next: d3_timer_queue - }; - if (!d3_timer_interval) { - d3_timer_timeout = clearTimeout(d3_timer_timeout); - d3_timer_interval = 1; - d3_timer_frame(d3_timer_step); - } - }; - d3.timer.flush = function() { - var elapsed, now = Date.now(), t1 = d3_timer_queue; - while (t1) { - elapsed = now - t1.then; - if (!t1.delay) t1.flush = t1.callback(elapsed); - t1 = t1.next; - } - d3_timer_flush(); - }; - var d3_timer_frame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { - setTimeout(callback, 17); - }; - d3.mouse = function(container) { - return d3_mousePoint(container, d3_eventSource()); - }; - var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; - d3.touches = function(container, touches) { - if (arguments.length < 2) touches = d3_eventSource().touches; - return touches ? d3_array(touches).map(function(touch) { - var point = d3_mousePoint(container, touch); - point.identifier = touch.identifier; - return point; - }) : []; - }; - d3.scale = {}; - d3.scale.linear = function() { - return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3.interpolate, false); - }; - d3.scale.log = function() { - return d3_scale_log(d3.scale.linear(), d3_scale_logp); - }; - var d3_scale_logFormat = d3.format(".0e"); - d3_scale_logp.pow = function(x) { - return Math.pow(10, x); - }; - d3_scale_logn.pow = function(x) { - return -Math.pow(10, -x); - }; - d3.scale.pow = function() { - return d3_scale_pow(d3.scale.linear(), 1); - }; - d3.scale.sqrt = function() { - return d3.scale.pow().exponent(.5); - }; - d3.scale.ordinal = function() { - return d3_scale_ordinal([], { - t: "range", - a: [ [] ] - }); - }; - d3.scale.category10 = function() { - return d3.scale.ordinal().range(d3_category10); - }; - d3.scale.category20 = function() { - return d3.scale.ordinal().range(d3_category20); - }; - d3.scale.category20b = function() { - return d3.scale.ordinal().range(d3_category20b); - }; - d3.scale.category20c = function() { - return d3.scale.ordinal().range(d3_category20c); - }; - var d3_category10 = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ]; - var d3_category20 = [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ]; - var d3_category20b = [ "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" ]; - var d3_category20c = [ "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9" ]; - d3.scale.quantile = function() { - return d3_scale_quantile([], []); - }; - d3.scale.quantize = function() { - return d3_scale_quantize(0, 1, [ 0, 1 ]); - }; - d3.scale.threshold = function() { - return d3_scale_threshold([ .5 ], [ 0, 1 ]); - }; - d3.scale.identity = function() { - return d3_scale_identity([ 0, 1 ]); - }; - d3.svg = {}; - d3.svg.arc = function() { - function arc() { - var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0), df = da < Math.PI ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1); - return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z"; - } - var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; - arc.innerRadius = function(v) { - if (!arguments.length) return innerRadius; - innerRadius = d3_functor(v); - return arc; - }; - arc.outerRadius = function(v) { - if (!arguments.length) return outerRadius; - outerRadius = d3_functor(v); - return arc; - }; - arc.startAngle = function(v) { - if (!arguments.length) return startAngle; - startAngle = d3_functor(v); - return arc; - }; - arc.endAngle = function(v) { - if (!arguments.length) return endAngle; - endAngle = d3_functor(v); - return arc; - }; - arc.centroid = function() { - var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; - return [ Math.cos(a) * r, Math.sin(a) * r ]; - }; - return arc; - }; - var d3_svg_arcOffset = -Math.PI / 2, d3_svg_arcMax = 2 * Math.PI - 1e-6; - d3.svg.line = function() { - return d3_svg_line(d3_identity); - }; - var d3_svg_lineInterpolators = d3.map({ - linear: d3_svg_lineLinear, - "linear-closed": d3_svg_lineLinearClosed, - "step-before": d3_svg_lineStepBefore, - "step-after": d3_svg_lineStepAfter, - basis: d3_svg_lineBasis, - "basis-open": d3_svg_lineBasisOpen, - "basis-closed": d3_svg_lineBasisClosed, - bundle: d3_svg_lineBundle, - cardinal: d3_svg_lineCardinal, - "cardinal-open": d3_svg_lineCardinalOpen, - "cardinal-closed": d3_svg_lineCardinalClosed, - monotone: d3_svg_lineMonotone - }); - d3_svg_lineInterpolators.forEach(function(key, value) { - value.key = key; - value.closed = /-closed$/.test(key); - }); - var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; - d3.svg.line.radial = function() { - var line = d3_svg_line(d3_svg_lineRadial); - line.radius = line.x, delete line.x; - line.angle = line.y, delete line.y; - return line; - }; - d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; - d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; - d3.svg.area = function() { - return d3_svg_area(d3_identity); - }; - d3.svg.area.radial = function() { - var area = d3_svg_area(d3_svg_lineRadial); - area.radius = area.x, delete area.x; - area.innerRadius = area.x0, delete area.x0; - area.outerRadius = area.x1, delete area.x1; - area.angle = area.y, delete area.y; - area.startAngle = area.y0, delete area.y0; - area.endAngle = area.y1, delete area.y1; - return area; - }; - d3.svg.chord = function() { - function chord(d, i) { - var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); - return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; - } - function subgroup(self, f, d, i) { - var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; - return { - r: r, - a0: a0, - a1: a1, - p0: [ r * Math.cos(a0), r * Math.sin(a0) ], - p1: [ r * Math.cos(a1), r * Math.sin(a1) ] - }; - } - function equals(a, b) { - return a.a0 == b.a0 && a.a1 == b.a1; - } - function arc(r, p, a) { - return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p; - } - function curve(r0, p0, r1, p1) { - return "Q 0,0 " + p1; - } - var source = d3_svg_chordSource, target = d3_svg_chordTarget, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; - chord.radius = function(v) { - if (!arguments.length) return radius; - radius = d3_functor(v); - return chord; - }; - chord.source = function(v) { - if (!arguments.length) return source; - source = d3_functor(v); - return chord; - }; - chord.target = function(v) { - if (!arguments.length) return target; - target = d3_functor(v); - return chord; - }; - chord.startAngle = function(v) { - if (!arguments.length) return startAngle; - startAngle = d3_functor(v); - return chord; - }; - chord.endAngle = function(v) { - if (!arguments.length) return endAngle; - endAngle = d3_functor(v); - return chord; - }; - return chord; - }; - d3.svg.diagonal = function() { - function diagonal(d, i) { - var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { - x: p0.x, - y: m - }, { - x: p3.x, - y: m - }, p3 ]; - p = p.map(projection); - return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; - } - var source = d3_svg_chordSource, target = d3_svg_chordTarget, projection = d3_svg_diagonalProjection; - diagonal.source = function(x) { - if (!arguments.length) return source; - source = d3_functor(x); - return diagonal; - }; - diagonal.target = function(x) { - if (!arguments.length) return target; - target = d3_functor(x); - return diagonal; - }; - diagonal.projection = function(x) { - if (!arguments.length) return projection; - projection = x; - return diagonal; - }; - return diagonal; - }; - d3.svg.diagonal.radial = function() { - var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; - diagonal.projection = function(x) { - return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; - }; - return diagonal; - }; - d3.svg.mouse = d3.mouse; - d3.svg.touches = d3.touches; - d3.svg.symbol = function() { - function symbol(d, i) { - return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); - } - var type = d3_svg_symbolType, size = d3_svg_symbolSize; - symbol.type = function(x) { - if (!arguments.length) return type; - type = d3_functor(x); - return symbol; - }; - symbol.size = function(x) { - if (!arguments.length) return size; - size = d3_functor(x); - return symbol; - }; - return symbol; - }; - var d3_svg_symbols = d3.map({ - circle: d3_svg_symbolCircle, - cross: function(size) { - var r = Math.sqrt(size / 5) / 2; - return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; - }, - diamond: function(size) { - var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; - return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; - }, - square: function(size) { - var r = Math.sqrt(size) / 2; - return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; - }, - "triangle-down": function(size) { - var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; - return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; - }, - "triangle-up": function(size) { - var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; - return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; - } - }); - d3.svg.symbolTypes = d3_svg_symbols.keys(); - var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); - d3.svg.axis = function() { - function axis(g) { - g.each(function() { - var g = d3.select(this); - var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_; - var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1); - var tick = g.selectAll("g").data(ticks, String), tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform; - var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathEnter = path.enter().append("path").attr("class", "domain"), pathUpdate = d3.transition(path); - var scale1 = scale.copy(), scale0 = this.__chart__ || scale1; - this.__chart__ = scale1; - tickEnter.append("line").attr("class", "tick"); - tickEnter.append("text"); - var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"); - switch (orient) { - case "bottom": - { - tickTransform = d3_svg_axisX; - subtickEnter.attr("y2", tickMinorSize); - subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize); - lineEnter.attr("y2", tickMajorSize); - textEnter.attr("y", Math.max(tickMajorSize, 0) + tickPadding); - lineUpdate.attr("x2", 0).attr("y2", tickMajorSize); - textUpdate.attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding); - text.attr("dy", ".71em").attr("text-anchor", "middle"); - pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize); - break; - } - case "top": - { - tickTransform = d3_svg_axisX; - subtickEnter.attr("y2", -tickMinorSize); - subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize); - lineEnter.attr("y2", -tickMajorSize); - textEnter.attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); - lineUpdate.attr("x2", 0).attr("y2", -tickMajorSize); - textUpdate.attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); - text.attr("dy", "0em").attr("text-anchor", "middle"); - pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize); - break; - } - case "left": - { - tickTransform = d3_svg_axisY; - subtickEnter.attr("x2", -tickMinorSize); - subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0); - lineEnter.attr("x2", -tickMajorSize); - textEnter.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)); - lineUpdate.attr("x2", -tickMajorSize).attr("y2", 0); - textUpdate.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0); - text.attr("dy", ".32em").attr("text-anchor", "end"); - pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize); - break; - } - case "right": - { - tickTransform = d3_svg_axisY; - subtickEnter.attr("x2", tickMinorSize); - subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0); - lineEnter.attr("x2", tickMajorSize); - textEnter.attr("x", Math.max(tickMajorSize, 0) + tickPadding); - lineUpdate.attr("x2", tickMajorSize).attr("y2", 0); - textUpdate.attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0); - text.attr("dy", ".32em").attr("text-anchor", "start"); - pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize); - break; - } - } - if (scale.ticks) { - tickEnter.call(tickTransform, scale0); - tickUpdate.call(tickTransform, scale1); - tickExit.call(tickTransform, scale1); - subtickEnter.call(tickTransform, scale0); - subtickUpdate.call(tickTransform, scale1); - subtickExit.call(tickTransform, scale1); - } else { - var dx = scale1.rangeBand() / 2, x = function(d) { - return scale1(d) + dx; - }; - tickEnter.call(tickTransform, x); - tickUpdate.call(tickTransform, x); - } - }); - } - var scale = d3.scale.linear(), orient = "bottom", tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0; - axis.scale = function(x) { - if (!arguments.length) return scale; - scale = x; - return axis; - }; - axis.orient = function(x) { - if (!arguments.length) return orient; - orient = x; - return axis; - }; - axis.ticks = function() { - if (!arguments.length) return tickArguments_; - tickArguments_ = arguments; - return axis; - }; - axis.tickValues = function(x) { - if (!arguments.length) return tickValues; - tickValues = x; - return axis; - }; - axis.tickFormat = function(x) { - if (!arguments.length) return tickFormat_; - tickFormat_ = x; - return axis; - }; - axis.tickSize = function(x, y, z) { - if (!arguments.length) return tickMajorSize; - var n = arguments.length - 1; - tickMajorSize = +x; - tickMinorSize = n > 1 ? +y : tickMajorSize; - tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; - return axis; - }; - axis.tickPadding = function(x) { - if (!arguments.length) return tickPadding; - tickPadding = +x; - return axis; - }; - axis.tickSubdivide = function(x) { - if (!arguments.length) return tickSubdivide; - tickSubdivide = +x; - return axis; - }; - return axis; - }; - d3.svg.brush = function() { - function brush(g) { - g.each(function() { - var g = d3.select(this), bg = g.selectAll(".background").data([ 0 ]), fg = g.selectAll(".extent").data([ 0 ]), tz = g.selectAll(".resize").data(resizes, String), e; - g.style("pointer-events", "all").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); - bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); - fg.enter().append("rect").attr("class", "extent").style("cursor", "move"); - tz.enter().append("g").attr("class", function(d) { - return "resize " + d; - }).style("cursor", function(d) { - return d3_svg_brushCursor[d]; - }).append("rect").attr("x", function(d) { - return /[ew]$/.test(d) ? -3 : null; - }).attr("y", function(d) { - return /^[ns]/.test(d) ? -3 : null; - }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); - tz.style("display", brush.empty() ? "none" : null); - tz.exit().remove(); - if (x) { - e = d3_scaleRange(x); - bg.attr("x", e[0]).attr("width", e[1] - e[0]); - redrawX(g); - } - if (y) { - e = d3_scaleRange(y); - bg.attr("y", e[0]).attr("height", e[1] - e[0]); - redrawY(g); - } - redraw(g); - }); - } - function redraw(g) { - g.selectAll(".resize").attr("transform", function(d) { - return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; - }); - } - function redrawX(g) { - g.select(".extent").attr("x", extent[0][0]); - g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); - } - function redrawY(g) { - g.select(".extent").attr("y", extent[0][1]); - g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); - } - function brushstart() { - function mouse() { - var touches = d3.event.changedTouches; - return touches ? d3.touches(target, touches)[0] : d3.mouse(target); - } - function keydown() { - if (d3.event.keyCode == 32) { - if (!dragging) { - center = null; - origin[0] -= extent[1][0]; - origin[1] -= extent[1][1]; - dragging = 2; - } - d3_eventCancel(); - } - } - function keyup() { - if (d3.event.keyCode == 32 && dragging == 2) { - origin[0] += extent[1][0]; - origin[1] += extent[1][1]; - dragging = 0; - d3_eventCancel(); - } - } - function brushmove() { - var point = mouse(), moved = false; - if (offset) { - point[0] += offset[0]; - point[1] += offset[1]; - } - if (!dragging) { - if (d3.event.altKey) { - if (!center) center = [ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2 ]; - origin[0] = extent[+(point[0] < center[0])][0]; - origin[1] = extent[+(point[1] < center[1])][1]; - } else center = null; - } - if (resizingX && move1(point, x, 0)) { - redrawX(g); - moved = true; - } - if (resizingY && move1(point, y, 1)) { - redrawY(g); - moved = true; - } - if (moved) { - redraw(g); - event_({ - type: "brush", - mode: dragging ? "move" : "resize" - }); - } - } - function move1(point, scale, i) { - var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max; - if (dragging) { - r0 -= position; - r1 -= size + position; - } - min = Math.max(r0, Math.min(r1, point[i])); - if (dragging) { - max = (min += position) + size; - } else { - if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); - if (position < min) { - max = min; - min = position; - } else { - max = position; - } - } - if (extent[0][i] !== min || extent[1][i] !== max) { - extentDomain = null; - extent[0][i] = min; - extent[1][i] = max; - return true; - } - } - function brushend() { - brushmove(); - g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); - d3.select("body").style("cursor", null); - w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); - event_({ - type: "brushend" - }); - d3_eventCancel(); - } - var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), center, origin = mouse(), offset; - var w = d3.select(window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup); - if (dragging) { - origin[0] = extent[0][0] - origin[0]; - origin[1] = extent[0][1] - origin[1]; - } else if (resizing) { - var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); - offset = [ extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1] ]; - origin[0] = extent[ex][0]; - origin[1] = extent[ey][1]; - } else if (d3.event.altKey) center = origin.slice(); - g.style("pointer-events", "none").selectAll(".resize").style("display", null); - d3.select("body").style("cursor", eventTarget.style("cursor")); - event_({ - type: "brushstart" - }); - brushmove(); - d3_eventCancel(); - } - var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, resizes = d3_svg_brushResizes[0], extent = [ [ 0, 0 ], [ 0, 0 ] ], extentDomain; - brush.x = function(z) { - if (!arguments.length) return x; - x = z; - resizes = d3_svg_brushResizes[!x << 1 | !y]; - return brush; - }; - brush.y = function(z) { - if (!arguments.length) return y; - y = z; - resizes = d3_svg_brushResizes[!x << 1 | !y]; - return brush; - }; - brush.extent = function(z) { - var x0, x1, y0, y1, t; - if (!arguments.length) { - z = extentDomain || extent; - if (x) { - x0 = z[0][0], x1 = z[1][0]; - if (!extentDomain) { - x0 = extent[0][0], x1 = extent[1][0]; - if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); - if (x1 < x0) t = x0, x0 = x1, x1 = t; - } - } - if (y) { - y0 = z[0][1], y1 = z[1][1]; - if (!extentDomain) { - y0 = extent[0][1], y1 = extent[1][1]; - if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); - if (y1 < y0) t = y0, y0 = y1, y1 = t; - } - } - return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; - } - extentDomain = [ [ 0, 0 ], [ 0, 0 ] ]; - if (x) { - x0 = z[0], x1 = z[1]; - if (y) x0 = x0[0], x1 = x1[0]; - extentDomain[0][0] = x0, extentDomain[1][0] = x1; - if (x.invert) x0 = x(x0), x1 = x(x1); - if (x1 < x0) t = x0, x0 = x1, x1 = t; - extent[0][0] = x0 | 0, extent[1][0] = x1 | 0; - } - if (y) { - y0 = z[0], y1 = z[1]; - if (x) y0 = y0[1], y1 = y1[1]; - extentDomain[0][1] = y0, extentDomain[1][1] = y1; - if (y.invert) y0 = y(y0), y1 = y(y1); - if (y1 < y0) t = y0, y0 = y1, y1 = t; - extent[0][1] = y0 | 0, extent[1][1] = y1 | 0; - } - return brush; - }; - brush.clear = function() { - extentDomain = null; - extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0; - return brush; - }; - brush.empty = function() { - return x && extent[0][0] === extent[1][0] || y && extent[0][1] === extent[1][1]; - }; - return d3.rebind(brush, event, "on"); - }; - var d3_svg_brushCursor = { - n: "ns-resize", - e: "ew-resize", - s: "ns-resize", - w: "ew-resize", - nw: "nwse-resize", - ne: "nesw-resize", - se: "nwse-resize", - sw: "nesw-resize" - }; - var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; - d3.behavior = {}; - d3.behavior.drag = function() { - function drag() { - this.on("mousedown.drag", mousedown).on("touchstart.drag", mousedown); - } - function mousedown() { - function point() { - var p = target.parentNode; - return touchId ? d3.touches(p).filter(function(p) { - return p.identifier === touchId; - })[0] : d3.mouse(p); - } - function dragmove() { - if (!target.parentNode) return dragend(); - var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; - moved |= dx | dy; - origin_ = p; - d3_eventCancel(); - event_({ - type: "drag", - x: p[0] + offset[0], - y: p[1] + offset[1], - dx: dx, - dy: dy - }); - } - function dragend() { - event_({ - type: "dragend" - }); - if (moved) { - d3_eventCancel(); - if (d3.event.target === eventTarget) w.on("click.drag", click, true); - } - w.on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", null); - } - function click() { - d3_eventCancel(); - w.on("click.drag", null); - } - var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches && d3.event.changedTouches[0].identifier, offset, origin_ = point(), moved = 0; - var w = d3.select(window).on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); - if (origin) { - offset = origin.apply(target, arguments); - offset = [ offset.x - origin_[0], offset.y - origin_[1] ]; - } else { - offset = [ 0, 0 ]; - } - if (!touchId) d3_eventCancel(); - event_({ - type: "dragstart" - }); - } - var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null; - drag.origin = function(x) { - if (!arguments.length) return origin; - origin = x; - return drag; - }; - return d3.rebind(drag, event, "on"); - }; - d3.behavior.zoom = function() { - function zoom() { - this.on("mousedown.zoom", mousedown).on("mousewheel.zoom", mousewheel).on("mousemove.zoom", mousemove).on("DOMMouseScroll.zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); - } - function location(p) { - return [ (p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale ]; - } - function point(l) { - return [ l[0] * scale + translate[0], l[1] * scale + translate[1] ]; - } - function scaleTo(s) { - scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); - } - function translateTo(p, l) { - l = point(l); - translate[0] += p[0] - l[0]; - translate[1] += p[1] - l[1]; - } - function dispatch(event) { - if (x1) x1.domain(x0.range().map(function(x) { - return (x - translate[0]) / scale; - }).map(x0.invert)); - if (y1) y1.domain(y0.range().map(function(y) { - return (y - translate[1]) / scale; - }).map(y0.invert)); - d3.event.preventDefault(); - event({ - type: "zoom", - scale: scale, - translate: translate - }); - } - function mousedown() { - function mousemove() { - moved = 1; - translateTo(d3.mouse(target), l); - dispatch(event_); - } - function mouseup() { - if (moved) d3_eventCancel(); - w.on("mousemove.zoom", null).on("mouseup.zoom", null); - if (moved && d3.event.target === eventTarget) w.on("click.zoom", click, true); - } - function click() { - d3_eventCancel(); - w.on("click.zoom", null); - } - var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target)); - window.focus(); - d3_eventCancel(); - } - function mousewheel() { - if (!translate0) translate0 = location(d3.mouse(this)); - scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale); - translateTo(d3.mouse(this), translate0); - dispatch(event.of(this, arguments)); - } - function mousemove() { - translate0 = null; - } - function dblclick() { - var p = d3.mouse(this), l = location(p); - scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2); - translateTo(p, l); - dispatch(event.of(this, arguments)); - } - function touchstart() { - var touches = d3.touches(this), now = Date.now(); - scale0 = scale; - translate0 = {}; - touches.forEach(function(t) { - translate0[t.identifier] = location(t); - }); - d3_eventCancel(); - if (touches.length === 1) { - if (now - touchtime < 500) { - var p = touches[0], l = location(touches[0]); - scaleTo(scale * 2); - translateTo(p, l); - dispatch(event.of(this, arguments)); - } - touchtime = now; - } - } - function touchmove() { - var touches = d3.touches(this), p0 = touches[0], l0 = translate0[p0.identifier]; - if (p1 = touches[1]) { - var p1, l1 = translate0[p1.identifier]; - p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; - l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; - scaleTo(d3.event.scale * scale0); - } - translateTo(p0, l0); - touchtime = null; - dispatch(event.of(this, arguments)); - } - var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime; - zoom.translate = function(x) { - if (!arguments.length) return translate; - translate = x.map(Number); - return zoom; - }; - zoom.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return zoom; - }; - zoom.scaleExtent = function(x) { - if (!arguments.length) return scaleExtent; - scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); - return zoom; - }; - zoom.x = function(z) { - if (!arguments.length) return x1; - x1 = z; - x0 = z.copy(); - return zoom; - }; - zoom.y = function(z) { - if (!arguments.length) return y1; - y1 = z; - y0 = z.copy(); - return zoom; - }; - return d3.rebind(zoom, event, "on"); - }; - var d3_behavior_zoomDiv, d3_behavior_zoomInfinity = [ 0, Infinity ]; - d3.layout = {}; - d3.layout.bundle = function() { - return function(links) { - var paths = [], i = -1, n = links.length; - while (++i < n) paths.push(d3_layout_bundlePath(links[i])); - return paths; - }; - }; - d3.layout.chord = function() { - function relayout() { - var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; - chords = []; - groups = []; - k = 0, i = -1; - while (++i < n) { - x = 0, j = -1; - while (++j < n) { - x += matrix[i][j]; - } - groupSums.push(x); - subgroupIndex.push(d3.range(n)); - k += x; - } - if (sortGroups) { - groupIndex.sort(function(a, b) { - return sortGroups(groupSums[a], groupSums[b]); - }); - } - if (sortSubgroups) { - subgroupIndex.forEach(function(d, i) { - d.sort(function(a, b) { - return sortSubgroups(matrix[i][a], matrix[i][b]); - }); - }); - } - k = (2 * Math.PI - padding * n) / k; - x = 0, i = -1; - while (++i < n) { - x0 = x, j = -1; - while (++j < n) { - var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; - subgroups[di + "-" + dj] = { - index: di, - subindex: dj, - startAngle: a0, - endAngle: a1, - value: v - }; - } - groups[di] = { - index: di, - startAngle: x0, - endAngle: x, - value: (x - x0) / k - }; - x += padding; - } - i = -1; - while (++i < n) { - j = i - 1; - while (++j < n) { - var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; - if (source.value || target.value) { - chords.push(source.value < target.value ? { - source: target, - target: source - } : { - source: source, - target: target - }); - } - } - } - if (sortChords) resort(); - } - function resort() { - chords.sort(function(a, b) { - return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); - }); - } - var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; - chord.matrix = function(x) { - if (!arguments.length) return matrix; - n = (matrix = x) && matrix.length; - chords = groups = null; - return chord; - }; - chord.padding = function(x) { - if (!arguments.length) return padding; - padding = x; - chords = groups = null; - return chord; - }; - chord.sortGroups = function(x) { - if (!arguments.length) return sortGroups; - sortGroups = x; - chords = groups = null; - return chord; - }; - chord.sortSubgroups = function(x) { - if (!arguments.length) return sortSubgroups; - sortSubgroups = x; - chords = null; - return chord; - }; - chord.sortChords = function(x) { - if (!arguments.length) return sortChords; - sortChords = x; - if (chords) resort(); - return chord; - }; - chord.chords = function() { - if (!chords) relayout(); - return chords; - }; - chord.groups = function() { - if (!groups) relayout(); - return groups; - }; - return chord; - }; - d3.layout.force = function() { - function repulse(node) { - return function(quad, x1, y1, x2, y2) { - if (quad.point !== node) { - var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy); - if ((x2 - x1) * dn < theta) { - var k = quad.charge * dn * dn; - node.px -= dx * k; - node.py -= dy * k; - return true; - } - if (quad.point && isFinite(dn)) { - var k = quad.pointCharge * dn * dn; - node.px -= dx * k; - node.py -= dy * k; - } - } - return !quad.charge; - }; - } - function dragmove(d) { - d.px = d3.event.x; - d.py = d3.event.y; - force.resume(); - } - var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, interval, nodes = [], links = [], distances, strengths, charges; - force.tick = function() { - if ((alpha *= .99) < .005) { - event.end({ - type: "end", - alpha: alpha = 0 - }); - return true; - } - var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; - for (i = 0; i < m; ++i) { - o = links[i]; - s = o.source; - t = o.target; - x = t.x - s.x; - y = t.y - s.y; - if (l = x * x + y * y) { - l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; - x *= l; - y *= l; - t.x -= x * (k = s.weight / (t.weight + s.weight)); - t.y -= y * k; - s.x += x * (k = 1 - k); - s.y += y * k; - } - } - if (k = alpha * gravity) { - x = size[0] / 2; - y = size[1] / 2; - i = -1; - if (k) while (++i < n) { - o = nodes[i]; - o.x += (x - o.x) * k; - o.y += (y - o.y) * k; - } - } - if (charge) { - d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); - i = -1; - while (++i < n) { - if (!(o = nodes[i]).fixed) { - q.visit(repulse(o)); - } - } - } - i = -1; - while (++i < n) { - o = nodes[i]; - if (o.fixed) { - o.x = o.px; - o.y = o.py; - } else { - o.x -= (o.px - (o.px = o.x)) * friction; - o.y -= (o.py - (o.py = o.y)) * friction; - } - } - event.tick({ - type: "tick", - alpha: alpha - }); - }; - force.nodes = function(x) { - if (!arguments.length) return nodes; - nodes = x; - return force; - }; - force.links = function(x) { - if (!arguments.length) return links; - links = x; - return force; - }; - force.size = function(x) { - if (!arguments.length) return size; - size = x; - return force; - }; - force.linkDistance = function(x) { - if (!arguments.length) return linkDistance; - linkDistance = d3_functor(x); - return force; - }; - force.distance = force.linkDistance; - force.linkStrength = function(x) { - if (!arguments.length) return linkStrength; - linkStrength = d3_functor(x); - return force; - }; - force.friction = function(x) { - if (!arguments.length) return friction; - friction = x; - return force; - }; - force.charge = function(x) { - if (!arguments.length) return charge; - charge = typeof x === "function" ? x : +x; - return force; - }; - force.gravity = function(x) { - if (!arguments.length) return gravity; - gravity = x; - return force; - }; - force.theta = function(x) { - if (!arguments.length) return theta; - theta = x; - return force; - }; - force.alpha = function(x) { - if (!arguments.length) return alpha; - if (alpha) { - if (x > 0) alpha = x; else alpha = 0; - } else if (x > 0) { - event.start({ - type: "start", - alpha: alpha = x - }); - d3.timer(force.tick); - } - return force; - }; - force.start = function() { - function position(dimension, size) { - var neighbors = neighbor(i), j = -1, m = neighbors.length, x; - while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; - return Math.random() * size; - } - function neighbor() { - if (!neighbors) { - neighbors = []; - for (j = 0; j < n; ++j) { - neighbors[j] = []; - } - for (j = 0; j < m; ++j) { - var o = links[j]; - neighbors[o.source.index].push(o.target); - neighbors[o.target.index].push(o.source); - } - } - return neighbors[i]; - } - var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; - for (i = 0; i < n; ++i) { - (o = nodes[i]).index = i; - o.weight = 0; - } - distances = []; - strengths = []; - for (i = 0; i < m; ++i) { - o = links[i]; - if (typeof o.source == "number") o.source = nodes[o.source]; - if (typeof o.target == "number") o.target = nodes[o.target]; - distances[i] = linkDistance.call(this, o, i); - strengths[i] = linkStrength.call(this, o, i); - ++o.source.weight; - ++o.target.weight; - } - for (i = 0; i < n; ++i) { - o = nodes[i]; - if (isNaN(o.x)) o.x = position("x", w); - if (isNaN(o.y)) o.y = position("y", h); - if (isNaN(o.px)) o.px = o.x; - if (isNaN(o.py)) o.py = o.y; - } - charges = []; - if (typeof charge === "function") { - for (i = 0; i < n; ++i) { - charges[i] = +charge.call(this, nodes[i], i); - } - } else { - for (i = 0; i < n; ++i) { - charges[i] = charge; - } - } - return force.resume(); - }; - force.resume = function() { - return force.alpha(.1); - }; - force.stop = function() { - return force.alpha(0); - }; - force.drag = function() { - if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart", d3_layout_forceDragstart).on("drag", dragmove).on("dragend", d3_layout_forceDragend); - this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); - }; - return d3.rebind(force, event, "on"); - }; - d3.layout.partition = function() { - function position(node, x, dx, dy) { - var children = node.children; - node.x = x; - node.y = node.depth * dy; - node.dx = dx; - node.dy = dy; - if (children && (n = children.length)) { - var i = -1, n, c, d; - dx = node.value ? dx / node.value : 0; - while (++i < n) { - position(c = children[i], x, d = c.value * dx, dy); - x += d; - } - } - } - function depth(node) { - var children = node.children, d = 0; - if (children && (n = children.length)) { - var i = -1, n; - while (++i < n) d = Math.max(d, depth(children[i])); - } - return 1 + d; - } - function partition(d, i) { - var nodes = hierarchy.call(this, d, i); - position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); - return nodes; - } - var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; - partition.size = function(x) { - if (!arguments.length) return size; - size = x; - return partition; - }; - return d3_layout_hierarchyRebind(partition, hierarchy); - }; - d3.layout.pie = function() { - function pie(data, i) { - var values = data.map(function(d, i) { - return +value.call(pie, d, i); - }); - var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle); - var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - startAngle) / d3.sum(values); - var index = d3.range(data.length); - if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { - return values[j] - values[i]; - } : function(i, j) { - return sort(data[i], data[j]); - }); - var arcs = []; - index.forEach(function(i) { - var d; - arcs[i] = { - data: data[i], - value: d = values[i], - startAngle: a, - endAngle: a += d * k - }; - }); - return arcs; - } - var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * Math.PI; - pie.value = function(x) { - if (!arguments.length) return value; - value = x; - return pie; - }; - pie.sort = function(x) { - if (!arguments.length) return sort; - sort = x; - return pie; - }; - pie.startAngle = function(x) { - if (!arguments.length) return startAngle; - startAngle = x; - return pie; - }; - pie.endAngle = function(x) { - if (!arguments.length) return endAngle; - endAngle = x; - return pie; - }; - return pie; - }; - var d3_layout_pieSortByValue = {}; - d3.layout.stack = function() { - function stack(data, index) { - var series = data.map(function(d, i) { - return values.call(stack, d, i); - }); - var points = series.map(function(d, i) { - return d.map(function(v, i) { - return [ x.call(stack, v, i), y.call(stack, v, i) ]; - }); - }); - var orders = order.call(stack, points, index); - series = d3.permute(series, orders); - points = d3.permute(points, orders); - var offsets = offset.call(stack, points, index); - var n = series.length, m = series[0].length, i, j, o; - for (j = 0; j < m; ++j) { - out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); - for (i = 1; i < n; ++i) { - out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); - } - } - return data; - } - var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; - stack.values = function(x) { - if (!arguments.length) return values; - values = x; - return stack; - }; - stack.order = function(x) { - if (!arguments.length) return order; - order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; - return stack; - }; - stack.offset = function(x) { - if (!arguments.length) return offset; - offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; - return stack; - }; - stack.x = function(z) { - if (!arguments.length) return x; - x = z; - return stack; - }; - stack.y = function(z) { - if (!arguments.length) return y; - y = z; - return stack; - }; - stack.out = function(z) { - if (!arguments.length) return out; - out = z; - return stack; - }; - return stack; - }; - var d3_layout_stackOrders = d3.map({ - "inside-out": function(data) { - var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { - return max[a] - max[b]; - }), top = 0, bottom = 0, tops = [], bottoms = []; - for (i = 0; i < n; ++i) { - j = index[i]; - if (top < bottom) { - top += sums[j]; - tops.push(j); - } else { - bottom += sums[j]; - bottoms.push(j); - } - } - return bottoms.reverse().concat(tops); - }, - reverse: function(data) { - return d3.range(data.length).reverse(); - }, - "default": d3_layout_stackOrderDefault - }); - var d3_layout_stackOffsets = d3.map({ - silhouette: function(data) { - var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; - for (j = 0; j < m; ++j) { - for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; - if (o > max) max = o; - sums.push(o); - } - for (j = 0; j < m; ++j) { - y0[j] = (max - sums[j]) / 2; - } - return y0; - }, - wiggle: function(data) { - var n = data.length, x = data[0], m = x.length, max = 0, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; - y0[0] = o = o0 = 0; - for (j = 1; j < m; ++j) { - for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; - for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { - for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { - s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; - } - s2 += s3 * data[i][j][1]; - } - y0[j] = o -= s1 ? s2 / s1 * dx : 0; - if (o < o0) o0 = o; - } - for (j = 0; j < m; ++j) y0[j] -= o0; - return y0; - }, - expand: function(data) { - var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; - for (j = 0; j < m; ++j) { - for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; - if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; - } - for (j = 0; j < m; ++j) y0[j] = 0; - return y0; - }, - zero: d3_layout_stackOffsetZero - }); - d3.layout.histogram = function() { - function histogram(data, i) { - var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; - while (++i < m) { - bin = bins[i] = []; - bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); - bin.y = 0; - } - if (m > 0) { - i = -1; - while (++i < n) { - x = values[i]; - if (x >= range[0] && x <= range[1]) { - bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; - bin.y += k; - bin.push(data[i]); - } - } - } - return bins; - } - var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; - histogram.value = function(x) { - if (!arguments.length) return valuer; - valuer = x; - return histogram; - }; - histogram.range = function(x) { - if (!arguments.length) return ranger; - ranger = d3_functor(x); - return histogram; - }; - histogram.bins = function(x) { - if (!arguments.length) return binner; - binner = typeof x === "number" ? function(range) { - return d3_layout_histogramBinFixed(range, x); - } : d3_functor(x); - return histogram; - }; - histogram.frequency = function(x) { - if (!arguments.length) return frequency; - frequency = !!x; - return histogram; - }; - return histogram; - }; - d3.layout.hierarchy = function() { - function recurse(data, depth, nodes) { - var childs = children.call(hierarchy, data, depth), node = d3_layout_hierarchyInline ? data : { - data: data - }; - node.depth = depth; - nodes.push(node); - if (childs && (n = childs.length)) { - var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d; - while (++i < n) { - d = recurse(childs[i], j, nodes); - d.parent = node; - c.push(d); - v += d.value; - } - if (sort) c.sort(sort); - if (value) node.value = v; - } else if (value) { - node.value = +value.call(hierarchy, data, depth) || 0; - } - return node; - } - function revalue(node, depth) { - var children = node.children, v = 0; - if (children && (n = children.length)) { - var i = -1, n, j = depth + 1; - while (++i < n) v += revalue(children[i], j); - } else if (value) { - v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; - } - if (value) node.value = v; - return v; - } - function hierarchy(d) { - var nodes = []; - recurse(d, 0, nodes); - return nodes; - } - var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; - hierarchy.sort = function(x) { - if (!arguments.length) return sort; - sort = x; - return hierarchy; - }; - hierarchy.children = function(x) { - if (!arguments.length) return children; - children = x; - return hierarchy; - }; - hierarchy.value = function(x) { - if (!arguments.length) return value; - value = x; - return hierarchy; - }; - hierarchy.revalue = function(root) { - revalue(root, 0); - return root; - }; - return hierarchy; - }; - var d3_layout_hierarchyInline = false; - d3.layout.pack = function() { - function pack(d, i) { - var nodes = hierarchy.call(this, d, i), root = nodes[0]; - root.x = 0; - root.y = 0; - d3_layout_treeVisitAfter(root, function(d) { - d.r = Math.sqrt(d.value); - }); - d3_layout_treeVisitAfter(root, d3_layout_packSiblings); - var w = size[0], h = size[1], k = Math.max(2 * root.r / w, 2 * root.r / h); - if (padding > 0) { - var dr = padding * k / 2; - d3_layout_treeVisitAfter(root, function(d) { - d.r += dr; - }); - d3_layout_treeVisitAfter(root, d3_layout_packSiblings); - d3_layout_treeVisitAfter(root, function(d) { - d.r -= dr; - }); - k = Math.max(2 * root.r / w, 2 * root.r / h); - } - d3_layout_packTransform(root, w / 2, h / 2, 1 / k); - return nodes; - } - var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; - pack.size = function(x) { - if (!arguments.length) return size; - size = x; - return pack; - }; - pack.padding = function(_) { - if (!arguments.length) return padding; - padding = +_; - return pack; - }; - return d3_layout_hierarchyRebind(pack, hierarchy); - }; - d3.layout.cluster = function() { - function cluster(d, i) { - var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0, kx, ky; - d3_layout_treeVisitAfter(root, function(node) { - var children = node.children; - if (children && children.length) { - node.x = d3_layout_clusterX(children); - node.y = d3_layout_clusterY(children); - } else { - node.x = previousNode ? x += separation(node, previousNode) : 0; - node.y = 0; - previousNode = node; - } - }); - var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; - d3_layout_treeVisitAfter(root, function(node) { - node.x = (node.x - x0) / (x1 - x0) * size[0]; - node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; - }); - return nodes; - } - var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; - cluster.separation = function(x) { - if (!arguments.length) return separation; - separation = x; - return cluster; - }; - cluster.size = function(x) { - if (!arguments.length) return size; - size = x; - return cluster; - }; - return d3_layout_hierarchyRebind(cluster, hierarchy); - }; - d3.layout.tree = function() { - function tree(d, i) { - function firstWalk(node, previousSibling) { - var children = node.children, layout = node._tree; - if (children && (n = children.length)) { - var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1; - while (++i < n) { - child = children[i]; - firstWalk(child, previousChild); - ancestor = apportion(child, previousChild, ancestor); - previousChild = child; - } - d3_layout_treeShift(node); - var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); - if (previousSibling) { - layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); - layout.mod = layout.prelim - midpoint; - } else { - layout.prelim = midpoint; - } - } else { - if (previousSibling) { - layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); - } - } - } - function secondWalk(node, x) { - node.x = node._tree.prelim + x; - var children = node.children; - if (children && (n = children.length)) { - var i = -1, n; - x += node._tree.mod; - while (++i < n) { - secondWalk(children[i], x); - } - } - } - function apportion(node, previousSibling, ancestor) { - if (previousSibling) { - var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift; - while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { - vom = d3_layout_treeLeft(vom); - vop = d3_layout_treeRight(vop); - vop._tree.ancestor = node; - shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); - if (shift > 0) { - d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); - sip += shift; - sop += shift; - } - sim += vim._tree.mod; - sip += vip._tree.mod; - som += vom._tree.mod; - sop += vop._tree.mod; - } - if (vim && !d3_layout_treeRight(vop)) { - vop._tree.thread = vim; - vop._tree.mod += sim - sop; - } - if (vip && !d3_layout_treeLeft(vom)) { - vom._tree.thread = vip; - vom._tree.mod += sip - som; - ancestor = node; - } - } - return ancestor; - } - var nodes = hierarchy.call(this, d, i), root = nodes[0]; - d3_layout_treeVisitAfter(root, function(node, previousSibling) { - node._tree = { - ancestor: node, - prelim: 0, - mod: 0, - change: 0, - shift: 0, - number: previousSibling ? previousSibling._tree.number + 1 : 0 - }; - }); - firstWalk(root); - secondWalk(root, -root._tree.prelim); - var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1; - d3_layout_treeVisitAfter(root, function(node) { - node.x = (node.x - x0) / (x1 - x0) * size[0]; - node.y = node.depth / y1 * size[1]; - delete node._tree; - }); - return nodes; - } - var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; - tree.separation = function(x) { - if (!arguments.length) return separation; - separation = x; - return tree; - }; - tree.size = function(x) { - if (!arguments.length) return size; - size = x; - return tree; - }; - return d3_layout_hierarchyRebind(tree, hierarchy); - }; - d3.layout.treemap = function() { - function scale(children, k) { - var i = -1, n = children.length, child, area; - while (++i < n) { - area = (child = children[i]).value * (k < 0 ? 0 : k); - child.area = isNaN(area) || area <= 0 ? 0 : area; - } - } - function squarify(node) { - var children = node.children; - if (children && children.length) { - var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = Math.min(rect.dx, rect.dy), n; - scale(remaining, rect.dx * rect.dy / node.value); - row.area = 0; - while ((n = remaining.length) > 0) { - row.push(child = remaining[n - 1]); - row.area += child.area; - if ((score = worst(row, u)) <= best) { - remaining.pop(); - best = score; - } else { - row.area -= row.pop().area; - position(row, u, rect, false); - u = Math.min(rect.dx, rect.dy); - row.length = row.area = 0; - best = Infinity; - } - } - if (row.length) { - position(row, u, rect, true); - row.length = row.area = 0; - } - children.forEach(squarify); - } - } - function stickify(node) { - var children = node.children; - if (children && children.length) { - var rect = pad(node), remaining = children.slice(), child, row = []; - scale(remaining, rect.dx * rect.dy / node.value); - row.area = 0; - while (child = remaining.pop()) { - row.push(child); - row.area += child.area; - if (child.z != null) { - position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); - row.length = row.area = 0; - } - } - children.forEach(stickify); - } - } - function worst(row, u) { - var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; - while (++i < n) { - if (!(r = row[i].area)) continue; - if (r < rmin) rmin = r; - if (r > rmax) rmax = r; - } - s *= s; - u *= u; - return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; - } - function position(row, u, rect, flush) { - var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; - if (u == rect.dx) { - if (flush || v > rect.dy) v = rect.dy; - while (++i < n) { - o = row[i]; - o.x = x; - o.y = y; - o.dy = v; - x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); - } - o.z = true; - o.dx += rect.x + rect.dx - x; - rect.y += v; - rect.dy -= v; - } else { - if (flush || v > rect.dx) v = rect.dx; - while (++i < n) { - o = row[i]; - o.x = x; - o.y = y; - o.dx = v; - y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); - } - o.z = false; - o.dy += rect.y + rect.dy - y; - rect.x += v; - rect.dx -= v; - } - } - function treemap(d) { - var nodes = stickies || hierarchy(d), root = nodes[0]; - root.x = 0; - root.y = 0; - root.dx = size[0]; - root.dy = size[1]; - if (stickies) hierarchy.revalue(root); - scale([ root ], root.dx * root.dy / root.value); - (stickies ? stickify : squarify)(root); - if (sticky) stickies = nodes; - return nodes; - } - var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, ratio = .5 * (1 + Math.sqrt(5)); - treemap.size = function(x) { - if (!arguments.length) return size; - size = x; - return treemap; - }; - treemap.padding = function(x) { - function padFunction(node) { - var p = x.call(treemap, node, node.depth); - return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); - } - function padConstant(node) { - return d3_layout_treemapPad(node, x); - } - if (!arguments.length) return padding; - var type; - pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], padConstant) : padConstant; - return treemap; - }; - treemap.round = function(x) { - if (!arguments.length) return round != Number; - round = x ? Math.round : Number; - return treemap; - }; - treemap.sticky = function(x) { - if (!arguments.length) return sticky; - sticky = x; - stickies = null; - return treemap; - }; - treemap.ratio = function(x) { - if (!arguments.length) return ratio; - ratio = x; - return treemap; - }; - return d3_layout_hierarchyRebind(treemap, hierarchy); - }; - d3.csv = d3_dsv(",", "text/csv"); - d3.tsv = d3_dsv("\t", "text/tab-separated-values"); - d3.geo = {}; - var d3_geo_radians = Math.PI / 180; - d3.geo.azimuthal = function() { - function azimuthal(coordinates) { - var x1 = coordinates[0] * d3_geo_radians - x0, y1 = coordinates[1] * d3_geo_radians, cx1 = Math.cos(x1), sx1 = Math.sin(x1), cy1 = Math.cos(y1), sy1 = Math.sin(y1), cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null, c, k = mode === "stereographic" ? 1 / (1 + cc) : mode === "gnomonic" ? 1 / cc : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0) : mode === "equalarea" ? Math.sqrt(2 / (1 + cc)) : 1, x = k * cy1 * sx1, y = k * (sy0 * cy1 * cx1 - cy0 * sy1); - return [ scale * x + translate[0], scale * y + translate[1] ]; - } - var mode = "orthographic", origin, scale = 200, translate = [ 480, 250 ], x0, y0, cy0, sy0; - azimuthal.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p = Math.sqrt(x * x + y * y), c = mode === "stereographic" ? 2 * Math.atan(p) : mode === "gnomonic" ? Math.atan(p) : mode === "equidistant" ? p : mode === "equalarea" ? 2 * Math.asin(.5 * p) : Math.asin(p), sc = Math.sin(c), cc = Math.cos(c); - return [ (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians, Math.asin(cc * sy0 - (p ? y * sc * cy0 / p : 0)) / d3_geo_radians ]; - }; - azimuthal.mode = function(x) { - if (!arguments.length) return mode; - mode = x + ""; - return azimuthal; - }; - azimuthal.origin = function(x) { - if (!arguments.length) return origin; - origin = x; - x0 = origin[0] * d3_geo_radians; - y0 = origin[1] * d3_geo_radians; - cy0 = Math.cos(y0); - sy0 = Math.sin(y0); - return azimuthal; - }; - azimuthal.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return azimuthal; - }; - azimuthal.translate = function(x) { - if (!arguments.length) return translate; - translate = [ +x[0], +x[1] ]; - return azimuthal; - }; - return azimuthal.origin([ 0, 0 ]); - }; - d3.geo.albers = function() { - function albers(coordinates) { - var t = n * (d3_geo_radians * coordinates[0] - lng0), p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n; - return [ scale * p * Math.sin(t) + translate[0], scale * (p * Math.cos(t) - p0) + translate[1] ]; - } - function reload() { - var phi1 = d3_geo_radians * parallels[0], phi2 = d3_geo_radians * parallels[1], lat0 = d3_geo_radians * origin[1], s = Math.sin(phi1), c = Math.cos(phi1); - lng0 = d3_geo_radians * origin[0]; - n = .5 * (s + Math.sin(phi2)); - C = c * c + 2 * n * s; - p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n; - return albers; - } - var origin = [ -98, 38 ], parallels = [ 29.5, 45.5 ], scale = 1e3, translate = [ 480, 250 ], lng0, n, C, p0; - albers.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p0y = p0 + y, t = Math.atan2(x, p0y), p = Math.sqrt(x * x + p0y * p0y); - return [ (lng0 + t / n) / d3_geo_radians, Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians ]; - }; - albers.origin = function(x) { - if (!arguments.length) return origin; - origin = [ +x[0], +x[1] ]; - return reload(); - }; - albers.parallels = function(x) { - if (!arguments.length) return parallels; - parallels = [ +x[0], +x[1] ]; - return reload(); - }; - albers.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return albers; - }; - albers.translate = function(x) { - if (!arguments.length) return translate; - translate = [ +x[0], +x[1] ]; - return albers; - }; - return reload(); - }; - d3.geo.albersUsa = function() { - function albersUsa(coordinates) { - var lon = coordinates[0], lat = coordinates[1]; - return (lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48)(coordinates); - } - var lower48 = d3.geo.albers(); - var alaska = d3.geo.albers().origin([ -160, 60 ]).parallels([ 55, 65 ]); - var hawaii = d3.geo.albers().origin([ -160, 20 ]).parallels([ 8, 18 ]); - var puertoRico = d3.geo.albers().origin([ -60, 10 ]).parallels([ 8, 18 ]); - albersUsa.scale = function(x) { - if (!arguments.length) return lower48.scale(); - lower48.scale(x); - alaska.scale(x * .6); - hawaii.scale(x); - puertoRico.scale(x * 1.5); - return albersUsa.translate(lower48.translate()); - }; - albersUsa.translate = function(x) { - if (!arguments.length) return lower48.translate(); - var dz = lower48.scale() / 1e3, dx = x[0], dy = x[1]; - lower48.translate(x); - alaska.translate([ dx - 400 * dz, dy + 170 * dz ]); - hawaii.translate([ dx - 190 * dz, dy + 200 * dz ]); - puertoRico.translate([ dx + 580 * dz, dy + 430 * dz ]); - return albersUsa; - }; - return albersUsa.scale(lower48.scale()); - }; - d3.geo.bonne = function() { - function bonne(coordinates) { - var x = coordinates[0] * d3_geo_radians - x0, y = coordinates[1] * d3_geo_radians - y0; - if (y1) { - var p = c1 + y1 - y, E = x * Math.cos(y) / p; - x = p * Math.sin(E); - y = p * Math.cos(E) - c1; - } else { - x *= Math.cos(y); - y *= -1; - } - return [ scale * x + translate[0], scale * y + translate[1] ]; - } - var scale = 200, translate = [ 480, 250 ], x0, y0, y1, c1; - bonne.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; - if (y1) { - var c = c1 + y, p = Math.sqrt(x * x + c * c); - y = c1 + y1 - p; - x = x0 + p * Math.atan2(x, c) / Math.cos(y); - } else { - y *= -1; - x /= Math.cos(y); - } - return [ x / d3_geo_radians, y / d3_geo_radians ]; - }; - bonne.parallel = function(x) { - if (!arguments.length) return y1 / d3_geo_radians; - c1 = 1 / Math.tan(y1 = x * d3_geo_radians); - return bonne; - }; - bonne.origin = function(x) { - if (!arguments.length) return [ x0 / d3_geo_radians, y0 / d3_geo_radians ]; - x0 = x[0] * d3_geo_radians; - y0 = x[1] * d3_geo_radians; - return bonne; - }; - bonne.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return bonne; - }; - bonne.translate = function(x) { - if (!arguments.length) return translate; - translate = [ +x[0], +x[1] ]; - return bonne; - }; - return bonne.origin([ 0, 0 ]).parallel(45); - }; - d3.geo.equirectangular = function() { - function equirectangular(coordinates) { - var x = coordinates[0] / 360, y = -coordinates[1] / 360; - return [ scale * x + translate[0], scale * y + translate[1] ]; - } - var scale = 500, translate = [ 480, 250 ]; - equirectangular.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; - return [ 360 * x, -360 * y ]; - }; - equirectangular.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return equirectangular; - }; - equirectangular.translate = function(x) { - if (!arguments.length) return translate; - translate = [ +x[0], +x[1] ]; - return equirectangular; - }; - return equirectangular; - }; - d3.geo.mercator = function() { - function mercator(coordinates) { - var x = coordinates[0] / 360, y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360; - return [ scale * x + translate[0], scale * Math.max(-.5, Math.min(.5, y)) + translate[1] ]; - } - var scale = 500, translate = [ 480, 250 ]; - mercator.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; - return [ 360 * x, 2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90 ]; - }; - mercator.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return mercator; - }; - mercator.translate = function(x) { - if (!arguments.length) return translate; - translate = [ +x[0], +x[1] ]; - return mercator; - }; - return mercator; - }; - d3.geo.path = function() { - function path(d, i) { - if (typeof pointRadius === "function") pointCircle = d3_path_circle(pointRadius.apply(this, arguments)); - pathType(d); - var result = buffer.length ? buffer.join("") : null; - buffer = []; - return result; - } - function project(coordinates) { - return projection(coordinates).join(","); - } - function polygonArea(coordinates) { - var sum = area(coordinates[0]), i = 0, n = coordinates.length; - while (++i < n) sum -= area(coordinates[i]); - return sum; - } - function polygonCentroid(coordinates) { - var polygon = d3.geom.polygon(coordinates[0].map(projection)), area = polygon.area(), centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), x = centroid[0], y = centroid[1], z = area, i = 0, n = coordinates.length; - while (++i < n) { - polygon = d3.geom.polygon(coordinates[i].map(projection)); - area = polygon.area(); - centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1); - x -= centroid[0]; - y -= centroid[1]; - z -= area; - } - return [ x, y, 6 * z ]; - } - function area(coordinates) { - return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); - } - var pointRadius = 4.5, pointCircle = d3_path_circle(pointRadius), projection = d3.geo.albersUsa(), buffer = []; - var pathType = d3_geo_type({ - FeatureCollection: function(o) { - var features = o.features, i = -1, n = features.length; - while (++i < n) buffer.push(pathType(features[i].geometry)); - }, - Feature: function(o) { - pathType(o.geometry); - }, - Point: function(o) { - buffer.push("M", project(o.coordinates), pointCircle); - }, - MultiPoint: function(o) { - var coordinates = o.coordinates, i = -1, n = coordinates.length; - while (++i < n) buffer.push("M", project(coordinates[i]), pointCircle); - }, - LineString: function(o) { - var coordinates = o.coordinates, i = -1, n = coordinates.length; - buffer.push("M"); - while (++i < n) buffer.push(project(coordinates[i]), "L"); - buffer.pop(); - }, - MultiLineString: function(o) { - var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m; - while (++i < n) { - subcoordinates = coordinates[i]; - j = -1; - m = subcoordinates.length; - buffer.push("M"); - while (++j < m) buffer.push(project(subcoordinates[j]), "L"); - buffer.pop(); - } - }, - Polygon: function(o) { - var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m; - while (++i < n) { - subcoordinates = coordinates[i]; - j = -1; - if ((m = subcoordinates.length - 1) > 0) { - buffer.push("M"); - while (++j < m) buffer.push(project(subcoordinates[j]), "L"); - buffer[buffer.length - 1] = "Z"; - } - } - }, - MultiPolygon: function(o) { - var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m, subsubcoordinates, k, p; - while (++i < n) { - subcoordinates = coordinates[i]; - j = -1; - m = subcoordinates.length; - while (++j < m) { - subsubcoordinates = subcoordinates[j]; - k = -1; - if ((p = subsubcoordinates.length - 1) > 0) { - buffer.push("M"); - while (++k < p) buffer.push(project(subsubcoordinates[k]), "L"); - buffer[buffer.length - 1] = "Z"; - } - } - } - }, - GeometryCollection: function(o) { - var geometries = o.geometries, i = -1, n = geometries.length; - while (++i < n) buffer.push(pathType(geometries[i])); - } - }); - var areaType = path.area = d3_geo_type({ - FeatureCollection: function(o) { - var area = 0, features = o.features, i = -1, n = features.length; - while (++i < n) area += areaType(features[i]); - return area; - }, - Feature: function(o) { - return areaType(o.geometry); - }, - Polygon: function(o) { - return polygonArea(o.coordinates); - }, - MultiPolygon: function(o) { - var sum = 0, coordinates = o.coordinates, i = -1, n = coordinates.length; - while (++i < n) sum += polygonArea(coordinates[i]); - return sum; - }, - GeometryCollection: function(o) { - var sum = 0, geometries = o.geometries, i = -1, n = geometries.length; - while (++i < n) sum += areaType(geometries[i]); - return sum; - } - }, 0); - var centroidType = path.centroid = d3_geo_type({ - Feature: function(o) { - return centroidType(o.geometry); - }, - Polygon: function(o) { - var centroid = polygonCentroid(o.coordinates); - return [ centroid[0] / centroid[2], centroid[1] / centroid[2] ]; - }, - MultiPolygon: function(o) { - var area = 0, coordinates = o.coordinates, centroid, x = 0, y = 0, z = 0, i = -1, n = coordinates.length; - while (++i < n) { - centroid = polygonCentroid(coordinates[i]); - x += centroid[0]; - y += centroid[1]; - z += centroid[2]; - } - return [ x / z, y / z ]; - } - }); - path.projection = function(x) { - projection = x; - return path; - }; - path.pointRadius = function(x) { - if (typeof x === "function") pointRadius = x; else { - pointRadius = +x; - pointCircle = d3_path_circle(pointRadius); - } - return path; - }; - return path; - }; - d3.geo.bounds = function(feature) { - var left = Infinity, bottom = Infinity, right = -Infinity, top = -Infinity; - d3_geo_bounds(feature, function(x, y) { - if (x < left) left = x; - if (x > right) right = x; - if (y < bottom) bottom = y; - if (y > top) top = y; - }); - return [ [ left, bottom ], [ right, top ] ]; - }; - var d3_geo_boundsTypes = { - Feature: d3_geo_boundsFeature, - FeatureCollection: d3_geo_boundsFeatureCollection, - GeometryCollection: d3_geo_boundsGeometryCollection, - LineString: d3_geo_boundsLineString, - MultiLineString: d3_geo_boundsMultiLineString, - MultiPoint: d3_geo_boundsLineString, - MultiPolygon: d3_geo_boundsMultiPolygon, - Point: d3_geo_boundsPoint, - Polygon: d3_geo_boundsPolygon - }; - d3.geo.circle = function() { - function circle() {} - function visible(point) { - return arc.distance(point) < radians; - } - function clip(coordinates) { - var i = -1, n = coordinates.length, clipped = [], p0, p1, p2, d0, d1; - while (++i < n) { - d1 = arc.distance(p2 = coordinates[i]); - if (d1 < radians) { - if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1))); - clipped.push(p2); - p0 = p1 = null; - } else { - p1 = p2; - if (!p0 && clipped.length) { - clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0))); - p0 = p1; - } - } - d0 = d1; - } - p0 = coordinates[0]; - p1 = clipped[0]; - if (p1 && p2[0] === p0[0] && p2[1] === p0[1] && !(p2[0] === p1[0] && p2[1] === p1[1])) { - clipped.push(p1); - } - return resample(clipped); - } - function resample(coordinates) { - var i = 0, n = coordinates.length, j, m, resampled = n ? [ coordinates[0] ] : coordinates, resamples, origin = arc.source(); - while (++i < n) { - resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates; - for (j = 0, m = resamples.length; ++j < m; ) resampled.push(resamples[j]); - } - arc.source(origin); - return resampled; - } - var origin = [ 0, 0 ], degrees = 90 - .01, radians = degrees * d3_geo_radians, arc = d3.geo.greatArc().source(origin).target(d3_identity); - circle.clip = function(d) { - if (typeof origin === "function") arc.source(origin.apply(this, arguments)); - return clipType(d) || null; - }; - var clipType = d3_geo_type({ - FeatureCollection: function(o) { - var features = o.features.map(clipType).filter(d3_identity); - return features && (o = Object.create(o), o.features = features, o); - }, - Feature: function(o) { - var geometry = clipType(o.geometry); - return geometry && (o = Object.create(o), o.geometry = geometry, o); - }, - Point: function(o) { - return visible(o.coordinates) && o; - }, - MultiPoint: function(o) { - var coordinates = o.coordinates.filter(visible); - return coordinates.length && { - type: o.type, - coordinates: coordinates - }; - }, - LineString: function(o) { - var coordinates = clip(o.coordinates); - return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); - }, - MultiLineString: function(o) { - var coordinates = o.coordinates.map(clip).filter(function(d) { - return d.length; - }); - return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); - }, - Polygon: function(o) { - var coordinates = o.coordinates.map(clip); - return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o); - }, - MultiPolygon: function(o) { - var coordinates = o.coordinates.map(function(d) { - return d.map(clip); - }).filter(function(d) { - return d[0].length; - }); - return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); - }, - GeometryCollection: function(o) { - var geometries = o.geometries.map(clipType).filter(d3_identity); - return geometries.length && (o = Object.create(o), o.geometries = geometries, o); - } - }); - circle.origin = function(x) { - if (!arguments.length) return origin; - origin = x; - if (typeof origin !== "function") arc.source(origin); - return circle; - }; - circle.angle = function(x) { - if (!arguments.length) return degrees; - radians = (degrees = +x) * d3_geo_radians; - return circle; - }; - return d3.rebind(circle, arc, "precision"); - }; - d3.geo.greatArc = function() { - function greatArc() { - var d = greatArc.distance.apply(this, arguments), t = 0, dt = precision / d, coordinates = [ p0 ]; - while ((t += dt) < 1) coordinates.push(interpolate(t)); - coordinates.push(p1); - return { - type: "LineString", - coordinates: coordinates - }; - } - var source = d3_geo_greatArcSource, p0, target = d3_geo_greatArcTarget, p1, precision = 6 * d3_geo_radians, interpolate = d3_geo_greatArcInterpolator(); - greatArc.distance = function() { - if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments)); - if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments)); - return interpolate.distance(); - }; - greatArc.source = function(_) { - if (!arguments.length) return source; - source = _; - if (typeof source !== "function") interpolate.source(p0 = source); - return greatArc; - }; - greatArc.target = function(_) { - if (!arguments.length) return target; - target = _; - if (typeof target !== "function") interpolate.target(p1 = target); - return greatArc; - }; - greatArc.precision = function(_) { - if (!arguments.length) return precision / d3_geo_radians; - precision = _ * d3_geo_radians; - return greatArc; - }; - return greatArc; - }; - d3.geo.greatCircle = d3.geo.circle; - d3.geom = {}; - d3.geom.contour = function(grid, start) { - var s = start || d3_geom_contourStart(grid), c = [], x = s[0], y = s[1], dx = 0, dy = 0, pdx = NaN, pdy = NaN, i = 0; - do { - i = 0; - if (grid(x - 1, y - 1)) i += 1; - if (grid(x, y - 1)) i += 2; - if (grid(x - 1, y)) i += 4; - if (grid(x, y)) i += 8; - if (i === 6) { - dx = pdy === -1 ? -1 : 1; - dy = 0; - } else if (i === 9) { - dx = 0; - dy = pdx === 1 ? -1 : 1; - } else { - dx = d3_geom_contourDx[i]; - dy = d3_geom_contourDy[i]; - } - if (dx != pdx && dy != pdy) { - c.push([ x, y ]); - pdx = dx; - pdy = dy; - } - x += dx; - y += dy; - } while (s[0] != x || s[1] != y); - return c; - }; - var d3_geom_contourDx = [ 1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 0, 0, -1, 0, -1, NaN ], d3_geom_contourDy = [ 0, -1, 0, 0, 0, -1, 0, 0, 1, -1, 1, 1, 0, -1, 0, NaN ]; - d3.geom.hull = function(vertices) { - if (vertices.length < 3) return []; - var len = vertices.length, plen = len - 1, points = [], stack = [], i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; - for (i = 1; i < len; ++i) { - if (vertices[i][1] < vertices[h][1]) { - h = i; - } else if (vertices[i][1] == vertices[h][1]) { - h = vertices[i][0] < vertices[h][0] ? i : h; - } - } - for (i = 0; i < len; ++i) { - if (i === h) continue; - y1 = vertices[i][1] - vertices[h][1]; - x1 = vertices[i][0] - vertices[h][0]; - points.push({ - angle: Math.atan2(y1, x1), - index: i - }); - } - points.sort(function(a, b) { - return a.angle - b.angle; - }); - a = points[0].angle; - v = points[0].index; - u = 0; - for (i = 1; i < plen; ++i) { - j = points[i].index; - if (a == points[i].angle) { - x1 = vertices[v][0] - vertices[h][0]; - y1 = vertices[v][1] - vertices[h][1]; - x2 = vertices[j][0] - vertices[h][0]; - y2 = vertices[j][1] - vertices[h][1]; - if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) { - points[i].index = -1; - } else { - points[u].index = -1; - a = points[i].angle; - u = i; - v = j; - } - } else { - a = points[i].angle; - u = i; - v = j; - } - } - stack.push(h); - for (i = 0, j = 0; i < 2; ++j) { - if (points[j].index !== -1) { - stack.push(points[j].index); - i++; - } - } - sp = stack.length; - for (; j < plen; ++j) { - if (points[j].index === -1) continue; - while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) { - --sp; - } - stack[sp++] = points[j].index; - } - var poly = []; - for (i = 0; i < sp; ++i) { - poly.push(vertices[stack[i]]); - } - return poly; - }; - d3.geom.polygon = function(coordinates) { - coordinates.area = function() { - var i = 0, n = coordinates.length, a = coordinates[n - 1][0] * coordinates[0][1], b = coordinates[n - 1][1] * coordinates[0][0]; - while (++i < n) { - a += coordinates[i - 1][0] * coordinates[i][1]; - b += coordinates[i - 1][1] * coordinates[i][0]; - } - return (b - a) * .5; - }; - coordinates.centroid = function(k) { - var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c; - if (!arguments.length) k = -1 / (6 * coordinates.area()); - while (++i < n) { - a = b; - b = coordinates[i]; - c = a[0] * b[1] - b[0] * a[1]; - x += (a[0] + b[0]) * c; - y += (a[1] + b[1]) * c; - } - return [ x * k, y * k ]; - }; - coordinates.clip = function(subject) { - var input, i = -1, n = coordinates.length, j, m, a = coordinates[n - 1], b, c, d; - while (++i < n) { - input = subject.slice(); - subject.length = 0; - b = coordinates[i]; - c = input[(m = input.length) - 1]; - j = -1; - while (++j < m) { - d = input[j]; - if (d3_geom_polygonInside(d, a, b)) { - if (!d3_geom_polygonInside(c, a, b)) { - subject.push(d3_geom_polygonIntersect(c, d, a, b)); - } - subject.push(d); - } else if (d3_geom_polygonInside(c, a, b)) { - subject.push(d3_geom_polygonIntersect(c, d, a, b)); - } - c = d; - } - a = b; - } - return subject; - }; - return coordinates; - }; - d3.geom.voronoi = function(vertices) { - var polygons = vertices.map(function() { - return []; - }); - d3_voronoi_tessellate(vertices, function(e) { - var s1, s2, x1, x2, y1, y2; - if (e.a === 1 && e.b >= 0) { - s1 = e.ep.r; - s2 = e.ep.l; - } else { - s1 = e.ep.l; - s2 = e.ep.r; - } - if (e.a === 1) { - y1 = s1 ? s1.y : -1e6; - x1 = e.c - e.b * y1; - y2 = s2 ? s2.y : 1e6; - x2 = e.c - e.b * y2; - } else { - x1 = s1 ? s1.x : -1e6; - y1 = e.c - e.a * x1; - x2 = s2 ? s2.x : 1e6; - y2 = e.c - e.a * x2; - } - var v1 = [ x1, y1 ], v2 = [ x2, y2 ]; - polygons[e.region.l.index].push(v1, v2); - polygons[e.region.r.index].push(v1, v2); - }); - return polygons.map(function(polygon, i) { - var cx = vertices[i][0], cy = vertices[i][1]; - polygon.forEach(function(v) { - v.angle = Math.atan2(v[0] - cx, v[1] - cy); - }); - return polygon.sort(function(a, b) { - return a.angle - b.angle; - }).filter(function(d, i) { - return !i || d.angle - polygon[i - 1].angle > 1e-10; - }); - }); - }; - var d3_voronoi_opposite = { - l: "r", - r: "l" - }; - d3.geom.delaunay = function(vertices) { - var edges = vertices.map(function() { - return []; - }), triangles = []; - d3_voronoi_tessellate(vertices, function(e) { - edges[e.region.l.index].push(vertices[e.region.r.index]); - }); - edges.forEach(function(edge, i) { - var v = vertices[i], cx = v[0], cy = v[1]; - edge.forEach(function(v) { - v.angle = Math.atan2(v[0] - cx, v[1] - cy); - }); - edge.sort(function(a, b) { - return a.angle - b.angle; - }); - for (var j = 0, m = edge.length - 1; j < m; j++) { - triangles.push([ v, edge[j], edge[j + 1] ]); - } - }); - return triangles; - }; - d3.geom.quadtree = function(points, x1, y1, x2, y2) { - function insert(n, p, x1, y1, x2, y2) { - if (isNaN(p.x) || isNaN(p.y)) return; - if (n.leaf) { - var v = n.point; - if (v) { - if (Math.abs(v.x - p.x) + Math.abs(v.y - p.y) < .01) { - insertChild(n, p, x1, y1, x2, y2); - } else { - n.point = null; - insertChild(n, v, x1, y1, x2, y2); - insertChild(n, p, x1, y1, x2, y2); - } - } else { - n.point = p; - } - } else { - insertChild(n, p, x1, y1, x2, y2); - } - } - function insertChild(n, p, x1, y1, x2, y2) { - var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = p.x >= sx, bottom = p.y >= sy, i = (bottom << 1) + right; - n.leaf = false; - n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); - if (right) x1 = sx; else x2 = sx; - if (bottom) y1 = sy; else y2 = sy; - insert(n, p, x1, y1, x2, y2); - } - var p, i = -1, n = points.length; - if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); - if (arguments.length < 5) { - if (arguments.length === 3) { - y2 = x2 = y1; - y1 = x1; - } else { - x1 = y1 = Infinity; - x2 = y2 = -Infinity; - while (++i < n) { - p = points[i]; - if (p.x < x1) x1 = p.x; - if (p.y < y1) y1 = p.y; - if (p.x > x2) x2 = p.x; - if (p.y > y2) y2 = p.y; - } - var dx = x2 - x1, dy = y2 - y1; - if (dx > dy) y2 = y1 + dx; else x2 = x1 + dy; - } - } - var root = d3_geom_quadtreeNode(); - root.add = function(p) { - insert(root, p, x1, y1, x2, y2); - }; - root.visit = function(f) { - d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); - }; - points.forEach(root.add); - return root; - }; - d3.time = {}; - var d3_time = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; - d3_time_utc.prototype = { - getDate: function() { - return this._.getUTCDate(); - }, - getDay: function() { - return this._.getUTCDay(); - }, - getFullYear: function() { - return this._.getUTCFullYear(); - }, - getHours: function() { - return this._.getUTCHours(); - }, - getMilliseconds: function() { - return this._.getUTCMilliseconds(); - }, - getMinutes: function() { - return this._.getUTCMinutes(); - }, - getMonth: function() { - return this._.getUTCMonth(); - }, - getSeconds: function() { - return this._.getUTCSeconds(); - }, - getTime: function() { - return this._.getTime(); - }, - getTimezoneOffset: function() { - return 0; - }, - valueOf: function() { - return this._.valueOf(); - }, - setDate: function() { - d3_time_prototype.setUTCDate.apply(this._, arguments); - }, - setDay: function() { - d3_time_prototype.setUTCDay.apply(this._, arguments); - }, - setFullYear: function() { - d3_time_prototype.setUTCFullYear.apply(this._, arguments); - }, - setHours: function() { - d3_time_prototype.setUTCHours.apply(this._, arguments); - }, - setMilliseconds: function() { - d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); - }, - setMinutes: function() { - d3_time_prototype.setUTCMinutes.apply(this._, arguments); - }, - setMonth: function() { - d3_time_prototype.setUTCMonth.apply(this._, arguments); - }, - setSeconds: function() { - d3_time_prototype.setUTCSeconds.apply(this._, arguments); - }, - setTime: function() { - d3_time_prototype.setTime.apply(this._, arguments); - } - }; - var d3_time_prototype = Date.prototype; - var d3_time_formatDateTime = "%a %b %e %H:%M:%S %Y", d3_time_formatDate = "%m/%d/%y", d3_time_formatTime = "%H:%M:%S"; - var d3_time_days = d3_time_daySymbols, d3_time_dayAbbreviations = d3_time_days.map(d3_time_formatAbbreviate), d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = d3_time_months.map(d3_time_formatAbbreviate); - d3.time.format = function(template) { - function format(date) { - var string = [], i = -1, j = 0, c, f; - while (++i < n) { - if (template.charCodeAt(i) == 37) { - string.push(template.substring(j, i), (f = d3_time_formats[c = template.charAt(++i)]) ? f(date) : c); - j = i + 1; - } - } - string.push(template.substring(j, i)); - return string.join(""); - } - var n = template.length; - format.parse = function(string) { - var d = { - y: 1900, - m: 0, - d: 1, - H: 0, - M: 0, - S: 0, - L: 0 - }, i = d3_time_parse(d, template, string, 0); - if (i != string.length) return null; - if ("p" in d) d.H = d.H % 12 + d.p * 12; - var date = new d3_time; - date.setFullYear(d.y, d.m, d.d); - date.setHours(d.H, d.M, d.S, d.L); - return date; - }; - format.toString = function() { - return template; - }; - return format; - }; - var d3_time_zfill2 = d3.format("02d"), d3_time_zfill3 = d3.format("03d"), d3_time_zfill4 = d3.format("04d"), d3_time_sfill2 = d3.format("2d"); - var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations); - var d3_time_formats = { - a: function(d) { - return d3_time_dayAbbreviations[d.getDay()]; - }, - A: function(d) { - return d3_time_days[d.getDay()]; - }, - b: function(d) { - return d3_time_monthAbbreviations[d.getMonth()]; - }, - B: function(d) { - return d3_time_months[d.getMonth()]; - }, - c: d3.time.format(d3_time_formatDateTime), - d: function(d) { - return d3_time_zfill2(d.getDate()); - }, - e: function(d) { - return d3_time_sfill2(d.getDate()); - }, - H: function(d) { - return d3_time_zfill2(d.getHours()); - }, - I: function(d) { - return d3_time_zfill2(d.getHours() % 12 || 12); - }, - j: function(d) { - return d3_time_zfill3(1 + d3.time.dayOfYear(d)); - }, - L: function(d) { - return d3_time_zfill3(d.getMilliseconds()); - }, - m: function(d) { - return d3_time_zfill2(d.getMonth() + 1); - }, - M: function(d) { - return d3_time_zfill2(d.getMinutes()); - }, - p: function(d) { - return d.getHours() >= 12 ? "PM" : "AM"; - }, - S: function(d) { - return d3_time_zfill2(d.getSeconds()); - }, - U: function(d) { - return d3_time_zfill2(d3.time.sundayOfYear(d)); - }, - w: function(d) { - return d.getDay(); - }, - W: function(d) { - return d3_time_zfill2(d3.time.mondayOfYear(d)); - }, - x: d3.time.format(d3_time_formatDate), - X: d3.time.format(d3_time_formatTime), - y: function(d) { - return d3_time_zfill2(d.getFullYear() % 100); - }, - Y: function(d) { - return d3_time_zfill4(d.getFullYear() % 1e4); - }, - Z: d3_time_zone, - "%": function(d) { - return "%"; - } - }; - var d3_time_parsers = { - a: d3_time_parseWeekdayAbbrev, - A: d3_time_parseWeekday, - b: d3_time_parseMonthAbbrev, - B: d3_time_parseMonth, - c: d3_time_parseLocaleFull, - d: d3_time_parseDay, - e: d3_time_parseDay, - H: d3_time_parseHour24, - I: d3_time_parseHour24, - L: d3_time_parseMilliseconds, - m: d3_time_parseMonthNumber, - M: d3_time_parseMinutes, - p: d3_time_parseAmPm, - S: d3_time_parseSeconds, - x: d3_time_parseLocaleDate, - X: d3_time_parseLocaleTime, - y: d3_time_parseYear, - Y: d3_time_parseFullYear - }; - var d3_time_numberRe = /^\s*\d+/; - var d3_time_amPmLookup = d3.map({ - am: 0, - pm: 1 - }); - d3.time.format.utc = function(template) { - function format(date) { - try { - d3_time = d3_time_utc; - var utc = new d3_time; - utc._ = date; - return local(utc); - } finally { - d3_time = Date; - } - } - var local = d3.time.format(template); - format.parse = function(string) { - try { - d3_time = d3_time_utc; - var date = local.parse(string); - return date && date._; - } finally { - d3_time = Date; - } - }; - format.toString = local.toString; - return format; - }; - var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); - d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso; - d3_time_formatIsoNative.parse = function(string) { - var date = new Date(string); - return isNaN(date) ? null : date; - }; - d3_time_formatIsoNative.toString = d3_time_formatIso.toString; - d3.time.second = d3_time_interval(function(date) { - return new d3_time(Math.floor(date / 1e3) * 1e3); - }, function(date, offset) { - date.setTime(date.getTime() + Math.floor(offset) * 1e3); - }, function(date) { - return date.getSeconds(); - }); - d3.time.seconds = d3.time.second.range; - d3.time.seconds.utc = d3.time.second.utc.range; - d3.time.minute = d3_time_interval(function(date) { - return new d3_time(Math.floor(date / 6e4) * 6e4); - }, function(date, offset) { - date.setTime(date.getTime() + Math.floor(offset) * 6e4); - }, function(date) { - return date.getMinutes(); - }); - d3.time.minutes = d3.time.minute.range; - d3.time.minutes.utc = d3.time.minute.utc.range; - d3.time.hour = d3_time_interval(function(date) { - var timezone = date.getTimezoneOffset() / 60; - return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); - }, function(date, offset) { - date.setTime(date.getTime() + Math.floor(offset) * 36e5); - }, function(date) { - return date.getHours(); - }); - d3.time.hours = d3.time.hour.range; - d3.time.hours.utc = d3.time.hour.utc.range; - d3.time.day = d3_time_interval(function(date) { - var day = new d3_time(1970, 0); - day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); - return day; - }, function(date, offset) { - date.setDate(date.getDate() + offset); - }, function(date) { - return date.getDate() - 1; - }); - d3.time.days = d3.time.day.range; - d3.time.days.utc = d3.time.day.utc.range; - d3.time.dayOfYear = function(date) { - var year = d3.time.year(date); - return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); - }; - d3_time_daySymbols.forEach(function(day, i) { - day = day.toLowerCase(); - i = 7 - i; - var interval = d3.time[day] = d3_time_interval(function(date) { - (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); - return date; - }, function(date, offset) { - date.setDate(date.getDate() + Math.floor(offset) * 7); - }, function(date) { - var day = d3.time.year(date).getDay(); - return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); - }); - d3.time[day + "s"] = interval.range; - d3.time[day + "s"].utc = interval.utc.range; - d3.time[day + "OfYear"] = function(date) { - var day = d3.time.year(date).getDay(); - return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); - }; - }); - d3.time.week = d3.time.sunday; - d3.time.weeks = d3.time.sunday.range; - d3.time.weeks.utc = d3.time.sunday.utc.range; - d3.time.weekOfYear = d3.time.sundayOfYear; - d3.time.month = d3_time_interval(function(date) { - date = d3.time.day(date); - date.setDate(1); - return date; - }, function(date, offset) { - date.setMonth(date.getMonth() + offset); - }, function(date) { - return date.getMonth(); - }); - d3.time.months = d3.time.month.range; - d3.time.months.utc = d3.time.month.utc.range; - d3.time.year = d3_time_interval(function(date) { - date = d3.time.day(date); - date.setMonth(0, 1); - return date; - }, function(date, offset) { - date.setFullYear(date.getFullYear() + offset); - }, function(date) { - return date.getFullYear(); - }); - d3.time.years = d3.time.year.range; - d3.time.years.utc = d3.time.year.utc.range; - var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; - var d3_time_scaleLocalMethods = [ [ d3.time.second, 1 ], [ d3.time.second, 5 ], [ d3.time.second, 15 ], [ d3.time.second, 30 ], [ d3.time.minute, 1 ], [ d3.time.minute, 5 ], [ d3.time.minute, 15 ], [ d3.time.minute, 30 ], [ d3.time.hour, 1 ], [ d3.time.hour, 3 ], [ d3.time.hour, 6 ], [ d3.time.hour, 12 ], [ d3.time.day, 1 ], [ d3.time.day, 2 ], [ d3.time.week, 1 ], [ d3.time.month, 1 ], [ d3.time.month, 3 ], [ d3.time.year, 1 ] ]; - var d3_time_scaleLocalFormats = [ [ d3.time.format("%Y"), function(d) { - return true; - } ], [ d3.time.format("%B"), function(d) { - return d.getMonth(); - } ], [ d3.time.format("%b %d"), function(d) { - return d.getDate() != 1; - } ], [ d3.time.format("%a %d"), function(d) { - return d.getDay() && d.getDate() != 1; - } ], [ d3.time.format("%I %p"), function(d) { - return d.getHours(); - } ], [ d3.time.format("%I:%M"), function(d) { - return d.getMinutes(); - } ], [ d3.time.format(":%S"), function(d) { - return d.getSeconds(); - } ], [ d3.time.format(".%L"), function(d) { - return d.getMilliseconds(); - } ] ]; - var d3_time_scaleLinear = d3.scale.linear(), d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats); - d3_time_scaleLocalMethods.year = function(extent, m) { - return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear); - }; - d3.time.scale = function() { - return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); - }; - var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { - return [ m[0].utc, m[1] ]; - }); - var d3_time_scaleUTCFormats = [ [ d3.time.format.utc("%Y"), function(d) { - return true; - } ], [ d3.time.format.utc("%B"), function(d) { - return d.getUTCMonth(); - } ], [ d3.time.format.utc("%b %d"), function(d) { - return d.getUTCDate() != 1; - } ], [ d3.time.format.utc("%a %d"), function(d) { - return d.getUTCDay() && d.getUTCDate() != 1; - } ], [ d3.time.format.utc("%I %p"), function(d) { - return d.getUTCHours(); - } ], [ d3.time.format.utc("%I:%M"), function(d) { - return d.getUTCMinutes(); - } ], [ d3.time.format.utc(":%S"), function(d) { - return d.getUTCSeconds(); - } ], [ d3.time.format.utc(".%L"), function(d) { - return d.getUTCMilliseconds(); - } ] ]; - var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats); - d3_time_scaleUTCMethods.year = function(extent, m) { - return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear); - }; - d3.time.scale.utc = function() { - return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); - }; -})(); \ No newline at end of file diff --git a/Modules/QtWidgetsWeb/src/QmitkHistogramJSWidget.cpp b/Modules/QtWidgetsWeb/src/QmitkHistogramJSWidget.cpp deleted file mode 100644 index e24898155c..0000000000 --- a/Modules/QtWidgetsWeb/src/QmitkHistogramJSWidget.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/*=================================================================== - -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" -#include "mitkBaseRenderer.h" -#include "mitkExtractSliceFilter.h" -#include "mitkImageTimeSelector.h" -#include "mitkPixelTypeMultiplex.h" -#include "mitkRenderingManager.h" -#include -#include - -QmitkHistogramJSWidget::QmitkHistogramJSWidget(QWidget *parent) : QWebEngineView(parent) -{ - // set histogram type to barchart in first instance - m_UseLineGraph = false; - m_Page = new QWebEnginePage(this); - setPage(m_Page); - // set html from source - // connect(page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(AddJSObject())); - QUrl myUrl = QUrl("qrc:///QtWidgetsWeb/Histogram.html"); - setUrl(myUrl); - - // set Scrollbars to be 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, using QtWebkitBridge -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) -{ - QWebEngineView::resizeEvent(resizeEvent); - - // workaround for Qt Bug: https://bugs.webkit.org/show_bug.cgi?id=75984 - // page()->mainFrame()->evaluateJavaScript("disconnectSignals()"); - 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_Histogram->Begin(); - ClearData(); - unsigned int i = 0; - bool firstValue = false; - // removes frequencies of 0, which are outside the first and last bin - for (; it != m_Histogram->End(); ++it) - { - if (it.GetFrequency() > 0.0) - { - endIt = it; - if (!firstValue) - { - firstValue = true; - startIt = it; - } - } - } - ++endIt; - // generating Lists of measurement and frequencies - for (it = startIt; it != endIt; ++it, ++i) - { - QVariant frequency = QVariant::fromValue(it.GetFrequency()); - QVariant measurement = it.GetMeasurementVector()[0]; - m_Frequency.insert(i, frequency); - m_Measurement.insert(i, measurement); - } - - m_IntensityProfile = false; - this->SignalDataChanged(); -} - -void QmitkHistogramJSWidget::ClearData() -{ - m_Frequency.clear(); - m_Measurement.clear(); -} - -void QmitkHistogramJSWidget::ClearHistogram() -{ - this->ClearData(); - this->SignalDataChanged(); -} - -QList QmitkHistogramJSWidget::GetFrequency() -{ - return m_Frequency; -} - -QList QmitkHistogramJSWidget::GetMeasurement() -{ - return m_Measurement; -} - -bool QmitkHistogramJSWidget::GetUseLineGraph() -{ - return m_UseLineGraph; -} - -void QmitkHistogramJSWidget::OnBarRadioButtonSelected() -{ - if (m_UseLineGraph) - { - m_UseLineGraph = false; - this->SignalGraphChanged(); - } -} - -void QmitkHistogramJSWidget::OnLineRadioButtonSelected() -{ - if (!m_UseLineGraph) - { - m_UseLineGraph = true; - this->SignalGraphChanged(); - } -} - -void QmitkHistogramJSWidget::SetImage(mitk::Image *image) -{ - m_Image = image; -} - -void QmitkHistogramJSWidget::SetPlanarFigure(const mitk::PlanarFigure *planarFigure) -{ - m_PlanarFigure = planarFigure; -} - -template -void ReadPixel(mitk::PixelType, mitk::Image::Pointer image, itk::Index<3> indexPoint, double &value) -{ - if (image->GetDimension() == 2) - { - mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData(0)); - itk::Index<2> idx; - idx[0] = indexPoint[0]; - idx[1] = indexPoint[1]; - value = readAccess.GetPixelByIndex(idx); - } - else if (image->GetDimension() == 3) - { - mitk::ImagePixelReadAccessor readAccess(image, image->GetVolumeData(0)); - itk::Index<3> idx; - idx[0] = indexPoint[0]; - idx[1] = indexPoint[1]; - idx[2] = indexPoint[2]; - value = readAccess.GetPixelByIndex(idx); - } - else - { - // unhandled - } -} - -void QmitkHistogramJSWidget::ComputeIntensityProfile(unsigned int timeStep, bool computeStatistics) -{ - this->ClearData(); - m_ParametricPath->Initialize(); - - if (m_PlanarFigure.IsNull()) - { - mitkThrow() << "PlanarFigure not set!"; - } - - if (m_Image.IsNull()) - { - mitkThrow() << "Image not set!"; - } - - mitk::Image::Pointer image; - - if (m_Image->GetDimension() == 4) - { - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(m_Image); - timeSelector->SetTimeNr(timeStep); - timeSelector->Update(); - - image = timeSelector->GetOutput(); - } - else - { - image = m_Image; - } - - mitk::IntensityProfile::Pointer intensityProfile = - mitk::ComputeIntensityProfile(image, const_cast(m_PlanarFigure.GetPointer())); - - m_Frequency.clear(); - m_Measurement.clear(); - - int i = -1; - mitk::IntensityProfile::ConstIterator end = intensityProfile->End(); - - for (mitk::IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) - { - m_Frequency.push_back(it.GetMeasurementVector()[0]); - m_Measurement.push_back(++i); - } - - if (computeStatistics) - { - mitk::ComputeIntensityProfileStatistics(intensityProfile, m_Statistics); - } - - m_IntensityProfile = true; - m_UseLineGraph = true; - this->SignalDataChanged(); -} - -bool QmitkHistogramJSWidget::GetIntensityProfile() -{ - return m_IntensityProfile; -} diff --git a/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp b/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp index 9c16220fe7..0189188fce 100644 --- a/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp +++ b/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp @@ -1,198 +1,200 @@ /*=================================================================== 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 "mitkFeatureBasedEdgeDetectionFilter.h" #include #include #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include mitk::FeatureBasedEdgeDetectionFilter::FeatureBasedEdgeDetectionFilter() { this->SetNumberOfRequiredInputs(1); this->SetNumberOfIndexedOutputs(1); } mitk::FeatureBasedEdgeDetectionFilter::~FeatureBasedEdgeDetectionFilter() { } void mitk::FeatureBasedEdgeDetectionFilter::GenerateData() { mitk::Image::Pointer image = ImageToUnstructuredGridFilter::GetInput(); if (m_SegmentationMask.IsNull()) { MITK_WARN << "Please set a segmentation mask first" << std::endl; return; } // First create a threshold segmentation of the image. The threshold is determined // by the mean +/- stddev of the pixel values that are covered by the segmentation mask // Compute mean and stdDev based on the current segmentation mitk::ImageStatisticsCalculator::Pointer statCalc = mitk::ImageStatisticsCalculator::New(); - statCalc->SetImage(image); - statCalc->SetMaskingModeToImage(); - statCalc->SetImageMask(m_SegmentationMask); - statCalc->ComputeStatistics(); - - mitk::ImageStatisticsCalculator::Statistics stats = statCalc->GetStatistics(); - double mean = stats.GetMean(); - double stdDev = stats.GetSigma(); + statCalc->SetInputImage(image); + + mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); + imgMask->SetImageMask(m_SegmentationMask); + + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats = statCalc->GetStatistics(); + double mean = stats->GetMean(); + double stdDev = stats->GetStd(); double upperThreshold = mean + stdDev; double lowerThreshold = mean - stdDev; // Perform thresholding mitk::Image::Pointer thresholdImage = mitk::Image::New(); AccessByItk_3(image.GetPointer(), ITKThresholding, lowerThreshold, upperThreshold, thresholdImage) mitk::ProgressBar::GetInstance() ->Progress(2); // Postprocess threshold segmentation // First a closing will be executed mitk::Image::Pointer closedImage = mitk::Image::New(); AccessByItk_1(thresholdImage, ThreadedClosing, closedImage); // Then we will holes that might exist mitk::MorphologicalOperations::FillHoles(closedImage); mitk::ProgressBar::GetInstance()->Progress(); // Extract the binary edges of the resulting segmentation mitk::Image::Pointer edgeImage = mitk::Image::New(); AccessByItk_1(closedImage, ContourSearch, edgeImage); // Convert the edge image into an unstructured grid mitk::ImageToUnstructuredGridFilter::Pointer i2UFilter = mitk::ImageToUnstructuredGridFilter::New(); i2UFilter->SetInput(edgeImage); i2UFilter->SetThreshold(1.0); i2UFilter->Update(); m_PointGrid = this->GetOutput(); if (m_PointGrid.IsNull()) m_PointGrid = mitk::UnstructuredGrid::New(); m_PointGrid->SetVtkUnstructuredGrid(i2UFilter->GetOutput()->GetVtkUnstructuredGrid()); mitk::ProgressBar::GetInstance()->Progress(); } template void mitk::FeatureBasedEdgeDetectionFilter::ThreadedClosing(itk::Image *originalImage, mitk::Image::Pointer &result) { typedef itk::BinaryBallStructuringElement myKernelType; myKernelType ball; ball.SetRadius(1); ball.CreateStructuringElement(); typedef typename itk::Image ImageType; typename itk::DilateObjectMorphologyImageFilter::Pointer dilationFilter = itk::DilateObjectMorphologyImageFilter::New(); dilationFilter->SetInput(originalImage); dilationFilter->SetKernel(ball); dilationFilter->Update(); typename itk::Image::Pointer dilatedImage = dilationFilter->GetOutput(); typename itk::ErodeObjectMorphologyImageFilter::Pointer erodeFilter = itk::ErodeObjectMorphologyImageFilter::New(); erodeFilter->SetInput(dilatedImage); erodeFilter->SetKernel(ball); erodeFilter->Update(); mitk::GrabItkImageMemory(erodeFilter->GetOutput(), result); } template void mitk::FeatureBasedEdgeDetectionFilter::ContourSearch(itk::Image *originalImage, mitk::Image::Pointer &result) { typedef itk::Image ImageType; typedef itk::BinaryContourImageFilter binaryContourImageFilterType; typedef unsigned short OutputPixelType; typedef itk::Image OutputImageType; typename binaryContourImageFilterType::Pointer binaryContourFilter = binaryContourImageFilterType::New(); binaryContourFilter->SetInput(originalImage); binaryContourFilter->SetForegroundValue(1); binaryContourFilter->SetBackgroundValue(0); binaryContourFilter->Update(); typename itk::Image::Pointer itkImage = itk::Image::New(); itkImage->Graft(binaryContourFilter->GetOutput()); mitk::GrabItkImageMemory(itkImage, result); } template void mitk::FeatureBasedEdgeDetectionFilter::ITKThresholding(itk::Image *originalImage, double lower, double upper, mitk::Image::Pointer &result) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; if (typeid(TPixel) != typeid(float) && typeid(TPixel) != typeid(double)) { // round the thresholds if we have nor a float or double image lower = std::floor(lower + 0.5); upper = std::floor(upper - 0.5); } if (lower >= upper) { upper = lower; } typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(originalImage); filter->SetLowerThreshold(lower); filter->SetUpperThreshold(upper); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); mitk::GrabItkImageMemory(filter->GetOutput(), result); } void mitk::FeatureBasedEdgeDetectionFilter::SetSegmentationMask(mitk::Image::Pointer segmentation) { this->m_SegmentationMask = segmentation; } void mitk::FeatureBasedEdgeDetectionFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); } diff --git a/Modules/SurfaceInterpolation/mitkImageToPointCloudFilter.cpp b/Modules/SurfaceInterpolation/mitkImageToPointCloudFilter.cpp index 508148291f..235f5a9972 100644 --- a/Modules/SurfaceInterpolation/mitkImageToPointCloudFilter.cpp +++ b/Modules/SurfaceInterpolation/mitkImageToPointCloudFilter.cpp @@ -1,165 +1,166 @@ /*=================================================================== 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 "mitkImageToPointCloudFilter.h" #include #include #include #include +#include #include #include #include #include mitk::ImageToPointCloudFilter::ImageToPointCloudFilter() : m_NumberOfExtractedPoints(0) { m_Method = DetectionMethod(0); this->SetNumberOfRequiredInputs(1); this->SetNumberOfIndexedOutputs(1); } mitk::ImageToPointCloudFilter::~ImageToPointCloudFilter() { } void mitk::ImageToPointCloudFilter::GenerateData() { mitk::Image::ConstPointer image = ImageToUnstructuredGridFilter::GetInput(); m_Geometry = image->GetGeometry(); if (image.IsNull()) { MITK_ERROR << "mitk::ImageToContourFilter: No input available. " "Please set the input!" << std::endl; return; } mitk::Image::Pointer notConstImage = const_cast(image.GetPointer()); switch (m_Method) { case 0: AccessByItk_1(notConstImage.GetPointer(), StdDeviations, 2) break; case 1: AccessByItk_1(notConstImage.GetPointer(), StdDeviations, 3) break; case 2: AccessByItk_1(notConstImage.GetPointer(), StdDeviations, 4) break; default: AccessByItk_1(notConstImage.GetPointer(), StdDeviations, 2) break; } } template void mitk::ImageToPointCloudFilter::StdDeviations(itk::Image *image, int amount) { typedef itk::Image InputImageType; typedef itk::CastImageFilter ImagePTypeToFloatPTypeCasterType; typedef itk::LaplacianImageFilter LaplacianFilterType; typename LaplacianFilterType::Pointer lapFilter = LaplacianFilterType::New(); typename ImagePTypeToFloatPTypeCasterType::Pointer caster = ImagePTypeToFloatPTypeCasterType::New(); caster->SetInput(image); caster->Update(); FloatImageType::Pointer fImage = caster->GetOutput(); lapFilter->SetInput(fImage); lapFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer edgeImage = mitk::ImportItkImage(lapFilter->GetOutput()); - mitk::ImageStatisticsCalculator::Pointer statCalc = mitk::ImageStatisticsCalculator::New(); - statCalc->SetImage(edgeImage); - statCalc->ComputeStatistics(); - mitk::ImageStatisticsCalculator::Statistics stats = statCalc->GetStatistics(); - double mean = stats.GetMean(); - double stdDev = stats.GetSigma(); + mitk::ImageStatisticsCalculator::Pointer statCalc = + mitk::ImageStatisticsCalculator::New(); + statCalc->SetInputImage(edgeImage); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats = statCalc->GetStatistics(); + double mean = stats->GetMean(); + double stdDev = stats->GetStd(); double upperThreshold = mean + stdDev * amount; double lowerThreshold = mean - stdDev * amount; typename itk::ImageRegionIterator it(lapFilter->GetOutput(), lapFilter->GetOutput()->GetRequestedRegion()); vtkSmartPointer points = vtkSmartPointer::New(); double greatX = 0, greatY = 0, greatZ = 0; it.GoToBegin(); while (!it.IsAtEnd()) { if (it.Get() > lowerThreshold && it.Get() < upperThreshold) { it.Set(0); } else { it.Set(1); mitk::Point3D imagePoint; mitk::Point3D worldPoint; imagePoint[0] = it.GetIndex()[0]; imagePoint[1] = it.GetIndex()[1]; imagePoint[2] = it.GetIndex()[2]; m_Geometry->IndexToWorld(imagePoint, worldPoint); if (worldPoint[0] > greatX) greatX = worldPoint[0]; if (worldPoint[1] > greatY) greatY = worldPoint[1]; if (worldPoint[2] > greatZ) greatZ = worldPoint[2]; points->InsertNextPoint(worldPoint[0], worldPoint[1], worldPoint[2]); m_NumberOfExtractedPoints++; } ++it; } /*need to build the UnstructuredGrid with at least one vertex otherwise its not visible*/ vtkSmartPointer verts = vtkSmartPointer::New(); verts->GetPointIds()->SetNumberOfIds(m_NumberOfExtractedPoints); for (int i = 0; i < m_NumberOfExtractedPoints; i++) { verts->GetPointIds()->SetId(i, i); } vtkSmartPointer uGrid = vtkSmartPointer::New(); uGrid->Allocate(1); uGrid->InsertNextCell(verts->GetCellType(), verts->GetPointIds()); uGrid->SetPoints(points); mitk::UnstructuredGrid::Pointer outputGrid = mitk::UnstructuredGrid::New(); outputGrid->SetVtkUnstructuredGrid(uGrid); this->SetNthOutput(0, outputGrid); } void mitk::ImageToPointCloudFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); } diff --git a/Modules/SurfaceInterpolation/mitkReduceContourSetFilter.cpp b/Modules/SurfaceInterpolation/mitkReduceContourSetFilter.cpp index 7c20ea6e22..82f5d2a6de 100644 --- a/Modules/SurfaceInterpolation/mitkReduceContourSetFilter.cpp +++ b/Modules/SurfaceInterpolation/mitkReduceContourSetFilter.cpp @@ -1,522 +1,523 @@ /*=================================================================== 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 "mitkReduceContourSetFilter.h" mitk::ReduceContourSetFilter::ReduceContourSetFilter() { m_MaxSegmentLenght = 0; m_StepSize = 10; m_Tolerance = -1; m_ReductionType = DOUGLAS_PEUCKER; m_MaxSpacing = -1; m_MinSpacing = -1; this->m_UseProgressBar = false; this->m_ProgressStepSize = 1; m_NumberOfPointsAfterReduction = 0; mitk::Surface::Pointer output = mitk::Surface::New(); this->SetNthOutput(0, output.GetPointer()); } mitk::ReduceContourSetFilter::~ReduceContourSetFilter() { } void mitk::ReduceContourSetFilter::SetInput(unsigned int idx, const mitk::Surface *surface) { this->SetNthInput(idx, const_cast(surface)); this->Modified(); } void mitk::ReduceContourSetFilter::SetInput(const mitk::Surface *surface) { this->SetInput(0, const_cast(surface)); } void mitk::ReduceContourSetFilter::GenerateData() { unsigned int numberOfInputs = this->GetNumberOfIndexedInputs(); unsigned int numberOfOutputs(0); vtkSmartPointer newPolyData; vtkSmartPointer newPolygons; vtkSmartPointer newPoints; // For the purpose of evaluation // unsigned int numberOfPointsBefore (0); m_NumberOfPointsAfterReduction = 0; for (unsigned int i = 0; i < numberOfInputs; i++) { mitk::Surface *currentSurface = const_cast(this->GetInput(i)); vtkSmartPointer polyData = currentSurface->GetVtkPolyData(); newPolyData = vtkSmartPointer::New(); newPolygons = vtkSmartPointer::New(); newPoints = vtkSmartPointer::New(); vtkSmartPointer existingPolys = polyData->GetPolys(); vtkSmartPointer existingPoints = polyData->GetPoints(); existingPolys->InitTraversal(); vtkIdType *cell(nullptr); vtkIdType cellSize(0); for (existingPolys->InitTraversal(); existingPolys->GetNextCell(cellSize, cell);) { bool incorporatePolygon = this->CheckForIntersection(cell, cellSize, existingPoints, /*numberOfIntersections, intersectionPoints, */ i); if (!incorporatePolygon) continue; vtkSmartPointer newPolygon = vtkSmartPointer::New(); if (m_ReductionType == NTH_POINT) { this->ReduceNumberOfPointsByNthPoint(cellSize, cell, existingPoints, newPolygon, newPoints); if (newPolygon->GetPointIds()->GetNumberOfIds() != 0) { newPolygons->InsertNextCell(newPolygon); } } else if (m_ReductionType == DOUGLAS_PEUCKER) { this->ReduceNumberOfPointsByDouglasPeucker(cellSize, cell, existingPoints, newPolygon, newPoints); if (newPolygon->GetPointIds()->GetNumberOfIds() > 3) { newPolygons->InsertNextCell(newPolygon); } } // Again for evaluation // numberOfPointsBefore += cellSize; m_NumberOfPointsAfterReduction += newPolygon->GetPointIds()->GetNumberOfIds(); } if (newPolygons->GetNumberOfCells() != 0) { newPolyData->SetPolys(newPolygons); newPolyData->SetPoints(newPoints); newPolyData->BuildLinks(); this->SetNumberOfIndexedOutputs(numberOfOutputs + 1); mitk::Surface::Pointer surface = mitk::Surface::New(); this->SetNthOutput(numberOfOutputs, surface.GetPointer()); surface->SetVtkPolyData(newPolyData); numberOfOutputs++; } } // MITK_INFO<<"Points before: "<SetNumberOfIndexedOutputs(numberOfOutputs); if (numberOfOutputs == 0) { mitk::Surface::Pointer tmp_output = mitk::Surface::New(); tmp_output->SetVtkPolyData(vtkPolyData::New()); this->SetNthOutput(0, tmp_output.GetPointer()); } // Setting progressbar if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(this->m_ProgressStepSize); } void mitk::ReduceContourSetFilter::ReduceNumberOfPointsByNthPoint( vtkIdType cellSize, vtkIdType *cell, vtkPoints *points, vtkPolygon *reducedPolygon, vtkPoints *reducedPoints) { unsigned int newNumberOfPoints(0); unsigned int mod = cellSize % m_StepSize; if (mod == 0) { newNumberOfPoints = cellSize / m_StepSize; } else { newNumberOfPoints = ((cellSize - mod) / m_StepSize) + 1; } if (newNumberOfPoints <= 3) { return; } reducedPolygon->GetPointIds()->SetNumberOfIds(newNumberOfPoints); reducedPolygon->GetPoints()->SetNumberOfPoints(newNumberOfPoints); for (vtkIdType i = 0; i < cellSize; i++) { if (i % m_StepSize == 0) { double point[3]; points->GetPoint(cell[i], point); vtkIdType id = reducedPoints->InsertNextPoint(point); reducedPolygon->GetPointIds()->SetId(i / m_StepSize, id); } } vtkIdType id = cell[0]; double point[3]; points->GetPoint(id, point); id = reducedPoints->InsertNextPoint(point); reducedPolygon->GetPointIds()->SetId(newNumberOfPoints - 1, id); } void mitk::ReduceContourSetFilter::ReduceNumberOfPointsByDouglasPeucker( vtkIdType cellSize, vtkIdType *cell, vtkPoints *points, vtkPolygon *reducedPolygon, vtkPoints *reducedPoints) { // If the cell is too small to obtain a reduced polygon with the given stepsize return if (cellSize <= static_cast(m_StepSize * 3)) return; /* What we do now is (see the Douglas Peucker Algorithm): 1. Divide the current contour in two line segments (start - middle; middle - end), put them into the stack 2. Fetch first line segment and create the following vectors: - v1 = (start;end) - v2 = (start;currentPoint) -> for each point of the current line segment! 3. Calculate the distance from the currentPoint to v1: a. Determine the length of the orthogonal projection of v2 to v1 by: l = v2 * (normalized v1) b. There a three possibilities for the distance then: d = sqrt(lenght(v2)^2 - l^2) if l > 0 and l < length(v1) d = lenght(v2-v1) if l > 0 and l > lenght(v1) d = length(v2) if l < 0 because v2 is then pointing in a different direction than v1 4. Memorize the point with the biggest distance and create two new line segments with it at the end of the iteration and put it into the stack 5. If the distance value D <= m_Tolerance, then add the start and end index and the corresponding points to the reduced ones */ // First of all set tolerance if none is specified if (m_Tolerance < 0) { if (m_MaxSpacing > 0) { m_Tolerance = m_MinSpacing; } else { m_Tolerance = 1.5; } } std::stack lineSegments; // 1. Divide in line segments LineSegment ls2; ls2.StartIndex = cell[cellSize / 2]; ls2.EndIndex = cell[cellSize - 1]; lineSegments.push(ls2); LineSegment ls1; ls1.StartIndex = cell[0]; ls1.EndIndex = cell[cellSize / 2]; lineSegments.push(ls1); LineSegment currentSegment; double v1[3]; double v2[3]; double tempV[3]; double lenghtV1; double currentMaxDistance(0); vtkIdType currentMaxDistanceIndex(0); double l; double d; vtkIdType pointId(0); // Add the start index to the reduced points. From now on just the end indices will be added pointId = reducedPoints->InsertNextPoint(points->GetPoint(cell[0])); reducedPolygon->GetPointIds()->InsertNextId(pointId); while (!lineSegments.empty()) { currentSegment = lineSegments.top(); lineSegments.pop(); // 2. Create vectors points->GetPoint(currentSegment.EndIndex, tempV); points->GetPoint(currentSegment.StartIndex, v1); v1[0] = tempV[0] - v1[0]; v1[1] = tempV[1] - v1[1]; v1[2] = tempV[2] - v1[2]; lenghtV1 = vtkMath::Norm(v1); vtkMath::Normalize(v1); int range = currentSegment.EndIndex - currentSegment.StartIndex; for (int i = 1; i < abs(range); ++i) { points->GetPoint(currentSegment.StartIndex + i, tempV); points->GetPoint(currentSegment.StartIndex, v2); v2[0] = tempV[0] - v2[0]; v2[1] = tempV[1] - v2[1]; v2[2] = tempV[2] - v2[2]; // 3. Calculate the distance l = vtkMath::Dot(v2, v1); d = vtkMath::Norm(v2); if (l > 0 && l < lenghtV1) { d = sqrt((d * d - l * l)); } else if (l > 0 && l > lenghtV1) { tempV[0] = lenghtV1 * v1[0] - v2[0]; tempV[1] = lenghtV1 * v1[1] - v2[1]; tempV[2] = lenghtV1 * v1[2] - v2[2]; d = vtkMath::Norm(tempV); } // 4. Memorize maximum distance if (d > currentMaxDistance) { currentMaxDistance = d; currentMaxDistanceIndex = currentSegment.StartIndex + i; } } // 4. & 5. if (currentMaxDistance <= m_Tolerance) { // double temp[3]; int segmentLenght = currentSegment.EndIndex - currentSegment.StartIndex; if (segmentLenght > (int)m_MaxSegmentLenght) { m_MaxSegmentLenght = (unsigned int)segmentLenght; } // MITK_INFO<<"Lenght: "< 25) { unsigned int newLenght(segmentLenght); while (newLenght > 25) { newLenght = newLenght * 0.5; } unsigned int divisions = abs(segmentLenght) / newLenght; // MITK_INFO<<"Divisions: "<InsertNextPoint(points->GetPoint(currentSegment.StartIndex + newLenght * i)); reducedPolygon->GetPointIds()->InsertNextId(pointId); } } // MITK_INFO<<"Inserting END: "<InsertNextPoint(points->GetPoint(currentSegment.EndIndex)); reducedPolygon->GetPointIds()->InsertNextId(pointId); } else { ls2.StartIndex = currentMaxDistanceIndex; ls2.EndIndex = currentSegment.EndIndex; lineSegments.push(ls2); ls1.StartIndex = currentSegment.StartIndex; ls1.EndIndex = currentMaxDistanceIndex; lineSegments.push(ls1); } currentMaxDistance = 0; } } bool mitk::ReduceContourSetFilter::CheckForIntersection( vtkIdType *currentCell, vtkIdType currentCellSize, vtkPoints *currentPoints, /* vtkIdType numberOfIntersections, vtkIdType* intersectionPoints,*/ unsigned int currentInputIndex) { /* If we check the current cell for intersections then we have to consider three possibilies: 1. There is another cell among all the other input surfaces which intersects the current polygon: - That means we have to save the intersection points because these points should not be eliminated 2. There current polygon exists just because of an intersection of another polygon with the current plane defined by the current polygon - That means the current polygon should not be incorporated and all of its points should be eliminated 3. There is no intersection - That mean we can just reduce the current polygons points without considering any intersections */ for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); i++) { // Don't check for intersection with the polygon itself if (i == currentInputIndex) continue; // Get the next polydata to check for intersection vtkSmartPointer poly = const_cast(this->GetInput(i))->GetVtkPolyData(); vtkSmartPointer polygonArray = poly->GetPolys(); polygonArray->InitTraversal(); vtkIdType anotherInputPolygonSize(0); vtkIdType *anotherInputPolygonIDs(nullptr); /* The procedure is: - Create the equation of the plane, defined by the points of next input - Calculate the distance of each point of the current polygon to the plane - If the maximum distance is not bigger than 1.5 of the maximum spacing AND the minimal distance is not bigger than 0.5 of the minimum spacing then the current contour is an intersection contour */ for (polygonArray->InitTraversal(); polygonArray->GetNextCell(anotherInputPolygonSize, anotherInputPolygonIDs);) { // Choosing three plane points to calculate the plane vectors double p1[3]; double p2[3]; double p3[3]; // The plane vectors double v1[3]; double v2[3] = {0}; // The plane normal double normal[3]; // Create first Vector poly->GetPoint(anotherInputPolygonIDs[0], p1); poly->GetPoint(anotherInputPolygonIDs[1], p2); v1[0] = p2[0] - p1[0]; v1[1] = p2[1] - p1[1]; v1[2] = p2[2] - p1[2]; // Find 3rd point for 2nd vector (The angle between the two plane vectors should be bigger than 30 degrees) double maxDistance(0); double minDistance(10000); for (vtkIdType j = 2; j < anotherInputPolygonSize; j++) { poly->GetPoint(anotherInputPolygonIDs[j], p3); v2[0] = p3[0] - p1[0]; v2[1] = p3[1] - p1[1]; v2[2] = p3[2] - p1[2]; // Calculate the angle between the two vector for the current point double dotV1V2 = vtkMath::Dot(v1, v2); double absV1 = sqrt(vtkMath::Dot(v1, v1)); double absV2 = sqrt(vtkMath::Dot(v2, v2)); double cosV1V2 = dotV1V2 / (absV1 * absV2); double arccos = acos(cosV1V2); double degree = vtkMath::DegreesFromRadians(arccos); // If angle is bigger than 30 degrees break if (degree > 30) break; } // for (to find 3rd point) // Calculate normal of the plane by taking the cross product of the two vectors vtkMath::Cross(v1, v2, normal); vtkMath::Normalize(normal); // Determine position of the plane double lambda = vtkMath::Dot(normal, p1); /* Calculate the distance to the plane for each point of the current polygon If the distance is zero then save the currentPoint as intersection point */ for (vtkIdType k = 0; k < currentCellSize; k++) { double currentPoint[3]; currentPoints->GetPoint(currentCell[k], currentPoint); double tempPoint[3]; tempPoint[0] = normal[0] * currentPoint[0]; tempPoint[1] = normal[1] * currentPoint[1]; tempPoint[2] = normal[2] * currentPoint[2]; double temp = tempPoint[0] + tempPoint[1] + tempPoint[2] - lambda; double distance = fabs(temp); if (distance > maxDistance) { maxDistance = distance; } if (distance < minDistance) { minDistance = distance; } } // for (to calculate distance and intersections with currentPolygon) if (maxDistance < 1.5 * m_MaxSpacing && minDistance < 0.5 * m_MinSpacing) { return false; } // Because we are considering the plane defined by the acual input polygon only one iteration is sufficient // We do not need to consider each cell of the plane break; } // for (to traverse through all cells of actualInputPolyData) } // for (to iterate through all inputs) return true; } void mitk::ReduceContourSetFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); } void mitk::ReduceContourSetFilter::Reset() { for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); i++) { this->PopBackInput(); } this->SetNumberOfIndexedInputs(0); this->SetNumberOfIndexedOutputs(0); + // BUG XXXXX Fix mitk::Surface::Pointer output = mitk::Surface::New(); this->SetNthOutput(0, output.GetPointer()); m_NumberOfPointsAfterReduction = 0; } void mitk::ReduceContourSetFilter::SetUseProgressBar(bool status) { this->m_UseProgressBar = status; } void mitk::ReduceContourSetFilter::SetProgressStepSize(unsigned int stepSize) { this->m_ProgressStepSize = stepSize; } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/CMakeLists.txt b/Plugins/org.mitk.gui.qt.measurementtoolbox/CMakeLists.txt index cbb2b24101..f6a08988fd 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/CMakeLists.txt +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/CMakeLists.txt @@ -1,9 +1,9 @@ # The project name must correspond to the directory name of your plug-in # and must not contain periods. project(org_mitk_gui_qt_measurementtoolbox) mitk_create_plugin( EXPORT_DIRECTIVE MITK_QT_MEASUREMENTTOOLBOX EXPORTED_INCLUDE_SUFFIXES src - MODULE_DEPENDS MitkQtWidgetsWeb MitkImageStatistics MitkPlanarFigure + MODULE_DEPENDS MitkQtWidgetsExt MitkImageStatistics MitkPlanarFigure MitkC3js ) diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp index aa91be8c6f..12d321fe1e 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp @@ -1,209 +1,266 @@ /*=================================================================== 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 "QmitkImageStatisticsCalculationThread.h" //QT headers #include #include +#include +#include +#include QmitkImageStatisticsCalculationThread::QmitkImageStatisticsCalculationThread():QThread(), m_StatisticsImage(nullptr), m_BinaryMask(nullptr), m_PlanarFigureMask(nullptr), m_TimeStep(0), - m_IgnoreZeros(false), m_CalculationSuccessful(false), m_StatisticChanged(false), m_HistogramBinSize(1.0), m_UseDefaultBinSize(true) + m_IgnoreZeros(false), m_CalculationSuccessful(false), m_StatisticChanged(false), m_HistogramBinSize(10.0), m_UseDefaultNBins(true), m_nBinsForHistogramStatistics(100), m_prioritizeNBinsOverBinSize(true) { } QmitkImageStatisticsCalculationThread::~QmitkImageStatisticsCalculationThread() { } void QmitkImageStatisticsCalculationThread::Initialize( mitk::Image::Pointer image, mitk::Image::Pointer binaryImage, mitk::PlanarFigure::Pointer planarFig ) { // reset old values if( this->m_StatisticsImage.IsNotNull() ) this->m_StatisticsImage = nullptr; if( this->m_BinaryMask.IsNotNull() ) this->m_BinaryMask = nullptr; if( this->m_PlanarFigureMask.IsNotNull()) this->m_PlanarFigureMask = nullptr; // set new values if passed in if(image.IsNotNull()) this->m_StatisticsImage = image->Clone(); if(binaryImage.IsNotNull()) this->m_BinaryMask = binaryImage->Clone(); if(planarFig.IsNotNull()) this->m_PlanarFigureMask = planarFig->Clone(); } -void QmitkImageStatisticsCalculationThread::SetUseDefaultBinSize(bool useDefault) +void QmitkImageStatisticsCalculationThread::SetUseDefaultNBins(bool useDefault) { - m_UseDefaultBinSize = useDefault; + m_UseDefaultNBins = useDefault; } void QmitkImageStatisticsCalculationThread::SetTimeStep( int times ) { this->m_TimeStep = times; } int QmitkImageStatisticsCalculationThread::GetTimeStep() { return this->m_TimeStep; } -std::vector QmitkImageStatisticsCalculationThread::GetStatisticsData() +std::vector QmitkImageStatisticsCalculationThread::GetStatisticsData() { return this->m_StatisticsVector; } mitk::Image::Pointer QmitkImageStatisticsCalculationThread::GetStatisticsImage() { return this->m_StatisticsImage; } void QmitkImageStatisticsCalculationThread::SetIgnoreZeroValueVoxel(bool _arg) { this->m_IgnoreZeros = _arg; } bool QmitkImageStatisticsCalculationThread::GetIgnoreZeroValueVoxel() { return this->m_IgnoreZeros; } void QmitkImageStatisticsCalculationThread::SetHistogramBinSize(double size) { this->m_HistogramBinSize = size; + this->m_prioritizeNBinsOverBinSize = false; } -double QmitkImageStatisticsCalculationThread::GetHistogramBinSize() +double QmitkImageStatisticsCalculationThread::GetHistogramBinSize() const { return this->m_HistogramBinSize; } +void QmitkImageStatisticsCalculationThread::SetHistogramNBins(double size) +{ + this->m_nBinsForHistogramStatistics = size; + this->m_prioritizeNBinsOverBinSize = true; +} + +double QmitkImageStatisticsCalculationThread::GetHistogramNBins() const +{ + return this->m_nBinsForHistogramStatistics; +} + std::string QmitkImageStatisticsCalculationThread::GetLastErrorMessage() { return m_message; } QmitkImageStatisticsCalculationThread::HistogramType::Pointer QmitkImageStatisticsCalculationThread::GetTimeStepHistogram(unsigned int t) { if (t >= this->m_HistogramVector.size()) return nullptr; return this->m_HistogramVector[t]; } bool QmitkImageStatisticsCalculationThread::GetStatisticsChangedFlag() { return m_StatisticChanged; } bool QmitkImageStatisticsCalculationThread::GetStatisticsUpdateSuccessFlag() { return m_CalculationSuccessful; } void QmitkImageStatisticsCalculationThread::run() { bool statisticCalculationSuccessful = true; mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); if(this->m_StatisticsImage.IsNotNull()) { - calculator->SetImage(m_StatisticsImage); - calculator->SetMaskingModeToNone(); + calculator->SetInputImage(m_StatisticsImage); } else { statisticCalculationSuccessful = false; } // Bug 13416 : The ImageStatistics::SetImageMask() method can throw exceptions, i.e. when the dimensionality // of the masked and input image differ, we need to catch them and mark the calculation as failed // the same holds for the ::SetPlanarFigure() try { if(this->m_BinaryMask.IsNotNull()) { - - calculator->SetImageMask(m_BinaryMask); - calculator->SetMaskingModeToImage(); + mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); + imgMask->SetImageMask(m_BinaryMask); + calculator->SetMask(imgMask.GetPointer()); } if(this->m_PlanarFigureMask.IsNotNull()) { - calculator->SetPlanarFigure(m_PlanarFigureMask); - calculator->SetMaskingModeToPlanarFigure(); + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_StatisticsImage); + pfMaskGen->SetPlanarFigure(m_PlanarFigureMask); + calculator->SetMask(pfMaskGen.GetPointer()); } } catch( const itk::ExceptionObject& e) { MITK_ERROR << "ITK Exception:" << e.what(); statisticCalculationSuccessful = false; } + catch( const mitk::Exception& e ) + { + MITK_ERROR<< "MITK Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + catch ( const std::runtime_error &e ) + { + MITK_ERROR<< "Runtime Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + catch ( const std::exception &e ) + { + //m_message = "Failure: " + std::string(e.what()); + MITK_ERROR<< "Standard Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + bool statisticChanged = false; - calculator->SetDoIgnorePixelValue(this->m_IgnoreZeros); - calculator->SetIgnorePixelValue(0); - calculator->SetHistogramBinSize( m_HistogramBinSize ); - calculator->SetUseDefaultBinSize( m_UseDefaultBinSize ); + if (this->m_IgnoreZeros) + { + mitk::IgnorePixelMaskGenerator::Pointer ignorePixelValueMaskGen = mitk::IgnorePixelMaskGenerator::New(); + ignorePixelValueMaskGen->SetIgnoredPixelValue(0); + ignorePixelValueMaskGen->SetInputImage(m_StatisticsImage); + calculator->SetSecondaryMask(ignorePixelValueMaskGen.GetPointer()); + } + else + { + calculator->SetSecondaryMask(nullptr); + } + + if (m_UseDefaultNBins) + { + calculator->SetNBinsForHistogramStatistics(100); + } + else + { + if (!m_prioritizeNBinsOverBinSize) + { + calculator->SetBinSizeForHistogramStatistics(m_HistogramBinSize); + } + else + { + calculator->SetNBinsForHistogramStatistics(100); + } + } + + //calculator->SetHistogramBinSize( m_HistogramBinSize ); + //calculator->SetUseDefaultBinSize( m_UseDefaultBinSize ); for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) { try { - statisticChanged = calculator->ComputeStatistics(i); + calculator->GetStatistics(i); } catch ( mitk::Exception& e) { //m_message = e.GetDescription(); MITK_ERROR<< "MITK Exception: " << e.what(); statisticCalculationSuccessful = false; } catch ( const std::runtime_error &e ) { //m_message = "Failure: " + std::string(e.what()); MITK_ERROR<< "Runtime Exception: " << e.what(); statisticCalculationSuccessful = false; } catch ( const std::exception &e ) { //m_message = "Failure: " + std::string(e.what()); MITK_ERROR<< "Standard Exception: " << e.what(); statisticCalculationSuccessful = false; } } this->m_StatisticChanged = statisticChanged; this->m_CalculationSuccessful = statisticCalculationSuccessful; if(statisticCalculationSuccessful) { this->m_StatisticsVector.clear(); this->m_HistogramVector.clear(); for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) { this->m_StatisticsVector.push_back(calculator->GetStatistics(i)); - this->m_HistogramVector.push_back((HistogramType*)calculator->GetHistogram(i)); + this->m_HistogramVector.push_back((HistogramType*)this->m_StatisticsVector[i]->GetHistogram()); } } - - m_HistogramBinSize = calculator->GetHistogramBinSize(); } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.h index e30ef93ed2..a18d6fd287 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.h @@ -1,115 +1,123 @@ /*=================================================================== 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 QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED #define QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED //QT headers #include #include //mitk headers #include "mitkImage.h" #include "mitkPlanarFigure.h" #include "mitkImageStatisticsCalculator.h" // itk headers #ifndef __itkHistogram_h #include #endif /** /brief This class is executed as background thread for image statistics calculation. * Documentation: This class is derived from QThread and is intended to be used by QmitkImageStatisticsView to run the image statistics calculation in a background thread keepung the gui usable. * \ingroup Plugins/MeasurementToolbox */ class QmitkImageStatisticsCalculationThread : public QThread { Q_OBJECT public: typedef itk::Statistics::Histogram HistogramType; /*! /brief standard constructor. */ QmitkImageStatisticsCalculationThread(); /*! /brief standard destructor. */ ~QmitkImageStatisticsCalculationThread(); /*! *\brief Automatically calculate bin size to obtain 200 bins. */ - void SetUseDefaultBinSize(bool useDefault); + void SetUseDefaultNBins(bool useDefault); /*! /brief Initializes the object with necessary data. */ void Initialize( mitk::Image::Pointer image, mitk::Image::Pointer binaryImage, mitk::PlanarFigure::Pointer planarFig ); /*! /brief returns the calculated image statistics. */ - std::vector GetStatisticsData(); + std::vector GetStatisticsData(); /*! /brief */ mitk::Image::Pointer GetStatisticsImage(); /*! /brief Set the time step of the image you want to process. */ void SetTimeStep( int times ); /*! /brief Get the time step of the image you want to process. */ int GetTimeStep(); /*! /brief Set flag to ignore zero valued voxels */ void SetIgnoreZeroValueVoxel( bool _arg ); /*! /brief Get status of zero value voxel ignoring. */ bool GetIgnoreZeroValueVoxel(); /*! /brief Set bin size for histogram resolution.*/ void SetHistogramBinSize( double size); /*! /brief Get bin size for histogram resolution.*/ - double GetHistogramBinSize(); + double GetHistogramBinSize() const; + /*! + /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins( double size); + /*! + /brief Get bin size for histogram resolution.*/ + double GetHistogramNBins() const; /*! /brief Returns the histogram of the currently selected time step. */ HistogramType::Pointer GetTimeStepHistogram(unsigned int t = 0); /*! /brief Returns a flag indicating if the statistics have changed during calculation */ bool GetStatisticsChangedFlag(); /*! /brief Returns a flag the indicates if the statistics are updated successfully */ bool GetStatisticsUpdateSuccessFlag(); /*! /brief Method called once the thread is executed. */ void run() override; std::string GetLastErrorMessage(); private: //member declaration mitk::Image::Pointer m_StatisticsImage; ///< member variable holds the input image for which the statistics need to be calculated. mitk::Image::Pointer m_BinaryMask; ///< member variable holds the binary mask image for segmentation image statistics calculation. mitk::PlanarFigure::Pointer m_PlanarFigureMask; ///< member variable holds the planar figure for segmentation image statistics calculation. - std::vector m_StatisticsVector; ///< member variable holds the result structs. + std::vector m_StatisticsVector; ///< member variable holds the result structs. int m_TimeStep; ///< member variable holds the time step for statistics calculation bool m_IgnoreZeros; ///< member variable holds flag to indicate if zero valued voxel should be suppressed double m_HistogramBinSize; ///< member variable holds the bin size for histogram resolution. bool m_StatisticChanged; ///< flag set if statistics have changed bool m_CalculationSuccessful; ///< flag set if statistics calculation was successful std::vector m_HistogramVector; ///< member holds the histograms of all time steps. std::string m_message; - bool m_UseDefaultBinSize; + bool m_UseDefaultNBins; + unsigned int m_nBinsForHistogramStatistics; + bool m_prioritizeNBinsOverBinSize; }; #endif // QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp index 52fe5e6aa8..866bc036cd 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,1211 +1,1361 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkImageStatisticsView.h" // Qt includes #include #include #include // berry includes #include // mitk includes #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateOr.h" #include "mitkPlanarFigureInteractor.h" // itk includes #include "itksys/SystemTools.hxx" #include #include +#include + const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; const int QmitkImageStatisticsView::STAT_TABLE_BASE_HEIGHT = 180; QmitkImageStatisticsView::QmitkImageStatisticsView(QObject* /*parent*/, const char* /*name*/) : m_Controls( NULL ), m_TimeStepperAdapter( NULL ), m_SelectedImage( NULL ), m_SelectedImageMask( NULL ), m_SelectedPlanarFigure( NULL ), m_ImageObserverTag( -1 ), m_ImageMaskObserverTag( -1 ), m_PlanarFigureObserverTag( -1 ), m_TimeObserverTag( -1 ), m_CurrentStatisticsValid( false ), m_StatisticsUpdatePending( false ), m_DataNodeSelectionChanged ( false ), m_Visible(false) { this->m_CalculationThread = new QmitkImageStatisticsCalculationThread; } QmitkImageStatisticsView::~QmitkImageStatisticsView() { if ( m_SelectedImage != NULL ) m_SelectedImage->RemoveObserver( m_ImageObserverTag ); if ( m_SelectedImageMask != NULL ) m_SelectedImageMask->RemoveObserver( m_ImageMaskObserverTag ); if ( m_SelectedPlanarFigure != NULL ) m_SelectedPlanarFigure->RemoveObserver( m_PlanarFigureObserverTag ); while(this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } delete this->m_CalculationThread; } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { if (m_Controls == NULL) { m_Controls = new Ui::QmitkImageStatisticsViewControls; m_Controls->setupUi(parent); CreateConnections(); m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_BinSizeFrame->setVisible(false); } } void QmitkImageStatisticsView::CreateConnections() { if ( m_Controls ) { connect( (QObject*)(this->m_Controls->m_ButtonCopyHistogramToClipboard), SIGNAL(clicked()),(QObject*) this, SLOT(OnClipboardHistogramButtonClicked()) ); connect( (QObject*)(this->m_Controls->m_ButtonCopyStatisticsToClipboard), SIGNAL(clicked()),(QObject*) this, SLOT(OnClipboardStatisticsButtonClicked()) ); connect( (QObject*)(this->m_Controls->m_IgnoreZerosCheckbox), SIGNAL(clicked()),(QObject*) this, SLOT(OnIgnoreZerosCheckboxClicked()) ); connect( (QObject*) this->m_CalculationThread, SIGNAL(finished()),this, SLOT( OnThreadedStatisticsCalculationEnds()),Qt::QueuedConnection); connect( (QObject*) this, SIGNAL(StatisticsUpdate()),this, SLOT( RequestStatisticsUpdate()), Qt::QueuedConnection); connect( (QObject*) this->m_Controls->m_StatisticsTable, SIGNAL(cellDoubleClicked(int,int)),this, SLOT( JumpToCoordinates(int,int)) ); - connect( (QObject*) (this->m_Controls->m_barRadioButton), SIGNAL(clicked()), (QObject*) (this->m_Controls->m_JSHistogram), SLOT(OnBarRadioButtonSelected())); - connect( (QObject*) (this->m_Controls->m_lineRadioButton), SIGNAL(clicked()), (QObject*) (this->m_Controls->m_JSHistogram), SLOT(OnLineRadioButtonSelected())); + connect((QObject*)(this->m_Controls->m_barRadioButton), SIGNAL(clicked()), (QObject*)(this), SLOT(OnBarRadioButtonSelected())); + connect((QObject*)(this->m_Controls->m_lineRadioButton), SIGNAL(clicked()), (QObject*)(this), SLOT(OnLineRadioButtonSelected())); connect( (QObject*) (this->m_Controls->m_HistogramBinSizeSpinbox), SIGNAL(editingFinished()), this, SLOT(OnHistogramBinSizeBoxValueChanged())); - connect( (QObject*)(this->m_Controls->m_UseDefaultBinSizeBox), SIGNAL(clicked()),(QObject*) this, SLOT(OnDefaultBinSizeBoxChanged()) ); + connect((QObject*)(this->m_Controls->m_UseDefaultBinSizeBox), SIGNAL(clicked()), (QObject*) this, SLOT(OnDefaultBinSizeBoxChanged())); + connect((QObject*)(this->m_Controls->m_ShowSubchartCheckBox), SIGNAL(clicked()), (QObject*) this, SLOT(OnShowSubchartBoxChanged())); } } void QmitkImageStatisticsView::OnDefaultBinSizeBoxChanged() { - if (m_CalculationThread!=NULL) - m_Controls->m_HistogramBinSizeSpinbox->setValue(m_CalculationThread->GetHistogramBinSize()); - if (m_Controls->m_UseDefaultBinSizeBox->isChecked()) - m_Controls->m_BinSizeFrame->setVisible(false); - else - m_Controls->m_BinSizeFrame->setVisible(true); + + m_Controls->m_BinSizeFrame->setVisible(!m_Controls->m_UseDefaultBinSizeBox->isChecked()); + +if (m_CalculationThread != NULL){ + m_Controls->m_HistogramBinSizeSpinbox->setValue(m_CalculationThread->GetHistogramBinSize()); + m_CalculationThread->SetUseDefaultNBins(m_Controls->m_UseDefaultBinSizeBox->isChecked()); +} +this->UpdateStatistics(); + +} + +void QmitkImageStatisticsView::OnShowSubchartBoxChanged() +{ + this->m_Controls->m_JSHistogram->SetAppearance( + this->m_Controls->m_lineRadioButton->isChecked(), this->m_Controls->m_ShowSubchartCheckBox->isChecked()); + + QString useLineChart = "false"; + if (this->m_Controls->m_lineRadioButton->isChecked()) + useLineChart = "true"; + + QString showSubchart = "false"; + if (this->m_Controls->m_ShowSubchartCheckBox->isChecked()) + showSubchart = "true"; + + this->m_Controls->m_JSHistogram->SendCommand( + "ReloadChart(" + useLineChart + "," + showSubchart + ")"); +} + + +void QmitkImageStatisticsView::OnBarRadioButtonSelected() +{ + this->m_Controls->m_JSHistogram->TransformView("bar"); +} + +void QmitkImageStatisticsView::OnLineRadioButtonSelected() +{ + this->m_Controls->m_JSHistogram->TransformView("line"); } void QmitkImageStatisticsView::PartClosed(const berry::IWorkbenchPartReference::Pointer& ) { } void QmitkImageStatisticsView::OnTimeChanged(const itk::EventObject& e) { if (this->m_SelectedDataNodes.isEmpty() || this->m_SelectedImage == NULL) return; const mitk::SliceNavigationController::GeometryTimeEvent* timeEvent = dynamic_cast(&e); assert(timeEvent != NULL); unsigned int timestep = timeEvent->GetPos(); if (this->m_SelectedImage->GetTimeSteps() > 1) { for (int x = 0; x < this->m_Controls->m_StatisticsTable->columnCount(); x++) { for (int y = 0; y < this->m_Controls->m_StatisticsTable->rowCount(); y++) { QTableWidgetItem* item = this->m_Controls->m_StatisticsTable->item(y, x); if (item == NULL) break; if (x == timestep) { item->setBackgroundColor(Qt::yellow); } else { if (y % 2 == 0) item->setBackground(this->m_Controls->m_StatisticsTable->palette().base()); else item->setBackground(this->m_Controls->m_StatisticsTable->palette().alternateBase()); } } } this->m_Controls->m_StatisticsTable->viewport()->update(); } if ((this->m_SelectedImage->GetTimeSteps() == 1 && timestep == 0) || this->m_SelectedImage->GetTimeSteps() > 1) { // display histogram for selected timestep this->m_Controls->m_JSHistogram->ClearHistogram(); QmitkImageStatisticsCalculationThread::HistogramType::Pointer histogram = this->m_CalculationThread->GetTimeStepHistogram(timestep); if (histogram.IsNotNull()) { bool closedFigure = this->m_CalculationThread->GetStatisticsUpdateSuccessFlag(); if ( closedFigure ) { - this->m_Controls->m_JSHistogram->ComputeHistogram(histogram.GetPointer()); + this->m_Controls->m_JSHistogram->ComputeHistogram( + histogram.GetPointer(), this->m_Controls->m_lineRadioButton->isChecked(), this->m_Controls->m_ShowSubchartCheckBox->isChecked() ); } //this->m_Controls->m_JSHistogram->ComputeHistogram(histogram.GetPointer()); /*else { m_Controls->m_JSHistogram->ComputeIntensityProfile(timestep, true); }*/ // this->m_Controls->m_JSHistogram->SignalGraphChanged(); // hacky way to make sure the protected SignalGraphChanged() is called - if (this->m_Controls->m_JSHistogram->GetUseLineGraph()) - { - this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); - this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); - } - else - { - this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); - this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); - } + //if (this->m_Controls->m_JSHistogram->GetUseLineGraph()) + //{ + //this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); + //this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); + //} + //else + //{ + //this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); + //this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); + //} } } } void QmitkImageStatisticsView::JumpToCoordinates(int row ,int col) { if(m_SelectedDataNodes.isEmpty()) { MITK_WARN("QmitkImageStatisticsView") << "No data node selected for statistics calculation." ; return; } mitk::Point3D world; if (row==4 && !m_WorldMinList.empty()) world = m_WorldMinList[col]; else if (row==3 && !m_WorldMaxList.empty()) world = m_WorldMaxList[col]; else return; mitk::IRenderWindowPart* part = this->GetRenderWindowPart(); if (part) { part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()->SelectSliceByPoint(world); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), col); part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SetGeometryTime(timeEvent); } } void QmitkImageStatisticsView::OnIgnoreZerosCheckboxClicked() { emit StatisticsUpdate(); } void QmitkImageStatisticsView::OnClipboardHistogramButtonClicked() { if ( m_CurrentStatisticsValid && !( m_SelectedPlanarFigure != NULL)) { const unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); typedef mitk::ImageStatisticsCalculator::HistogramType HistogramType; const HistogramType *histogram = this->m_CalculationThread->GetTimeStepHistogram(t).GetPointer(); QString clipboard( "Measurement \t Frequency\n" ); for ( HistogramType::ConstIterator it = histogram->Begin(); it != histogram->End(); ++it ) { if( m_Controls->m_HistogramBinSizeSpinbox->value() == 1.0) { clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( it.GetMeasurementVector()[0], 0, 'f', 0 ) .arg( it.GetFrequency() ); } else { clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( it.GetMeasurementVector()[0], 0, 'f', 2 ) .arg( it.GetFrequency() ); } } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard ); } // If a (non-closed) PlanarFigure is selected, display a line profile widget else if ( m_CurrentStatisticsValid && (m_SelectedPlanarFigure != NULL )) { - auto intensity = m_Controls->m_JSHistogram->GetFrequency(); + /*auto intensity = m_Controls->m_JSHistogram->GetFrequency(); auto pixel = m_Controls->m_JSHistogram->GetMeasurement(); QString clipboard( "Pixel \t Intensity\n" ); auto j = pixel.begin(); for (auto i = intensity.begin(); i < intensity.end(); i++) { assert(j != pixel.end()); clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( (*j).toString()) .arg( (*i).toString()); j++; } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard ); + */ } else { QApplication::clipboard()->clear(); } } void QmitkImageStatisticsView::OnClipboardStatisticsButtonClicked() { QLocale tempLocal; QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); if ( m_CurrentStatisticsValid && !( m_SelectedPlanarFigure != NULL)) { - const std::vector &statistics = + const std::vector &statistics = this->m_CalculationThread->GetStatisticsData(); // Set time borders for for loop ;) unsigned int startT, endT; if(this->m_Controls->m_CheckBox4dCompleteTable->checkState()==Qt::CheckState::Unchecked) { startT = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); endT = startT+1; } else { startT = 0; endT = statistics.size(); } QVector< QVector > statisticsTable; QStringList headline; // Create Headline headline << " " << "Mean" << "Median" << "StdDev" << "RMS" << "Max" << "Min" << "NumberOfVoxels" << "Skewness" << "Kurtosis" << "Uniformity" << "Entropy" << "MPP" << "UPP" << "V [mm³]"; for(int i=0;i row; row.append(headline.at(i)); statisticsTable.append(row); } // Fill Table for(unsigned int t=startT;tGetMean()) + << QString::number(statistics[t]->GetMedian()) + << QString::number(statistics[t]->GetStd()) + << QString::number(statistics[t]->GetRMS()) + << QString::number(statistics[t]->GetMax()) + << QString::number(statistics[t]->GetMin()) + << QString::number(statistics[t]->GetN()) + << QString::number(statistics[t]->GetSkewness()) + << QString::number(statistics[t]->GetKurtosis()) + << QString::number(statistics[t]->GetUniformity()) + << QString::number(statistics[t]->GetEntropy()) + << QString::number(statistics[t]->GetMPP()) + << QString::number(statistics[t]->GetUPP()) << QString::number(m_Controls->m_StatisticsTable->item(7, 0)->data(Qt::DisplayRole).toDouble()); for(int z=0;zsetText(clipboard, QClipboard::Clipboard); } else { QApplication::clipboard()->clear(); } QLocale::setDefault(tempLocal); } void QmitkImageStatisticsView::OnSelectionChanged( berry::IWorkbenchPart::Pointer /*part*/, const QList &selectedNodes ) { if (this->m_Visible) { this->SelectionChanged( selectedNodes ); } else { this->m_DataNodeSelectionChanged = true; } } void QmitkImageStatisticsView::SelectionChanged(const QList &selectedNodes) { if( this->m_StatisticsUpdatePending ) { this->m_DataNodeSelectionChanged = true; return; // not ready for new data now! } if (selectedNodes.size() == this->m_SelectedDataNodes.size()) { int i = 0; for (; i < selectedNodes.size(); ++i) { if (selectedNodes.at(i) != this->m_SelectedDataNodes.at(i)) { break; } } // node selection did not change if (i == selectedNodes.size()) return; } //reset the feature image and image mask field m_Controls->m_SelectedFeatureImageLabel->setText("None"); m_Controls->m_SelectedMaskLabel->setText("None"); this->ReinitData(); if (selectedNodes.isEmpty()) { m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); // m_Controls->horizontalLayout_3->setEnabled(false); m_Controls->groupBox->setEnabled(false); m_Controls->groupBox_3->setEnabled(false); } else { // m_Controls->horizontalLayout_3->setEnabled(true); m_Controls->groupBox->setEnabled(true); m_Controls->groupBox_3->setEnabled(true); } if(selectedNodes.size() == 1 || selectedNodes.size() == 2) { bool isBinary = false; selectedNodes.value(0)->GetBoolProperty("binary",isBinary); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); isBinary |= isLabelSet->CheckNode(selectedNodes.value(0)); if(isBinary) { m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); } for (int i= 0; i< selectedNodes.size(); ++i) { this->m_SelectedDataNodes.push_back(selectedNodes.at(i)); } this->m_DataNodeSelectionChanged = false; this->m_Controls->m_ErrorMessageLabel->setText( "" ); this->m_Controls->m_ErrorMessageLabel->hide(); emit StatisticsUpdate(); } else { this->m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::ReinitData() { while( this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if(this->m_SelectedImage != NULL) { this->m_SelectedImage->RemoveObserver( this->m_ImageObserverTag); this->m_SelectedImage = NULL; } if(this->m_SelectedImageMask != NULL) { this->m_SelectedImageMask->RemoveObserver( this->m_ImageMaskObserverTag); this->m_SelectedImageMask = NULL; } if(this->m_SelectedPlanarFigure != NULL) { this->m_SelectedPlanarFigure->RemoveObserver( this->m_PlanarFigureObserverTag); this->m_SelectedPlanarFigure = NULL; } this->m_SelectedDataNodes.clear(); this->m_StatisticsUpdatePending = false; m_Controls->m_ErrorMessageLabel->setText( "" ); m_Controls->m_ErrorMessageLabel->hide(); this->InvalidateStatisticsTableView(); m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); } void QmitkImageStatisticsView::OnThreadedStatisticsCalculationEnds() { std::stringstream message; message << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->hide(); this->WriteStatisticsToGUI(); } void QmitkImageStatisticsView::UpdateStatistics() { mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); if ( renderPart == NULL ) { this->m_StatisticsUpdatePending = false; return; } m_WorldMinList.clear(); m_WorldMaxList.clear(); // classify selected nodes mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateOr::Pointer imagePredicate = mitk::NodePredicateOr::New(isImage, isLabelSet); std::string maskName = std::string(); std::string maskType = std::string(); std::string featureImageName = std::string(); unsigned int maskDimension = 0; // reset data from last run ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction( this, &QmitkImageStatisticsView::SelectedDataModified ); mitk::DataNode::Pointer planarFigureNode; for( int i= 0 ; i < this->m_SelectedDataNodes.size(); ++i) { mitk::PlanarFigure::Pointer planarFig = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); if( imagePredicate->CheckNode(this->m_SelectedDataNodes.at(i)) ) { bool isMask = false; this->m_SelectedDataNodes.at(i)->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(this->m_SelectedDataNodes.at(i)); if( this->m_SelectedImageMask == NULL && isMask) { this->m_SelectedImageMask = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageMaskObserverTag = this->m_SelectedImageMask->AddObserver(itk::ModifiedEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = m_SelectedImageMask->GetNameOfClass(); maskDimension = 3; } else if( !isMask ) { if(this->m_SelectedImage == NULL) { this->m_SelectedImage = static_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } featureImageName = this->m_SelectedDataNodes.at(i)->GetName(); } } else if (planarFig.IsNotNull()) { if(this->m_SelectedPlanarFigure == NULL) { this->m_SelectedPlanarFigure = planarFig; this->m_PlanarFigureObserverTag = this->m_SelectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = this->m_SelectedPlanarFigure->GetNameOfClass(); maskDimension = 2; planarFigureNode = m_SelectedDataNodes.at(i); } } else { std::stringstream message; message << "" << "Invalid data node type!" << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); } } if(maskName == "") { maskName = "None"; maskType = ""; maskDimension = 0; } if(featureImageName == "") { featureImageName = "None"; } if (m_SelectedPlanarFigure != NULL && m_SelectedImage == NULL) { mitk::DataStorage::SetOfObjects::ConstPointer parentSet = this->GetDataStorage()->GetSources(planarFigureNode); for (int i=0; iSize(); i++) { mitk::DataNode::Pointer node = parentSet->ElementAt(i); if( imagePredicate->CheckNode(node) ) { bool isMask = false; node->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(node); if( !isMask ) { if(this->m_SelectedImage == NULL) { this->m_SelectedImage = static_cast(node->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } } } } } unsigned int timeStep = renderPart->GetTimeNavigationController()->GetTime()->GetPos(); if ( m_SelectedImage != NULL && m_SelectedImage->IsInitialized()) { // Check if a the selected image is a multi-channel image. If yes, statistics // cannot be calculated currently. if ( m_SelectedImage->GetPixelType().GetNumberOfComponents() > 1 ) { std::stringstream message; message << "Multi-component images not supported."; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_JSHistogram->ClearHistogram(); m_CurrentStatisticsValid = false; this->m_StatisticsUpdatePending = false; m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); return; } std::stringstream maskLabel; maskLabel << maskName; if ( maskDimension > 0 ) { maskLabel << " [" << maskDimension << "D " << maskType << "]"; } m_Controls->m_SelectedMaskLabel->setText( maskLabel.str().c_str() ); m_Controls->m_SelectedFeatureImageLabel->setText(featureImageName.c_str()); // check time step validity if(m_SelectedImage->GetDimension() <= 3 && timeStep > m_SelectedImage->GetDimension(3)-1) { timeStep = m_SelectedImage->GetDimension(3)-1; } // Add the used mask time step to the mask label so the user knows which mask time step was used // if the image time step is bigger than the total number of mask time steps (see // ImageStatisticsCalculator::ExtractImageAndMask) if (m_SelectedImageMask != NULL) { unsigned int maskTimeStep = timeStep; if (maskTimeStep >= m_SelectedImageMask->GetTimeSteps()) { maskTimeStep = m_SelectedImageMask->GetTimeSteps() - 1; } m_Controls->m_SelectedMaskLabel->setText(m_Controls->m_SelectedMaskLabel->text() + QString(" (t=") + QString::number(maskTimeStep) + QString(")")); } //// initialize thread and trigger it this->m_CalculationThread->SetIgnoreZeroValueVoxel( m_Controls->m_IgnoreZerosCheckbox->isChecked() ); this->m_CalculationThread->Initialize( m_SelectedImage, m_SelectedImageMask, m_SelectedPlanarFigure ); this->m_CalculationThread->SetTimeStep( timeStep ); - this->m_CalculationThread->SetHistogramBinSize(m_Controls->m_HistogramBinSizeSpinbox->value()); + std::stringstream message; message << "Calculating statistics..."; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); try { // Compute statistics - this->m_CalculationThread->SetUseDefaultBinSize(m_Controls->m_UseDefaultBinSizeBox->isChecked()); + // this->m_CalculationThread->SetUseDefaultBinSize(m_Controls->m_UseDefaultBinSizeBox->isChecked()); this->m_CalculationThread->start(); } catch ( const mitk::Exception& e) { std::stringstream message; message << "" << e.GetDescription() << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::runtime_error &e ) { // In case of exception, print error message on GUI std::stringstream message; message << "" << e.what() << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::exception &e ) { MITK_ERROR << "Caught exception: " << e.what(); // In case of exception, print error message on GUI std::stringstream message; message << "Error! Unequal Dimensions of Image and Segmentation. No recompute possible "; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } } else { this->m_StatisticsUpdatePending = false; } } void QmitkImageStatisticsView::SelectedDataModified() { if( !m_StatisticsUpdatePending ) { emit StatisticsUpdate(); } } void QmitkImageStatisticsView::NodeRemoved(const mitk::DataNode *node) { while(this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if (node->GetData() == m_SelectedImage) { m_SelectedImage = NULL; } } void QmitkImageStatisticsView::RequestStatisticsUpdate() { if ( !m_StatisticsUpdatePending ) { if(this->m_DataNodeSelectionChanged) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->m_StatisticsUpdatePending = true; this->UpdateStatistics(); } } if (this->GetRenderWindowPart()) this->GetRenderWindowPart()->RequestUpdate(); } void QmitkImageStatisticsView::OnHistogramBinSizeBoxValueChanged() { - this->UpdateStatistics(); + if (m_Controls->m_HistogramBinSizeSpinbox->value() != m_HistogramBinSize) + { + m_HistogramBinSize = m_Controls->m_HistogramBinSizeSpinbox->value(); + this->m_CalculationThread->SetHistogramBinSize(m_Controls->m_HistogramBinSizeSpinbox->value()); + this->UpdateStatistics(); + } } void QmitkImageStatisticsView::WriteStatisticsToGUI() { + disconnect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), 0, 0); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); if(m_DataNodeSelectionChanged) { this->m_StatisticsUpdatePending = false; this->RequestStatisticsUpdate(); return; // stop visualization of results and calculate statistics of new selection } if ( this->m_CalculationThread->GetStatisticsUpdateSuccessFlag()) { if ( this->m_CalculationThread->GetStatisticsChangedFlag() ) { // Do not show any error messages m_Controls->m_ErrorMessageLabel->hide(); m_CurrentStatisticsValid = true; } if (m_Controls->m_barRadioButton->isChecked()) { - m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); + //m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); } m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_HistogramBinSizeSpinbox->setValue( this->m_CalculationThread->GetHistogramBinSize() ); //m_Controls->m_JSHistogram->ComputeHistogram( this->m_CalculationThread->GetTimeStepHistogram(this->m_CalculationThread->GetTimeStep()).GetPointer() ); this->FillStatisticsTableView( this->m_CalculationThread->GetStatisticsData(), this->m_CalculationThread->GetStatisticsImage()); + m_CurrentStatisticsValid = true; } else { m_Controls->m_SelectedMaskLabel->setText( "None" ); m_Controls->m_ErrorMessageLabel->setText( m_CalculationThread->GetLastErrorMessage().c_str() ); m_Controls->m_ErrorMessageLabel->show(); // Clear statistics and histogram this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); //m_Controls->m_JSHistogram->clearHistogram(); m_CurrentStatisticsValid = false; // If a (non-closed) PlanarFigure is selected, display a line profile widget if ( m_SelectedPlanarFigure != NULL ) { // Check if the (closed) planar figure is out of bounds and so no image mask could be calculated--> Intensity Profile can not be calculated bool outOfBounds = false; if ( m_SelectedPlanarFigure->IsClosed() && m_SelectedImageMask == NULL) { outOfBounds = true; std::stringstream message; message << "Planar figure is on a rotated image plane or outside the image bounds."; m_Controls->m_InfoLabel->setText(message.str().c_str()); } // check whether PlanarFigure is initialized const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_SelectedPlanarFigure->GetPlaneGeometry(); if ( !(planarFigurePlaneGeometry == NULL || outOfBounds)) { unsigned int timeStep = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); m_Controls->m_JSHistogram->SetImage(this->m_CalculationThread->GetStatisticsImage()); m_Controls->m_JSHistogram->SetPlanarFigure(m_SelectedPlanarFigure); + connect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), (QObject*) this, SLOT(OnLineRadioButtonSelected())); m_Controls->m_JSHistogram->ComputeIntensityProfile(timeStep, true); //m_Controls->m_JSHistogram->ComputeIntensityProfile(timeStep); + //this->ComputeIntensityProfile(m_SelectedPlanarFigure, this->m_CalculationThread->GetStatisticsImage(), timeStep, true); + + m_Controls->m_lineRadioButton->setChecked(true); m_Controls->m_lineRadioButton->setEnabled(false); m_Controls->m_barRadioButton->setEnabled(false); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(false); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(false); // m_Controls->m_HistogramBinSizeLabel->setEnabled(false); this->FillLinearProfileStatisticsTableView( this->m_CalculationThread->GetStatisticsImage() ); std::stringstream message; message << "Only linegraph available for an intensity profile!"; m_Controls->m_InfoLabel->setText(message.str().c_str()); m_CurrentStatisticsValid = true; } else { // Clear statistics, histogram, and GUI this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_JSHistogram->ClearHistogram(); m_CurrentStatisticsValid = false; m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_SelectedMaskLabel->setText( "None" ); this->m_StatisticsUpdatePending = false; m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); if (!outOfBounds) m_Controls->m_InfoLabel->setText(QString("")); return; // Sebastian Wirkert: would suggest to remove this return, since it is an artifact of previous // code architecture. However, removing it will cause m_StatisticsUpdatePending to be set to false // in case of invalid statistics which it previously was not. } } } this->m_StatisticsUpdatePending = false; } void QmitkImageStatisticsView::FillStatisticsTableView( - const std::vector &s, + const std::vector &s, const mitk::Image *image ) { this->m_Controls->m_StatisticsTable->setColumnCount(image->GetTimeSteps()); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(image->GetTimeSteps() > 1); // Set Checkbox for complete copy of statistic table if(image->GetTimeSteps()>1) { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(true); } else { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(false); this->m_Controls->m_CheckBox4dCompleteTable->setChecked(false); } int decimals = 2; mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) { decimals = 5; } for (unsigned int t = 0; t < image->GetTimeSteps(); t++) { this->m_Controls->m_StatisticsTable->setHorizontalHeaderItem(t, new QTableWidgetItem(QString::number(t))); - if (s[t].GetMaxIndex().size()==3) + if (s[t]->GetMaxIndex().size()==3) { mitk::Point3D index, max, min; - index[0] = s[t].GetMaxIndex()[0]; - index[1] = s[t].GetMaxIndex()[1]; - index[2] = s[t].GetMaxIndex()[2]; + index[0] = s[t]->GetMaxIndex()[0]; + index[1] = s[t]->GetMaxIndex()[1]; + index[2] = s[t]->GetMaxIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, max); this->m_WorldMaxList.push_back(max); - index[0] = s[t].GetMinIndex()[0]; - index[1] = s[t].GetMinIndex()[1]; - index[2] = s[t].GetMinIndex()[2]; + index[0] = s[t]->GetMinIndex()[0]; + index[1] = s[t]->GetMinIndex()[1]; + index[2] = s[t]->GetMinIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, min); this->m_WorldMinList.push_back(min); } + typedef mitk::ImageStatisticsCalculator::StatisticsContainer::RealType RealType; + RealType maxVal = std::numeric_limits::max(); + this->m_Controls->m_StatisticsTable->setItem( 0, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetMean(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetMean(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 1, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetMedian(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetMedian(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 2, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetSigma(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetStd(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 3, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetRMS(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetRMS(), 0, 'f', decimals) ) ); - QString max; max.append(QString("%1").arg(s[t].GetMax(), 0, 'f', decimals)); + QString max; max.append(QString("%1").arg(s[t]->GetMax(), 0, 'f', decimals)); max += " ("; - for (int i=0; iGetMaxIndex().size(); i++) { - max += QString::number(s[t].GetMaxIndex()[i]); - if (iGetMaxIndex()[i]); + if (iGetMaxIndex().size()-1) max += ","; } max += ")"; this->m_Controls->m_StatisticsTable->setItem( 4, t, new QTableWidgetItem( max ) ); - QString min; min.append(QString("%1").arg(s[t].GetMin(), 0, 'f', decimals)); + QString min; min.append(QString("%1").arg(s[t]->GetMin(), 0, 'f', decimals)); min += " ("; - for (int i=0; iGetMinIndex().size(); i++) { - min += QString::number(s[t].GetMinIndex()[i]); - if (iGetMinIndex()[i]); + if (iGetMinIndex().size()-1) min += ","; } min += ")"; this->m_Controls->m_StatisticsTable->setItem( 5, t, new QTableWidgetItem( min ) ); this->m_Controls->m_StatisticsTable->setItem( 6, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetN()) ) ); + QString("%1").arg(s[t]->GetN()) ) ); const mitk::BaseGeometry *geometry = image->GetGeometry(); if ( geometry != NULL ) { const mitk::Vector3D &spacing = image->GetGeometry()->GetSpacing(); - double volume = spacing[0] * spacing[1] * spacing[2] * (double) s[t].GetN(); + double volume = spacing[0] * spacing[1] * spacing[2] * (double) s[t]->GetN(); this->m_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( QString("%1").arg(volume, 0, 'f', decimals) ) ); } else { this->m_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( "NA" ) ); } //statistics of higher order should have 5 decimal places because they used to be very small this->m_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetSkewness(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetSkewness(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetKurtosis(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetKurtosis(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 10, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetUniformity(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetUniformity(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 11, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetEntropy(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetEntropy(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 12, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetMPP(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetMPP(), 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 13, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetUPP(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetUPP(), 0, 'f', 5) ) ); } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); // make sure the current timestep's column is highlighted (and the correct histogram is displayed) unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), t); this->OnTimeChanged(timeEvent); t = std::min(image->GetTimeSteps() - 1, t); // See bug 18340 /*QString hotspotMean; hotspotMean.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMean(), 0, 'f', decimals)); hotspotMean += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( hotspotMean ) ); QString hotspotMax; hotspotMax.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMax(), 0, 'f', decimals)); hotspotMax += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( hotspotMax ) ); QString hotspotMin; hotspotMin.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMin(), 0, 'f', decimals)); hotspotMin += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( hotspotMin ) );*/ } std::vector QmitkImageStatisticsView::CalculateStatisticsForPlanarFigure( const mitk::Image *image) { std::vector result; int decimals = 2; mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) { decimals = 5; } - mitk::ImageStatisticsCalculator::Statistics &stats = m_Controls->m_JSHistogram->GetStatistics(); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats = m_Controls->m_JSHistogram->GetStatistics(); + + typedef mitk::ImageStatisticsCalculator::StatisticsContainer::RealType RealType; + RealType maxVal = std::numeric_limits::max(); + + if (stats->GetMean() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMean(), 0, 'f', decimals)); + } + + if (stats->GetMedian() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMedian(), 0, 'f', decimals)); + } + + if (stats->GetStd() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back( QString("%1").arg( stats->GetStd(), 0, 'f', decimals)); + } + + if (stats->GetRMS() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg( stats->GetRMS(), 0, 'f', decimals)); + } - result.push_back(QString("%1").arg(stats.GetMean(), 0, 'f', decimals)); - result.push_back(QString("%1").arg(stats.GetMedian(), 0, 'f', decimals)); + if (stats->GetMax() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + QString max; + max.append(QString("%1").arg(stats->GetMax(), 0, 'f', decimals)); + result.push_back(max); + } - double stdDev = sqrt( stats.GetVariance() ); - result.push_back( QString("%1").arg( stdDev, 0, 'f', decimals)); + if (stats->GetMin() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + QString min; + min.append(QString("%1").arg(stats->GetMin(), 0, 'f', decimals)); + result.push_back(min); - double rms = stats.GetRMS(); - result.push_back(QString("%1").arg( rms, 0, 'f', decimals)); + } - QString max; max.append(QString("%1").arg(stats.GetMax(), 0, 'f', decimals)); - result.push_back(max); - QString min; min.append(QString("%1").arg(stats.GetMin(), 0, 'f', decimals)); - result.push_back(min); - result.push_back(QString("%1").arg(stats.GetN())); + if (stats->GetN() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetN())); + } result.push_back(QString("NA")); //statistics of higher order should have 5 decimal places because they used to be very small - result.push_back(QString("%1").arg(stats.GetSkewness(), 0, 'f', 5 )); - - result.push_back(QString("%1").arg(stats.GetKurtosis(), 0, 'f', 5) ); + if (stats->GetSkewness() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetSkewness(), 0, 'f', 5 )); + } - result.push_back(QString("%1").arg(stats.GetUniformity(), 0, 'f', 5) ); + if (stats->GetKurtosis() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetKurtosis(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats.GetEntropy(), 0, 'f', 5) ); + if (stats->GetUniformity() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetUniformity(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats.GetMPP(), 0, 'f', decimals) ); + if (stats->GetEntropy() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetEntropy(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats.GetUPP(), 0, 'f', 5) ); + if (stats->GetMPP() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMPP(), 0, 'f', decimals) ); + } + if (stats->GetUPP() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetUPP(), 0, 'f', 5) ); + } return result; } void QmitkImageStatisticsView::FillLinearProfileStatisticsTableView( const mitk::Image *image ) { this->m_Controls->m_StatisticsTable->setColumnCount(1); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); m_PlanarFigureStatistics = this->CalculateStatisticsForPlanarFigure(image); for (int i = 0; i< m_PlanarFigureStatistics.size(); i++) { this->m_Controls->m_StatisticsTable->setItem( i, 0, new QTableWidgetItem(m_PlanarFigureStatistics[i] )); } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); } void QmitkImageStatisticsView::InvalidateStatisticsTableView() { this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); this->m_Controls->m_StatisticsTable->setColumnCount(1); for ( unsigned int i = 0; i < this->m_Controls->m_StatisticsTable->rowCount(); ++i ) { { this->m_Controls->m_StatisticsTable->setItem( i, 0, new QTableWidgetItem( "NA" ) ); } } this->m_Controls->m_StatisticsTable->setMinimumHeight(STAT_TABLE_BASE_HEIGHT); } void QmitkImageStatisticsView::Activated() { } void QmitkImageStatisticsView::Deactivated() { } void QmitkImageStatisticsView::Visible() { m_Visible = true; mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { itk::ReceptorMemberCommand::Pointer cmdTimeEvent = itk::ReceptorMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkImageStatisticsView::OnTimeChanged); // It is sufficient to add the observer to the axial render window since the GeometryTimeEvent // is always triggered by all views. m_TimeObserverTag = renderWindow->GetQmitkRenderWindow("axial")-> GetSliceNavigationController()-> AddObserver(mitk::SliceNavigationController::GeometryTimeEvent(NULL, 0), cmdTimeEvent); } if (m_DataNodeSelectionChanged) { if (this->IsCurrentSelectionValid()) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->SelectionChanged(this->GetDataManagerSelection()); } m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::Hidden() { m_Visible = false; // The slice navigation controller observer is removed here instead of in the destructor. // If it was called in the destructor, the application would freeze because the view's // destructor gets called after the render windows have been destructed. if ( m_TimeObserverTag != NULL ) { mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { renderWindow->GetQmitkRenderWindow("axial")->GetSliceNavigationController()-> RemoveObserver( m_TimeObserverTag ); } m_TimeObserverTag = NULL; } } void QmitkImageStatisticsView::SetFocus() { } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h index 6a96944481..2770e2a0ac 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h @@ -1,190 +1,197 @@ /*=================================================================== 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 QmitkImageStatisticsView_H__INCLUDED #define QmitkImageStatisticsView_H__INCLUDED #include "ui_QmitkImageStatisticsViewControls.h" // Qmitk includes #include #include "QmitkStepperAdapter.h" #include "QmitkImageStatisticsCalculationThread.h" #include // mitk includes #include "mitkImageStatisticsCalculator.h" #include "mitkILifecycleAwarePart.h" #include "mitkPlanarLine.h" /*! \brief QmitkImageStatisticsView is a bundle that allows statistics calculation from images. Three modes are supported: 1. Statistics of one image, 2. Statistics of an image and a segmentation, 3. Statistics of an image and a Planar Figure. The statistics calculation is realized in a seperate thread to keep the gui accessable during calculation. \ingroup Plugins/org.mitk.gui.qt.measurementtoolbox */ class QmitkImageStatisticsView : public QmitkAbstractView, public mitk::ILifecycleAwarePart, public berry::IPartListener { Q_OBJECT private: /*! \ Convenient typedefs */ typedef mitk::DataStorage::SetOfObjects ConstVector; typedef ConstVector::ConstPointer ConstVectorPointer; typedef ConstVector::ConstIterator ConstVectorIterator; typedef std::map< mitk::Image *, mitk::ImageStatisticsCalculator::Pointer > ImageStatisticsMapType; typedef QList SelectedDataNodeVectorType; typedef itk::SimpleMemberCommand< QmitkImageStatisticsView > ITKCommandType; public: /*! \brief default constructor */ QmitkImageStatisticsView(QObject *parent=nullptr, const char *name=nullptr); /*! \brief default destructor */ virtual ~QmitkImageStatisticsView(); /*! \brief method for creating the widget containing the application controls, like sliders, buttons etc. */ virtual void CreateQtPartControl(QWidget *parent) override; /*! \brief method for creating the connections of main and control widget */ virtual void CreateConnections(); /*! \brief not implemented*/ //bool IsExclusiveFunctionality() const; /*! \brief Is called from the selection mechanism once the data manager selection has changed*/ void OnSelectionChanged( berry::IWorkbenchPart::Pointer part, const QList &nodes ) override; static const std::string VIEW_ID; static const int STAT_TABLE_BASE_HEIGHT; public slots: /** \brief Called when the statistics update is finished, sets the results to GUI.*/ void OnThreadedStatisticsCalculationEnds(); /** \brief Update bin size for histogram resolution. */ void OnHistogramBinSizeBoxValueChanged(); protected slots: /** \brief Saves the histogram to the clipboard */ void OnClipboardHistogramButtonClicked(); /** \brief Saves the statistics to the clipboard */ void OnClipboardStatisticsButtonClicked(); /** \brief Indicates if zeros should be excluded from statistics calculation */ void OnIgnoreZerosCheckboxClicked( ); /** \brief Checks if update is possible and calls StatisticsUpdate() possible */ void RequestStatisticsUpdate(); /** \brief Jump to coordinates stored in the double clicked cell */ void JumpToCoordinates(int row, int col); /** \brief Toogle GUI elements if histogram default bin size checkbox value changed. */ void OnDefaultBinSizeBoxChanged(); + void OnShowSubchartBoxChanged(); + + void OnBarRadioButtonSelected(); + + void OnLineRadioButtonSelected(); + signals: /** \brief Method to set the data to the member and start the threaded statistics update */ void StatisticsUpdate(); protected: /** \brief Writes the calculated statistics to the GUI */ - void FillStatisticsTableView( const std::vector &s, - const mitk::Image *image ); - + void FillStatisticsTableView(const std::vector &s, + const mitk::Image *image ); std::vector CalculateStatisticsForPlanarFigure( const mitk::Image *image); void FillLinearProfileStatisticsTableView( const mitk::Image *image ); /** \brief Removes statistics from the GUI */ void InvalidateStatisticsTableView(); /** \brief Recalculate statistics for currently selected image and mask and * update the GUI. */ void UpdateStatistics(); /** \brief Listener for progress events to update progress bar. */ void UpdateProgressBar(); /** \brief Removes any cached images which are no longer referenced elsewhere. */ void RemoveOrphanImages(); /** \brief Computes an Intensity Profile along line and updates the histogram widget with it. */ void ComputeIntensityProfile( mitk::PlanarLine* line ); /** \brief Removes all Observers to images, masks and planar figures and sets corresponding members to zero */ void ClearObservers(); void Activated() override; void Deactivated() override; void Visible() override; void Hidden() override; void SetFocus() override; /** \brief Method called when itkModifiedEvent is called by selected data. */ void SelectedDataModified(); /** \brief Method called when the data manager selection changes */ void SelectionChanged(const QList &selectedNodes); /** \brief Method called to remove old selection when a new selection is present */ void ReinitData(); /** \brief writes the statistics to the gui*/ void WriteStatisticsToGUI(); void NodeRemoved(const mitk::DataNode *node) override; /** \brief Is called right before the view closes (before the destructor) */ virtual void PartClosed(const berry::IWorkbenchPartReference::Pointer& ) override; /** \brief Is called from the image navigator once the time step has changed */ void OnTimeChanged( const itk::EventObject& ); /** \brief Required for berry::IPartListener */ virtual Events::Types GetPartEventTypes() const override { return Events::CLOSED; } // member variables Ui::QmitkImageStatisticsViewControls *m_Controls; // if you have a planar figure selected, the statistics values will be saved in this one. std::vector m_PlanarFigureStatistics; QmitkImageStatisticsCalculationThread* m_CalculationThread; QmitkStepperAdapter* m_TimeStepperAdapter; unsigned int m_CurrentTime; QString m_Clipboard; // Image and mask data mitk::Image* m_SelectedImage; mitk::Image* m_SelectedImageMask; mitk::PlanarFigure* m_SelectedPlanarFigure; // observer tags long m_ImageObserverTag; long m_ImageMaskObserverTag; long m_PlanarFigureObserverTag; long m_TimeObserverTag; SelectedDataNodeVectorType m_SelectedDataNodes; bool m_CurrentStatisticsValid; bool m_StatisticsUpdatePending; bool m_StatisticsIntegrationPending; bool m_DataNodeSelectionChanged; bool m_Visible; + double m_HistogramBinSize; + std::vector m_WorldMinList; std::vector m_WorldMaxList; }; #endif // QmitkImageStatisticsView_H__INCLUDED 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 9d496cf7ea..2d4b3edae3 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui @@ -1,625 +1,643 @@ QmitkImageStatisticsViewControls true 0 0 548 800 Form 0 0 Qt::LeftToRight Feature Image: 0 0 None 0 0 Mask: 0 0 None -1 0 0 color: rgb(255, 0, 0); Error Message Qt::AutoText Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Ignore zero-valued voxels false Statistics 9 9 9 100 180 16777215 16777215 Qt::ScrollBarAsNeeded Qt::ScrollBarAsNeeded true QAbstractItemView::NoEditTriggers true true Qt::DotLine false 14 + + 1 + false false 80 true 80 false true true false 25 25 false false Mean Median StdDev RMS Max Min N V (mm³) Skewness Kurtosis Uniformity Entropy MPP UPP 0 0 0 0 0 0 Copy to Clipboard Qt::Horizontal QSizePolicy::Fixed 20 20 false copy complete table Qt::Horizontal 40 20 false - 150 - 160 + 400 + 450 Histogram false 0 0 0 0 16777215 16777215 Plot 0 0 Barchart true 0 0 0 0 Linegraph Qt::Horizontal 40 20 Use default bin size true + + + + Show Subchart + + + true + + + QFrame::NoFrame QFrame::Raised 0 0 0 0 0 - + 60 0 100 16777215 Bin size: - + Press enter to recalculate statistics with new bin size. true 5 0.000010000000000 1000000.000000000000000 - 10.000000000000000 + 100.000000000000000 + + + 0 + 0 + + 0 - + 0 0 0 0 0 0 0 Copy to Clipboard Qt::Horizontal - 40 - 20 + 413 + 17 Qt::Vertical 20 40 - QmitkHistogramJSWidget + QmitkC3jsWidget QWidget -
QmitkHistogramJSWidget.h
- 1 +
QmitkC3jsWidget.h