PDA

View Full Version : 10 - 12 bit grayscale QImage format display



moccij
29th January 2020, 17:33
Greetings,

Some scientific camera capture grayscale images at 10, 12 and 16 bits in addition to the common 8 bit format.
Such formats are little-endian and encode each pixels into 2 Bytes - the unused bits are set to zero.
Of course the 8bit grayscale pixel only requires 1 Byte.

The problem is that even with the newly introduced Grayscale 16 QImage format (>=Qt5.14), 10 and 12 bits per pixels grayscale images can't be properly displayed within QPainter without first processing the whole image.

The 8bit image is displayed just fine:


painter->drawImage(
transformedRect,
QImage(
(const unsigned char*) _image.memory,
_image.width,
_image.height,
QImage::Format_Grayscale8));


Even the 16bit shows no problems, decoding the char memory 2 Bytes per pixel:


painter->drawImage(
transformedRect,
QImage(
(const unsigned char*) _image.memory,
_image.width,
_image.height,
QImage::Format_Grayscale16));


However I have no solution for what is in between. As expected, the Grayscale16 format loads the QImage just fine, but since the expected maximum brightness value of 2^16 is much higher than 2^12 and more so than 2^10, the displayed image is very dark.
Working on the image pixels is not an option because it is more time consuming than the Grayscale16 format.

The QImage pixelColor method clearly shows how this decoding is done (but it is highly unlikely that this is the exact method used by QPainter as it is not optimized at all):


QColor QImage::pixelColor(int x, int y) const
{
if (!d || x < 0 || x >= d->width || y < 0 || y >= height()) {
qWarning("QImage::pixelColor: coordinate (%d,%d) out of range", x, y);
return QColor();
}
QRgba64 c;
const uchar * s = constScanLine(y);
switch (d->format) {
case Format_BGR30:
case Format_A2BGR30_Premultiplied:
c = qConvertA2rgb30ToRgb64<PixelOrderBGR>(reinterpret_cast<const quint32 *>(s)[x]);
break;
case Format_RGB30:
case Format_A2RGB30_Premultiplied:
c = qConvertA2rgb30ToRgb64<PixelOrderRGB>(reinterpret_cast<const quint32 *>(s)[x]);
break;
case Format_RGBX64:
case Format_RGBA64:
case Format_RGBA64_Premultiplied:
c = reinterpret_cast<const QRgba64 *>(s)[x];
break;
case Format_Grayscale16: {
quint16 v = reinterpret_cast<const quint16 *>(s)[x];
return QColor(qRgba64(v, v, v, 0xffff));
}
default:
c = QRgba64::fromArgb32(pixel(x, y));
break;
}
// QColor is always unpremultiplied
if (hasAlphaChannel() && qPixelLayouts[d->format].premultiplied)
c = c.unpremultiplied();
return QColor(c);
}


It's just how data is reinterpreted.
However, I don't really want to tamper with the built-in Qt classes.

The other idea I had is to alter the on-screen pixel with some sort of QPainter color-space transformation. I have tried composition to enhance brightness and contrast but failed. It was acceptable computationally-wise though, so maybe it is the right path to follow.

I feel like I'm missing something obvious since 10 and 12bits images are commonly used in medical appliances.
What should I do to correctly display such images at the same speed as 16bits images?

Thanks

d_stranz
29th January 2020, 20:39
Are these static images or a video stream?

If they are static, can't you simply shift each 16-bit word by 4 or 6 bits before constructing the QImage? You will still never get to full brightness unless you also set the low-order bits to 1, but then in that case you'd never get full black either. Another option would be to multiply every word by the ratio of 16 / n, where n is 10 or 12. This would expand the word values to cover the full range and you would get both true white and black.

If you really need speed, this transformation is something you could do in parallel, either through multiple threads that each work on part of the image or using a CUDA transformation on a video card to do the entire array in one operation.

moccij
30th January 2020, 10:27
d_stranz,

The camera transfers the captured image into the allocated read-only image memory.
Since I didn't implement any buffering technique, the new image simply overwrites the old one. Nonetheless, the computing part of the program reads the image fast enough for it not to be a problem.
The GUI process lives in another thread and displays the image asynchronously. Obviously this leads to flickering (the memory is constantly getting updated) but this does not represent a problem.

Rescaling the brightness dynamic range of the image into the right one was the first thing I tried. This solved the problem but the computational expense was unjustifiable, especially considering that QPainter manages to display the 16bit format much faster.

I also thought of bit shifting the memory but this still requires to touch the image or copying it, unless I can instruct QImage to parse the image memory in a different way.

Associating a different QColorSpace to the QImage seems to have no effect in how QPainter shows the image.

The question is: how to let QPainter display full-range grayscale 10 (or 12) bpp images without editing the image memory?

Added after 9 minutes:

I forgot to mention that I also tried other formats:

- QImage::Format_RGB555 uniquely maps the 10bit grayscale into the full dynamic range of red and green components;
- QImage::Format_RGB444 uniquely maps the 12bit grayscale into the full dynamic range of red, green and blue components.

Even if such formats lead to unavoidable color banding, this definitely proves that Qt is perfectly capable to handle "uncommon" formats. Such formats are as fast as grayscale16.

d_stranz
30th January 2020, 16:34
The question is: how to let QPainter display full-range grayscale 10 (or 12) bpp images without editing the image memory?

Can't give you much advice here, since I have never had to deal with such problems. A QImage constructed via a pointer to a data buffer does not make a copy of the buffer, so there is no copy overhead.

You could look into the Qt source code to see what is happening with the two RGB formats you have tried. That might give you some insight into how to map the images using greyscale alone.

^NyAw^
31st January 2020, 10:16
Hi,

You could try VTK library. It supports 10 and 12 bit images and there is also a QVTKWidget.

d_stranz
31st January 2020, 17:58
You could try VTK library.

Oh, but what a headache. VTK is as big as Qt and even harder to learn.