PDA

View Full Version : How to design an interactive image viewer for big gray-scale images



Notsocute
30th January 2020, 00:36
Hello Qt users,

I apologize in advance if the answer was already given somewhere. I did some research on many forums and tutorials, but it seemed that I could only find partial answers to my question, so as I am a true beginner in Qt I thought asking real humans could be a good option...

So I would like to create a simple image viewer, but with some specific image constraints and some specific features:

1) image constraints:
_ suited for satellite images (mainly tiff format) with possibly massive size (up to several giga-pixels)
_ always displayed as gray-scale images, but with any pixel type and depth (among uint8 uint16, int16, uint32, int32, possibly float32 and float64)
_ if image contains several bands, just interpret them as a stack of gray-scale images (and for eg. read only one band), never RGB or colored image.
-> note : the process of reading properly the pixel data from tiff images should to be handled beforehand by gdal library

2) viewer features:
_ basic "interactive" view using mouse (pan, scroll) with decent execution speed !
_ possibility to adjust brightness/contrast (for e.g. with sliders)
_ on-the-fly reading of pixel coordinates (given in "native" image units in case of pan/zoom) and "native" image pixel value. By "native" I mean in the range of original image data (ex: 10,000 x 10,000 pixels with gray-scale in [0;65535] even if the image is displayed with a size of 500 x 500 pixels and gray-scale in [0;255] for example).
An good example of what I would like to do is the interactive image viewer in QGIS program.

Correct me if I am wrong but it looks like I will have to handle at least 2 versions of my image in parallel, I mean:
1) an original image (or close to it) with full resolution in pixels and all original pixel values
2) a transformed image for display purpose, with reduced size and values transformed into gray-scale
Then I imagine I have to make some conversions between the 2 images, like convert the mouse display coordinates into native image coordinates, as well as convert native gray-scale values into a convenient scale for display ([0;255]?)

So I would appreciate some general advice to create this viewer, like maybe :
_ which key class(es) should I use (QImage?QPixmap?both?others?) and maybe how they are connected/nested ?
_ maybe which key functions (to read the mouse coordinates) are vital to do the job ?
_ how to avoid some unnecessary copy of image data, especially if the image is huge (several GBytes) ?
_ how to handle properly gray-scale values of any numerical type (int8/uint8 to int32/uint32) ? For eg. QImage::Format seems way too complicated for that purpose and never really matches what I want (i.e. only gray levels, not artistic combination of colors)

I will appreciate any comment/suggestion/link towards something similar that I missed...
So thanks a lot for you answers !

d_stranz
30th January 2020, 17:52
I will appreciate any comment/suggestion/link towards something similar that I missed...

Perhaps Paraview (https://www.paraview.org/) already does a lot of what you need?

Notsocute
30th January 2020, 23:26
Hi, thank you but that was a rather unexpected suggestion, as my main goal here is to learn and develop in Qt. I guess that's why people go on Qt forum, don't they ?

As I said, I do know some software that do more or less the job (like QGIS), but it never does it exactly the way I want. Moreover I understood that Qt was the perfect choice to achieve solid performance with decent code simplicity.

So to put my question in a nutshell:
I would like to write an interactive image viewer in C++/Qt
a) with good speed performance and memory usage (in case of big images)
b) reading only gray-scale tiff image, but with different possible pixel depth (8, 16 or 32 bits)
c) with basic mouse interactivity (pan/scan/read pixel position and value)

So thanks again for your answers/suggestions.

d_stranz
31st January 2020, 19:46
I am a true beginner in Qt ... So I would like to create a simple image viewer

but you didn't say it had to be using Qt, and based on your detailed list of features what you have outlined is a major piece of work. You also didn't say what your application was, so I suggested something that might work out of the box for you.

But if you really want to write it yourself...


a) with good speed performance and memory usage (in case of big images)
b) reading only gray-scale tiff image, but with different possible pixel depth (8, 16 or 32 bits)
c) with basic mouse interactivity (pan/scan/read pixel position and value)

QImage and QPixmap are the two primary Qt classes for image manipulation and display. QPixmap is optimized for display on screen, QImage is optimized for I/O and manipulation. Support for some image file formats (like tiff) require an external Qt plugin (DLL) that must be loaded at runtime. QImage uses a copy-on-write model for its data, so if you pass a pointer to an external buffer when creating a QImage, the data will not be copied. The external buffer has to remain valid for the life of the QImage. QPixmap also uses data sharing.

I would guess that the tiff reader would be able to create a QImage of whatever bit depth is represented in the file.

The usual way to display an image is to convert it to a QPixmap and install it on a QLabel in a normal QWidget-based UI, or to create a QGraphicsScene with a QGraphicsPixmapItem containing the QPixmap. If you want to interactively places labels or other annotations on a displayed image, I would recommend using the QGraphicsScene approach. Annotations could be added in a reversible manner by adding them as independent QGraphicsItem instances in the scene.

Mouse interactivity (pan / zoom, etc.) can be achieved by using a QScrollArea and QLabel and manipulating the viewport (region of the image that is displayed in the window), or by using the QGraphicsScene / QGraphicsView and manipulating the view's sceneRect().

Retrieving pixel values can only be done using a QImage. A QPixmap is basically a write-only output device. Mouse events on either a QLabel or QGraphicsPixmapItem will tell you the screen or window coordinates of the mouse location, which you would have to map to the image location to be able to retrieve the pixel value. This would of course be affected by any pan or zoom transformation you have applied to the image. QPixmap will interpolate pixel values when scaling an image, so there may not be a 1:1 correspondence between the screen pixel coordinates and pixel coordinates in the source image.

Notsocute
2nd February 2020, 01:27
Hi d_tranz,

Thanks again for all your useful answers. Also I think I see your first point, maybe what I want to achieve is a little bit disproportionate for a beginner... I probably got too optimistic because I could more or less get the same result done in python/matplotlib with very few effort, but with 2 noticeable drawbacks:
_ the speed performance is awful (so I can forget about handling huge images)
_ many things are done "under the hood" by python, like mouse interactivity which is great but on which I have no control

So just to focus on one goal at a time, I will be happy to be able to display correctly a tiff image with gray scale, without worrying about performance and/or mouse interactivity. For that, yours explanations about QImage and QPixmap (+QLabel and so...) were very clear and helpful, especially about the copy-on-write feature.

However, one details remains unclear to me about displaying the right gray-scale intensity on screen. So my tiff reader ("gdal") should handle the proper bit depth, and logically so can QImage if I use a pointer towards the original data. Now how do I get my final image displayed (with QPixmap if got it right) with correct gray intensity, again without any color merging/color palette interpretation ?

Example: on original image, pixel value range is [2,000; 8,000] (coded on 16 bits, here not fully used) and I wish a transformation towards display range (ex. [0;255] or whatever the graphics card needs). What's the right way to do that ?

Thanks again.

d_stranz
2nd February 2020, 04:47
I could more or less get the same result done in python/matplotlib with very few effort

Yes, python is very good for that, because of all of the third-party packages you can install and use, like matplotlib, numpy, and so forth.


Example: on original image, pixel value range is [2,000; 8,000] (coded on 16 bits, here not fully used) and I wish a transformation towards display range (ex. [0;255] or whatever the graphics card needs). What's the right way to do that ?

You should be using the qtiff plugin to read TIFF files. This will directly read TIFF files and create images in QPixmap or QImage formats. This plugin is found in your Qt distribution, in the plugins / imageformats directory. Google for "Qt Image Formats" and read the section on deployment to be sure you put the plugin in the right place so that the QImage loader can find it at runtime.

Notsocute
4th February 2020, 21:57
Ok many thanks again I appreciate.