Tuesday, 12 March 2013

Plot Multi-Channel Histogram in QWT -Part 2

This is the second part in a series on how to do multi-channel plots in QWT. These kinds of plots can be done very simply in Matlab but are more challenging to implement in C++. Armed with the QWT library, we have investigated how to overlay multiple channel histograms on each other. Here I will show you how to make a plot similar to what you would do with the subplot() function in Matlab. This method simply puts multiple channels into one window in a grid form; this method is more presentable and makes it easier to compare plots. Lets get started...

Code

The most important part of the code is the PlotMatrix class which uses a QGridLayout to arrange the sub-plots into a matrix with specified number of rows and cols.The class is defined in the plotmatix.h header file and implemented in plotmatrix.cpp. In main.cpp we define and implement a MainWindow class which inherits from the PlotMatrix class, the histograms are created from an image and displayed in the MainWindow.

The Main function and others

The MainWindow class is a derived class from the PlotMatrix class. Here, we define a 2 by 3 matrix of plots in a static manner(Although, we only want to plot 3 channels). Grid lines are included in the plots for fun but this can be removed or edited as needed.
#include <QApplication>
#include <plotmatrix.h>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;

class MainWindow: public PlotMatrix
{
public:
   MainWindow():PlotMatrix( 2, 3 )
   {
     for ( int row = 0; row < numRows(); row++ )
     {
        for ( int col = 0; col < numColumns(); col++ )
        {
            //set default scale if any.
            setAxisScale( QwtPlot::yLeft, row, col, 0, 100 );
            setAxisScale( QwtPlot::xBottom, row, col, 0, 255 );
            QwtPlot *plt = plot( row, col );
            plt->setCanvasBackground( QColor( Qt::darkGray ) );
            //Allows the Axis to scale automatically. 
            plt->setAxisAutoScale(QwtPlot::yLeft, true);
            //Show plot grid lines just for aesthetics.
            QwtPlotGrid *grid = new QwtPlotGrid();
            grid->enableXMin( true );
            grid->setMajorPen( Qt::white, 0, Qt::DashLine);
            grid->setMinorPen( Qt::gray, 0 , Qt::DotLine );
            grid->attach( plt );
        }
     }
   }
};

void getPoints(MatND& hist, int* histSize, QPolygonF& points)
{
    points.clear();
    for( int h = 0; h < histSize[0]; ++h) {
      float bin_value = hist.at<float>(h);
      points << QPointF((float)h, bin_value);
    }
}

class Curve: public QwtPlotCurve
{
public:
    Curve( const QString &title, const QColor &colour, int penSize, 
           QPolygonF &points ):QwtPlotCurve( title )
    {
        QColor c = colour;
        c.setAlpha( 150 );//handle colour transparency
        setPen(c, penSize);
        setBrush( c );
        setSamples( points );
        setRenderHint( QwtPlotItem::RenderAntialiased );
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    if (argc < 2)
        return 1;

    //Read input image
    Mat img = imread(argv[1]);

    //Convert to grayscale
    if (img.data && img.channels() != 3)
       return 1;

    MainWindow mainWindow;
    mainWindow.resize( 600, 400 );

    int histSize[] = {256}; // number of bins
    float hranges[] = {0.0, 255.0}; // min and max pixel value
    const float* ranges[] = {hranges};
    int channels[] = {0}; // only 1 channel used

    std::vector<Mat> rgbChannels(3);
    split(img, rgbChannels);

    MatND hist;
    QPolygonF points;

    calcHist(&rgbChannels[2], 1, channels, Mat(), hist, 1, histSize,ranges);
    /*Insert the points that should be plotted on the graph in a
    Vector of QPoints or a QPolgonF */
    getPoints(hist, histSize, points);
    Curve *curve = new Curve("Red Channel", Qt::red, 2, points);
    curve->attach( mainWindow.plot(0,0) ); // Attach curve to the plot

    calcHist(&rgbChannels[1], 1, channels, Mat(), hist, 1, histSize,ranges);
    getPoints(hist, histSize, points);
    curve = new Curve("Green Channel", Qt::green, 2, points);
    curve->attach( mainWindow.plot(0,1) );

    calcHist(&rgbChannels[0], 1, channels, Mat(), hist, 1, histSize,ranges);
    getPoints(hist, histSize, points);
    curve = new Curve("Blue Channel", Qt::blue, 2, points);
    curve->attach( mainWindow.plot(0,2) );


    mainWindow.show(); //Show plot
    return a.exec();
}

PlotMatrix class

This implementation uses a QVector of pointers to QwtPlot instances to represent the matrix of subplots. It provides a function plot(row, col) which returns the pointer to the desired plot.
//PlotMatrix.h

#ifndef _PLOT_MATRIX_H_
#define _PLOT_MATRIX_H_

#include <qframe.h>
#include <qwt_plot.h>

class PlotMatrix: public QFrame
{
    Q_OBJECT

public:
    PlotMatrix( int rows, int columns, QWidget * parent = NULL );
    virtual ~PlotMatrix();

    int numRows() const;
    int numColumns() const;

    QwtPlot* plot( int row, int column );
    const QwtPlot* plot( int row, int column ) const;

    void setAxisScale(int axisId, int row, int col,
        double min, double max, double step = 0 );

private:
    QVector<QwtPlot *> plotWidgets;
};

#endif

//PlotMatrix.cpp
//This is based on the PlotMatrix implementation in the Qwt Samples. 
#include <qlayout.h>
#include <qpen.h>
#include <qwt_plot.h>
#include <qwt_scale_widget.h>
#include <qwt_scale_draw.h>
#include "plotmatrix.h"

PlotMatrix::PlotMatrix( int numRows, int numColumns, QWidget *parent ):
    QFrame( parent )
{
    plotWidgets.resize( numRows * numColumns );

    QGridLayout *layout = new QGridLayout( this );
    for ( int row = 0; row < numRows; row++ )
    {
        for ( int col = 0; col < numColumns; col++ )
        {
            QwtPlot *plot = new QwtPlot( this );
            layout->addWidget( plot, row, col );
            plotWidgets[row * numColumns + col] = plot;
        }
    }
}

PlotMatrix::~PlotMatrix()
{
    foreach(QwtPlot* p, plotWidgets)
        delete p;
}

int PlotMatrix::numRows() const
{
    const QGridLayout *l = qobject_cast<const QGridLayout *>( layout() );
    if ( l )
        return l->rowCount();

    return 0;
}

int PlotMatrix::numColumns() const
{
    const QGridLayout *l = qobject_cast<const QGridLayout *>( layout() );
    if ( l )
        return l->columnCount();
    return 0;
}

QwtPlot* PlotMatrix::plot( int row, int column )
{
    const int index = row * numColumns() + column;
    if ( index < plotWidgets.size() )
        return plotWidgets[index];

    return NULL;
}

const QwtPlot* PlotMatrix::plot( int row, int column ) const
{
    const int index = row * numColumns() + column;
    if ( index <plotWidgets.size() )
        return plotWidgets[index];

    return NULL;
}

void PlotMatrix::setAxisScale( int axis, int row, int col,
    double min, double max, double step )
{

    QwtPlot *plt = plot( row, col );
    if ( plt )
    {
        plt->setAxisScale( axis, min, max, step );
        plt->updateAxes();
    }
}

Result

Conclusion

The subplot() is one of the most useful matlab functions if you plot several graphs or images. Now you can have something similar in QWT although not as simple. It is also possible to plot multi-channel histograms in a 3 dimensional plot, we leave this for another time. Happy Coding!.

No comments:

Post a Comment