PDA

View Full Version : Advice needed - QImage vs. OpenGL for 2D data display



d_stranz
27th August 2016, 04:48
I have scientific data in the form of x vs. y with a color gradient that assigns a color to each (x, y) pair (the z axis). The problem is that the aspect ratio is crazy. There might be 100K points (or more) in the x dimension, but only 400 points in the y dimension. Because the data matrix is sparse, most of the z axis values will be zero. The z-values are "spiky" - if you scan along the x axis, there will be sharp ~Lorentzian spikes for 10 or so points separated by hundreds of zeros. The spikes might extend for 3 or 4 points in the y dimension.

I need to implement a 2D display that has the ability to display the entire matrix in a "normal" sized window, say 1000 x 1000 pixels. This will generally require expanding the y range so that 400 points fill a 1000 point range. That's not hard and can be implemented with interpolation.

What about the x axis? Given that maybe 100 data points will map to the same x pixel, what is the best choice? Choose the maximum z value in the range? The average z value? It would be confusing to the user if, as the plot was zoomed in, peaks suddenly started appearing where there were no peaks at lower zoom level.

Zooming has to be fast. Resampling a section of a 400 x 100K point matrix into a 1000 x 1000 pixel image could take forever if every pixel has to be recomputed each time. It is sparse data, so I really don't want to have to explode it into a 400 x 100K matrix where most of it is zero.

Finally, what is the best way to implement this for display? Using a QImage? Or implementing it as a texture in OpenGL? Are there GPU-side OpenGL mechanisms to create a texture using the full data matrix size (400 x 100K) that will allow me to map and zoom without huge CPU-side overhead? I could constrain the z-axis gradient to 0 - 255, so a matrix of that size would only be 40 MB.

If would appreciate any advice.

Cruz
1st September 2016, 11:03
This will generally require expanding the y range so that 400 points fill a 1000 point range. That's not hard and can be implemented with interpolation.

You don't necessarily have to expand the y range to fit the screen in the y direction. You could always show a stripe of 1000 x 400 squares on the screen rather than unnaturally y-elongated rectangles.


What about the x axis? Given that maybe 100 data points will map to the same x pixel, what is the best choice? It would be confusing to the user if, as the plot was zoomed in, peaks suddenly started appearing where there were no peaks at lower zoom level.

Yap! Are your spikes always in the positive direction? If yes, I think it's best if you select the maximum z-value that has been mapped to the same pixel and show that. This way no spikes will be hidden. However, it is up to your application how interesting these spikes are. Say if you map 1000 values to the same pixel, 999 are zeros and one is a spike, would you want to see the spike or not? This is a choice you have to make. If yes, select the maximum. If not, average the z values or use a soft max function maybe. If your spikes occur in the positive AND the negative direction, selecting just the most extreme z-value in one pixel may eradicate a second spike in the opposite direction. In this case, I would declare the smallest unit to be 2 pixels, one of them showing the most extreme positive value and the other the most extreme negative value in the bin.



Zooming has to be fast. Resampling a section of a 400 x 100K point matrix into a 1000 x 1000 pixel image could take forever if every pixel has to be recomputed each time. It is sparse data, so I really don't want to have to explode it into a 400 x 100K matrix where most of it is zero.

Do you have any meta information about the sparse structure, for example an index of the starting points of the non-zero blocks and the zero blocks for each line? Yes, precomputing such a data structure, and then using it for a (more complicated) resampling algorithm could save you tremendous amounts of computation time. Obviously, your most critical use case is when the entire matrix is on the screen, and all 100K pixels need to be mapped to 1000. When the user zooms in, a smaller amount of values will be mapped to one pixel, and not the entire matrix will be seen on the screen, which means you will need a pan operation. You can accelerate the sampling in this case (in combination with exploiting the sparse structure) by computing the bounding box of the actually visible portion of the matrix, and mapping only those cells to the visible resolution.

However, if you end up not needing a special operation for the visualization of the spike at low zoom levels, you could just render the whole matrix once into a QImage or QPixmap, and then just use screen transformations (QTransform) to implement your panning and scaling. This would be easy and very fast.



Finally, what is the best way to implement this for display? Using a QImage? Or implementing it as a texture in OpenGL?

I have once faced a similar task where I had to render a computationally demanding image pixel by pixel and made good experiences with rendering offline into a QImage and then showing that on the screen, or even drawing additional things on top of it. This ended up being fast enough for my application. I have no clue about using OpenGL textures and shaders, so hopefully someone else can throw in another penny concerning that approach.

Cheers
Cruz

d_stranz
1st September 2016, 17:34
@Cruz - thanks for the feedback.


Are your spikes always in the positive direction?

All data points are positive, so no negative / positive concerns. I'm leaning towards showing the maximum value.


Do you have any meta information about the sparse structure

Not really. The data are an array of paired arrays of sparse points. That is, there is a master y-array containing the y-axis coordinates. For each y-axis value, there are x and z arrays. The z-array contains only the non-zero intensity values, plus a pair of zeros on each end of a block to serve as a delimiter. The x-array contains the positions of each of the z-array points.

So there is potentially sparseness in both x- and y-dimensions. If there are no non-zero intensity points along the x-axis for a given y coordinate, then that coordinate (and corresponding x- and z-arrays) is omitted.


precomputing such a data structure

Both x- and y-arrays are sorted in increasing coordinate order, so binary search might be as effective. If I do precompute something, I am thinking about a quadtree-like approach that stores the maximum z-value at each level of detail and stops when all of the pixels within a given region are equal-valued. Have to think more on that.


rendering offline into a QImage

My current plan as well. I was reading up on OpenGL textures yesterday, and there's more to learn than I care to learn at this point.