QCustomPlot Discussion and Comments

Efficient way to update an image?Return to overview

Dear users,

I'm currently using QCPColorMap with QCPColorScale to display an image (stored as a 1D std::vector). This image is belonging to a movie. I want to update, as fast as possible, the content of the image whenever the frame number to be displayed has been changed. Currently, I'm using the following code to update the image:

void VideoWidgetBasic::updateImage(std::vector<uint16_t> vec)
{
    for (int xIndex=0; xIndex<nx; ++xIndex)
        {
            for (int yIndex=0; yIndex<ny; ++yIndex)
              {
                 colorMap->data()->setCell(xIndex, yIndex, vec[yIndex*nx+xIndex]);
              }
        }
    replot();
}

The performance of this approach is really bad. To give some numbers, for 512*640 pixels images, a refresh takes about 17ms on MacOS and 30ms on Win10 (running on different machines). The same update, based on python and PyQtGraph, takes 8ms on MacOS and 10ms on Win10. What can I do to improve the performance of QCustomPlot? Is there a better way to update the content of an image?

Thanks for your time.

Regards,

Vincent

Well, a look at the source shows mData to be private. So you could derive from it, add a member that sets mData and then use assignment to copy it in.

class ColorMapData : public QCPColorMapData {
public:
  ColorMapData(
      int keySize,
      int valueSize,
      const QCPRange &keyRange,
      const QCPRange &valueRange )
  : QCPColorMapData( keySize, valueSize, keyRange, valueRange ) {
  }

  void
  copyData( int *src ) {
    double *dst = mData;
    double lower = src[0];
    double upper = src[0];

    for( int i = 0, end = keySize() * valueSize(); i < end; ++i, ++dst ) {
      auto const *pos = dst;
      *dst = *src++;
      lower = std::min( lower, *dst );
      upper = std::max( upper, *dst );
    }

    mDataBounds.upper = upper;
    mDataBounds.lower = lower;
  }
};

then given

std::vector<int> v(nx*ny);

instead of this:

   for (int xIndex=0; xIndex<nx; ++xIndex){
      for (int yIndex=0; yIndex<ny; ++yIndex){
        data->setCell( xIndex, yIndex, v[yIndex * ny + xIndex] );
      }
    }

you can write this:

    ColorMapData cmData( nx, ny, data()->keyRange(), data()->valueRange() );
    cmData.copyData( v.data() );
    *colorMap->data() = cmd;

I didn't do extensive benchmarking on this, but I got about a 3x speedup.

Well, a look at the source shows mData to be protected (not private)

Hi Thomas,

Many thanks, it works like a charm! I have slightly modified your code to avoid the creation of ColorMapData each time the updateImage function is called :

void ImageWidgetBasic::testUpdate(QVector<quint16> vec)
{
    quint16 *data = vec.data();
    colorMapData->copyData(data);
    colorMap->setData(colorMapData, true);
    replot(QCustomPlot::rpQueuedReplot);
}

Now I get, for an image of 1920*1536, an update takes 10ms on MacOS and 20ms on Win10. Better than my previous post, for an image whose size has been multiplied by a factor of 10! Incredible ;-)

BTW, another question : what is the meaning of the bool flag in the setData method? It is not clear to me. If it is set to false, then, the image is not updated even with the replot call. Is it logical? Can someone explain to me the role of the flag??

This is setData

/*!
  Replaces the current \ref data with the provided \a data.

  If \a copy is set to true, the \a data object will only be copied. if false,
  the color map takes ownership of the passed data and replaces the internal
  data pointer with it. This is significantly faster than copying for large
  datasets.
*/
void
QCPColorMap::setData( QCPColorMapData *data, bool copy ) {
  if( mMapData == data ) {
    qDebug() << Q_FUNC_INFO
             << "The data pointer is already in (and owned by) this plottable"
             << reinterpret_cast<quintptr>( data );
    return;
  }
  if( copy ) {
    *mMapData = *data;
  } else {
    delete mMapData;
    mMapData = data;
  }
  mMapImageInvalidated = true;
}

You cannot call it with false because the pointer you pass in remains owned by the vector and the QCPColorMap should not take ownership of the pointer. Calling it with false will eventually lead to double free. However, until then, on the following updates nothing will happen because mMapData == data will be true and mMapImageInvalidated will not be set to true.

ok, this is clearer to me now. Many thanks for your precious help Thomas!

Vincent