Skip to content

use QGraphicsPixmapItem instead of ImageItem to draw colorbar#2781

Merged
j9ac9k merged 1 commit intopyqtgraph:masterfrom
pijyoi:lightweight-colorbar
Jul 26, 2023
Merged

use QGraphicsPixmapItem instead of ImageItem to draw colorbar#2781
j9ac9k merged 1 commit intopyqtgraph:masterfrom
pijyoi:lightweight-colorbar

Conversation

@pijyoi
Copy link
Copy Markdown
Contributor

@pijyoi pijyoi commented Jul 24, 2023

Rather than using a heavy-weight ImageItem, a colorbar can be rendered by drawing a rectangle using a gradient brush.
(both GradientLegend and GLGradientLegendItem already do that)

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jul 24, 2023

Nice Find!

@NilsNemitz
Copy link
Copy Markdown
Contributor

Hi @pijyoi ,

I'll admit to laziness here, but there was a bit of a reason why that was handled by an image:

  • The image has the specific 256 levels that are provided by most of the color maps, so (anti-aliasing aside) each pixel should have a defined color rather than an interpolation. That might work better with categorical / stepped color maps, which would seem to blur otherwise.
  • I think the two extreme values at the start and end of the map only get half a "slot" in the gradient-colored rectangle: Let's imagine a 255.0 units wide bar. The left edge is coordinate 0.0 and gets color 0. This color 0 dominates until coordinate 0.5, where color 1 takes over until coordinate 1.5. So if that's the correct mental image, the first and last colors are only 0.5 units wide in the gradient rectangle. All other colors get 1.0 units. Irrelevant for a smooth gradient, but those are precisely the two colors where it might make sense to set them to special values as overflow/underflow indicators. The image gives them the same space, and hopefully reduces blurring effects some.

I think these points might be worth a quick consideration; and they can probably be addressed by tweaking the gradient settings a bit, rather than keeping the image. Offsetting the start and end point by half a unit, maybe? Maybe Qt does that anyway? I don't think stepped/categorical color maps commonly use a color bar... But if there's a "nearest neighbor" setting for the gradient, that might be interesting to expose.

Thank you for cleaning up the code :)

@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Jul 25, 2023

The following script demonstrates what @NilsNemitz refers to in his two points, i.e. blurring and the half "slot" syndrome at the edges. From what I understand, the first point boils down to whether a colormap is being treated as a gradient or as a look-up-table.

import itertools
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets

pg.mkQApp()
pw = pg.PlotWidget()
pw.show()
pi = pw.getPlotItem()

cycle = itertools.cycle([(255,0,0),(0,255,0),(0,0,255)])
colors = [next(cycle) for x in range(256)]
cmap = pg.ColorMap(None, colors)

data = np.ones(10)[:,np.newaxis] * np.arange(256)
img = pg.ImageItem(image=data, axisOrder='row-major')
pi.addItem(img)
pi.addColorBar(img, colorMap=cmap, orientation='horizontal', interactive=False)

rect = QtWidgets.QGraphicsRectItem(0,11,256,10)
rect.setPen(QtGui.QPen(QtCore.Qt.PenStyle.NoPen))
grad = cmap.getGradient()
grad.setCoordinateMode(QtGui.QGradient.CoordinateMode.ObjectMode)
rect.setBrush(grad)
pi.addItem(rect)

pg.exec()

@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Jul 25, 2023

So if using a gradient does not accurately represent a discrete lookup-table, a lightweight replacement would be a greatly simplified version of ImageItem where the lut is directly used as the QImage without undergoing any more rescaling.

@pijyoi pijyoi marked this pull request as draft July 25, 2023 13:47
@NilsNemitz
Copy link
Copy Markdown
Contributor

I like that as a solution, if it works out, it will be a very neat!

But --if-- it turns out to be unpleasantly complex, let me say that I wouldn't be violently opposed to the gradient solution either:

  • While I am pretty sure there are users of the "image as a look-up table" approach, I am struggling to see the need for the color bar in that application. (Except for the colormap preview in the examples?)
  • If necessary, the half-unit effect could be addressed by a slight (parametrized?) stretch of the rectangle into regions beyond 0 to 1, with the gradient covering the inner (0 to 1) part of it. Setting the gradient to QGradient::PadSpread should then pad the <0 end with the minimum color and the >1 end with the maximum color.

@pijyoi pijyoi force-pushed the lightweight-colorbar branch from f2917d3 to ce8b09c Compare July 26, 2023 11:20
@pijyoi pijyoi changed the title use QGraphicsRectItem instead of Image to draw colorbar use QGraphicsPixmapItem instead of ImageItem to draw colorbar Jul 26, 2023
@pijyoi pijyoi marked this pull request as ready for review July 26, 2023 11:37
@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jul 26, 2023

Nice find on the PixmapItem @pijyoi

Thanks for the PR, LGTM, merging.

@j9ac9k j9ac9k merged commit 95b0a3f into pyqtgraph:master Jul 26, 2023
@pijyoi pijyoi deleted the lightweight-colorbar branch July 26, 2023 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants