Commit e22602ea authored by Jan Möbius's avatar Jan Möbius

Merge branch 'propvis-histogram-mr' into 'master'

Implement propvis histograms

Cf. issue #75 

See merge request !186
parents b8177901 3f56d32b
Pipeline #3633 passed with stage
in 61 minutes and 17 seconds
/*===========================================================================*\
* *
* OpenFlipper *
* *
* OpenFlipper *
* Copyright (c) 2001-2015, RWTH-Aachen University *
* Department of Computer Graphics and Multimedia *
* All rights reserved. *
......@@ -36,26 +36,36 @@
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
* *
* *
\*===========================================================================*/
/*
* ValenceHistogramWidget.cc
*
* Created on: Jan 27, 2016
* Author: hc
*/
// Based on QtHistogram by hc
#include "ValenceHistogramWidget.hh"
#include "QtHistogramWidget.hh"
#include <QPainter>
ValenceHistogramWidget::ValenceHistogramWidget(QWidget *parent) :
histogram_(0) {
namespace ACG {
namespace QtWidgets {
QtHistogramWidget::QtHistogramWidget(QWidget *parent)
: QWidget(parent),
color_(QColor::fromRgbF(0.518, 0.573, 0.643, 1.0))
{}
QtHistogramWidget::~QtHistogramWidget() = default;
void QtHistogramWidget::setHistogram(std::unique_ptr<Histogram> histogram) {
histogram_ = std::move(histogram);
this->update();
}
void ValenceHistogramWidget::paintEvent(QPaintEvent *event) {
void QtHistogramWidget::setColorCoder(std::unique_ptr<IColorCoder> color_coder) {
color_coder_ = std::move(color_coder);
this->update();
}
void QtHistogramWidget::paintEvent(QPaintEvent *event) {
if (!histogram_) {
QWidget::paintEvent(event);
return;
......@@ -64,25 +74,19 @@ void ValenceHistogramWidget::paintEvent(QPaintEvent *event) {
/*
* Analyze histogram/
*/
std::vector<size_t>::iterator nonzero_begin = histogram_->begin();
for (; nonzero_begin != histogram_->end() && *nonzero_begin == 0;
++nonzero_begin);
if (nonzero_begin == histogram_->end()) return;
std::vector<size_t>::iterator nonzero_end = histogram_->end();
for (; (nonzero_end-1) != nonzero_begin && *(nonzero_end-1) == 0;
--nonzero_end);
const std::vector<size_t> &bins = histogram_->getBins();
const std::vector<double> &bin_widths = histogram_->getBinWidths();
const double total_width = histogram_->getTotalWidth();
const size_t hist_size = std::distance(nonzero_begin, nonzero_end);
const size_t hist_max = *std::max_element(nonzero_begin, nonzero_end);
const size_t ofs = std::distance(histogram_->begin(), nonzero_begin);
const size_t hist_max = *std::max_element(bins.begin(), bins.end());
/*
* Establish regions
*/
const qreal labelHeight = 16;
QRectF paint_rect = this->contentsRect();
QRectF bargraph_rect = paint_rect;
bargraph_rect.setBottom(bargraph_rect.bottom() - 16);
bargraph_rect.setBottom(bargraph_rect.bottom() - labelHeight);
QRectF label_rect = paint_rect;
label_rect.setTop(bargraph_rect.bottom());
QPainter painter(this);
......@@ -91,28 +95,39 @@ void ValenceHistogramWidget::paintEvent(QPaintEvent *event) {
* Painter attributes.
*/
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(QColor::fromRgbF(0.518, 0.573, 0.643, 1.0));
painter.setFont(this->font());
const qreal stride =
static_cast<qreal>(bargraph_rect.width()) / hist_size;
const qreal gap = (stride > 8) ? 1.0 : 0.0;
const qreal avg_width = bargraph_rect.width() / bins.size();
const qreal gap = (avg_width > 8) ? 1.0 : 0.0;
const qreal label_gap = 4;
QRectF barRect(0, 0, stride - gap, 0);
const qreal scale = static_cast<qreal>(bargraph_rect.height()) / hist_max;
const qreal y_scale = bargraph_rect.height() / hist_max;
const qreal total_gap = (bins.size() - 1) * gap;
const qreal total_barwidth = bargraph_rect.width() - total_gap;
QRectF barRect;
/*
* Draw.
*/
int cnt = 0;
double cumulative_width = 0.0;
qreal xpos = 0;
qreal lastLabelX = label_rect.left();
for (std::vector<size_t>::iterator it = nonzero_begin;
it != nonzero_end; ++it, ++cnt) {
const size_t n_bins = bins.size();
for (size_t idx = 0; idx < n_bins; ++idx) {
const double bin_width = bin_widths[idx];
const qreal bar_width = total_barwidth * (bin_width / total_width);
// Bar
painter.setPen(Qt::NoPen);
barRect.setHeight(scale * (*it));
barRect.moveBottomLeft(
bargraph_rect.bottomLeft() + QPoint(stride * cnt, 0));
// relative position t in [0..1] for the middle of the current bin
const double t = (cumulative_width + bin_width/2) / total_width;
cumulative_width += bin_width;
painter.setBrush(getColor(t));
barRect.setWidth(bar_width - gap);
barRect.setHeight(y_scale * bins[idx]);
barRect.moveBottomLeft(bargraph_rect.bottomLeft() + QPoint(xpos, 0));
if (gap > 0.0)
painter.drawRoundedRect(barRect, 3, 3, Qt::AbsoluteSize);
else
......@@ -120,21 +135,43 @@ void ValenceHistogramWidget::paintEvent(QPaintEvent *event) {
// Label
painter.setPen(Qt::black);
QString labelText = QString::number(cnt + ofs);
QRectF labelBB =
painter.boundingRect(
QRectF(barRect.center().x()-50, label_rect.y(),
100.0, label_rect.height()),
Qt::AlignHCenter | Qt::AlignBottom, labelText);
qreal labelX = 0;
QString labelText;
switch (histogram_->getLabelType()) {
case Histogram::LabelType::PerBin:
labelX = barRect.center().x();
labelText = histogram_->getBinLabel(idx);
break;
case Histogram::LabelType::PerBoundary:
labelX = barRect.x();
labelText = histogram_->getBoundaryLabel(idx);
break;
}
QRectF labelBB = painter.boundingRect(
QRectF(labelX - (label_distance_/2), label_rect.y(),
label_distance_, label_rect.height()),
Qt::AlignHCenter | Qt::AlignBottom, labelText);
if (labelBB.left() >= lastLabelX + label_gap) {
painter.drawText(labelBB, Qt::AlignHCenter | Qt::AlignBottom,
labelText);
lastLabelX = labelBB.right();
}
xpos += bar_width;
}
// TODO: draw last perBoundary label?
}
void ValenceHistogramWidget::setHistogram(std::vector<size_t> *histogram) {
histogram_ = histogram;
this->update();
QColor QtHistogramWidget::getColor(double val)
{
if (color_coder_) {
return color_coder_->color_qcolor(val);
} else {
return color_;
}
}
}
}
/*===========================================================================*\
* *
* OpenFlipper *
* *
* OpenFlipper *
* Copyright (c) 2001-2015, RWTH-Aachen University *
* Department of Computer Graphics and Multimedia *
* All rights reserved. *
......@@ -36,33 +36,53 @@
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
* *
* *
\*===========================================================================*/
/*
* ValenceHistogramWidget.hh
*
* Created on: Jan 27, 2016
* Author: hc
*/
#ifndef QTHISTOGRAM_HH
#define QTHISTOGRAM_HH
// Based on ValenceHistogramWidget by hc
#ifndef PLUGIN_INFOMESHOBJECT_VALENCEHISTOGRAMWIDGET_HH_
#define PLUGIN_INFOMESHOBJECT_VALENCEHISTOGRAMWIDGET_HH_
#include <memory>
#include <QWidget>
#include "../Config/ACGDefines.hh"
#include "../Utils/Histogram.hh"
#include "../Utils/ColorCoder.hh"
class ValenceHistogramWidget: public QWidget {
namespace ACG {
namespace QtWidgets {
class ACGDLLEXPORT QtHistogramWidget : public QWidget {
Q_OBJECT
public:
ValenceHistogramWidget(QWidget *parent);
explicit QtHistogramWidget(QWidget *parent);
~QtHistogramWidget();
QtHistogramWidget(const QtHistogramWidget &other) = delete;
QtHistogramWidget& operator=(const QtHistogramWidget &other) = delete;
void setHistogram(std::vector<size_t> *histogram);
void setHistogram(std::unique_ptr<Histogram> histogram);
void setColorCoder(std::unique_ptr<IColorCoder> color_coder);
protected:
void paintEvent(QPaintEvent *event);
QColor getColor(double val); // val in [0..1]
std::unique_ptr<Histogram> histogram_ = nullptr;
double label_distance_ = 100;
QColor color_; // ignored if we have a color coder
std::unique_ptr<IColorCoder> color_coder_ = nullptr;
std::vector<size_t> *histogram_;
};
#endif /* PLUGIN_INFOMESHOBJECT_VALENCEHISTOGRAMWIDGET_HH_ */
} // namespace QtWidgets
} // namespace ACG
#endif // QTHISTOGRAM_HH
......@@ -93,51 +93,13 @@ ACG::Vec4uc ColorCoder::color4(float _v) const
return signed_mode_ ? color_signed(_v) : color_unsigned(_v);
}
/// color coding
ACG::Vec3uc ColorCoder::color(float _v) const
{
ACG::Vec4uc c;
if (signed_mode_)
c = color_signed(_v);
else
c = color_unsigned(_v);
return ( ACG::Vec3uc(c[0], c[1], c[2]) );
}
/// color coding
ACG::Vec3f ColorCoder::color_float(float _v) const
{
ACG::Vec4uc c;
if (signed_mode_)
c = color_signed(_v);
else
c = color_unsigned(_v);
return (ACG::Vec3f(c[0], c[1], c[2]) / 255.f);
}
/// color coding
ACG::Vec4f ColorCoder::color_float4(float _v) const
{
ACG::Vec4uc c;
if (signed_mode_)
c = color_signed(_v);
else
c = color_unsigned(_v);
ACG::Vec4uc c = color4(_v);
return (ACG::Vec4f(c[0], c[1], c[2], c[3]) / 255.f);
}
/// color coding
QColor ColorCoder::color_qcolor(float _v) const
{
ACG::Vec4uc c;
if (signed_mode_)
c = color_signed(_v);
else
c = color_unsigned(_v);
return(QColor(c[0], c[1], c[2], c[3]));
}
/// min scalar value
float ColorCoder::min() const
{
......
......@@ -61,6 +61,7 @@
#include <ACG/Math/VectorT.hh>
#include <ACG/Config/ACGDefines.hh>
#include <QColor>
#include "IColorCoder.hh"
//== NAMESPACES ===============================================================
......@@ -72,7 +73,7 @@ namespace ACG {
*
*
*/
class ACGDLLEXPORT ColorCoder {
class ACGDLLEXPORT ColorCoder : public IColorCoder {
public:
/// Default constructor.
......@@ -82,30 +83,16 @@ public:
void set_range(float _min, float _max, bool _signed);
/// color coding
ACG::Vec4uc color4(float _v) const;
ACG::Vec4uc color4(float _v) const override;
/// color coding
ACG::Vec3uc color(float _v) const;
/// color coding
ACG::Vec3f color_float(float _v) const;
/// color coding
ACG::Vec4f color_float4(float _v) const;
/// color coding
QColor color_qcolor(float _v) const;
ACG::Vec4f color_float4(float _v) const override;
\
/// min scalar value
float min() const;
float min() const override;
/// max scalar value
float max() const;
// Make the color coder usable as a function operator.
inline ACG::Vec4f operator() (float _v) const {
return color_float4(_v);
}
float max() const override;
private:
......@@ -116,6 +103,8 @@ private:
bool signed_mode_;
};
//=============================================================================
}// namespace ACG
//=============================================================================
......
#ifndef ACG_COLORCONVERSION_HH
#define ACG_COLORCONVERSION_HH
#include <QColor>
#include <ACG/Math/VectorT.hh>
namespace ACG {
inline Vec4f to_Vec4f(const QColor _color){
return Vec4f(_color.redF(),
_color.greenF(),
_color.blueF(),
_color.alphaF());
}
} // namespace ACG
#endif // COLORCONVERSION_HH
#include "Histogram.hh"
namespace ACG {
template<>
QString HistogramT<double>::getBoundaryLabel(size_t idx) const {
// TODO: choose accuracy based on avg_bin_size_
return QString::number(bin_boundaries_[idx], 'g', 2);
}
} // namespace ACG
/*===========================================================================*\
* *
* OpenFlipper *
* Copyright (c) 2001-2015, RWTH-Aachen University *
* Department of Computer Graphics and Multimedia *
* All rights reserved. *
* www.openflipper.org *
* *
*---------------------------------------------------------------------------*
* This file is part of OpenFlipper. *
*---------------------------------------------------------------------------*
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* *
* 1. Redistributions of source code must retain the above copyright notice, *
* this list of conditions and the following disclaimer. *
* *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* *
* 3. Neither the name of the copyright holder nor the names of its *
* contributors may be used to endorse or promote products derived from *
* this software without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
* *
\*===========================================================================*/
// Author: Martin Heistermann, <martin.heistermann()rwth-aachen.de>
#ifndef ACG_HISTOGRAM_HH
#define ACG_HISTOGRAM_HH
#include <vector>
#include <cassert>
#include <memory>
#include <exception>
#include <algorithm>
#include <type_traits>
#include <QString>
#include "../Config/ACGDefines.hh"
namespace ACG {
class ACGDLLEXPORT Histogram {
public:
enum class LabelType {
PerBin,
PerBoundary,
};
virtual ~Histogram() = default;
const std::vector<size_t> &getBins() const { return bins_; }
const std::vector<double> &getBinWidths() const { return bin_widths_; }
virtual double getTotalWidth() const = 0;
virtual LabelType getLabelType() const = 0;
virtual QString getBoundaryLabel(size_t /*idx*/) const { assert(false); return QString();}
virtual QString getBinLabel (size_t /*idx*/) const { assert(false); return QString();}
protected:
std::vector<size_t> bins_;
std::vector<double> bin_widths_;
};
// we need to be careful with ranges, some sums (e.g. INT_MAX - INT_MIN) do not fit into a signed int,
// so we store bin sizes as doubles. With specialization or some tricks we
// could probably use the next-biggest integer type, but if we're using
// the biggest integer type already, we should to fall back to double anyways.
template<typename T>
class HistogramT : public Histogram {
public:
HistogramT(const std::vector<int> &histogram,
const std::vector<T> &bin_boundaries,
const std::vector<double> &bin_widths)
{
if (bins_.size() != bin_widths_.size()
|| bins_.size() + 1 != bin_boundaries_.size()) {
throw std::runtime_error("Histogram constructor sizes don't match.");
}
bins_ = histogram;
bin_boundaries_ = bin_boundaries;
bin_widths_ = bin_widths;
double range = bin_boundaries.back() - bin_boundaries.front();
avg_bin_size_ = range / bins_.size();
}
template<typename IterT>
HistogramT(IterT begin, IterT end, size_t max_bins)
{
static_assert(std::is_assignable<T&, typename IterT::value_type>::value, "IterT incompatible with T.");
static_assert(std::is_floating_point<typename IterT::value_type>::value, "HistogramT currently only supports floating point values.");
assert(max_bins > 0);
const size_t n = std::distance(begin, end);
if (n == 0) return;
const auto minmax = std::minmax_element(begin, end);
const T min = *minmax.first;
const T max = *minmax.second;
const double min_dbl = static_cast<double>(min);
const double range = static_cast<double>(max) - min_dbl;
const size_t n_bins_max = std::min(max_bins, n);
bin_boundaries_.reserve(n_bins_max + 1);
T last_boundary = min;
bin_boundaries_.push_back(min);
for (size_t i = 1; i < n_bins_max; ++i) {
// Adding range/n_bins to a accumulator might seem more efficient/elegant,
// but might cause numeric issues.
// This multiplication order is bad for huge ranges that cause overflows,
// however I assume tiny ranges are more common than huge values and more
// important to get right. If you disagree, add a case distinction or something better.
T boundary = static_cast<T>(min + (i * range) / n_bins_max);
// avoid zero-sized bins (happens for many ints with values in a small range)
if (boundary != last_boundary || i == 0) {
bin_boundaries_.push_back(boundary);
bin_widths_.push_back(boundary - last_boundary);
}
last_boundary = boundary;
}
bin_boundaries_.push_back(max); // avoid rounding issues etc by explicitly picking max.
bin_widths_.push_back(max - last_boundary);
bin_boundaries_.shrink_to_fit();
size_t n_bins = bin_boundaries_.size() - 1;
bins_.resize(n_bins);
// note that due to rounding, our bins may have differing sizes, which matters
// if we handle integral types (relative size difference worst case: bin width 1 vs 2).
// Be careful to select the right bin.
std::for_each(begin, end, [&](const T &val) {
auto it = std::upper_bound(bin_boundaries_.begin(), bin_boundaries_.end(), val);
if (it == bin_boundaries_.end()) --it; // the last value is exactly max!
size_t idx = std::distance(bin_boundaries_.begin(), it);
assert(idx > 0);
++bins_[idx - 1];
});
avg_bin_size_ = range / n_bins;
}
const std::vector<T> &getBinBoundaries() const {
return bin_boundaries_;
}
double getTotalWidth() const override
{
return bin_boundaries_.back() - bin_boundaries_.front();
}
LabelType getLabelType() const override
{
return LabelType::PerBoundary;
}
QString getBoundaryLabel (size_t idx) const override;
private:
std::vector<T> bin_boundaries_;
double avg_bin_size_ = 0.0;
};
template<typename T>
QString HistogramT<T>::getBoundaryLabel(size_t idx) const {
return QString::number(bin_boundaries_[idx]);
}
} // namespace ACG
#endif // ACG_HISTOGRAM_HH
#ifndef ACG_ICOLORCODER_HH
#define ACG_ICOLORCODER_HH
#include <ACG/Math/VectorT.hh>
#include <ACG/Config/ACGDefines.hh>
#include <QColor>
namespace ACG {
class ACGDLLEXPORT IColorCoder {
public:
virtual ~IColorCoder() = default;
virtual ACG::Vec4uc color4(float _v) const = 0;
virtual ACG::Vec4f color_float4(float _v) const = 0;
/// min scalar value
virtual float min() const = 0;
/// max scalar value