Support horizontal HistogramLUT{Item,Widget} (#1757)
* Improve HistogramLUTItem docs, a few cosmetic changes * Initial implementation of horizontally-oriented HistogramLUTItem - Also adds support in HistogramLUTWidget - Fixes AxisItem orientation bug for vertical orientation - Make use of GradientEditorItem orientation (fixes another bug for vertical orientation) - Use horizontal orientation in an example for demonstration * Remove unused HistogramLUTItem option * A few more minor fixups * Copy paste bug * Use f-strings * Update from review and a couple more minor updates * Woops * Add doc for orientation arg * Add top/bottom orientation to doc. Expand on levelMode doc a bit
This commit is contained in:
parent
7dd033c03b
commit
cafe079910
@ -52,10 +52,11 @@ win.setWindowTitle('pyqtgraph example: Non-uniform Image')
|
|||||||
|
|
||||||
p = cw.addPlot(title="Power Losses [W]", row=0, col=0)
|
p = cw.addPlot(title="Power Losses [W]", row=0, col=0)
|
||||||
|
|
||||||
lut = pg.HistogramLUTItem()
|
lut = pg.HistogramLUTItem(orientation="horizontal")
|
||||||
|
|
||||||
p.setMouseEnabled(x=False, y=False)
|
p.setMouseEnabled(x=False, y=False)
|
||||||
|
|
||||||
|
cw.nextRow()
|
||||||
cw.addItem(lut)
|
cw.addItem(lut)
|
||||||
|
|
||||||
# load the gradient
|
# load the gradient
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
GraphicsWidget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images.
|
GraphicsWidget displaying an image histogram along with gradient editor. Can be used to
|
||||||
|
adjust the appearance of images.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -25,65 +26,115 @@ __all__ = ['HistogramLUTItem']
|
|||||||
|
|
||||||
class HistogramLUTItem(GraphicsWidget):
|
class HistogramLUTItem(GraphicsWidget):
|
||||||
"""
|
"""
|
||||||
This is a graphicsWidget which provides controls for adjusting the display of an image.
|
:class:`~pyqtgraph.GraphicsWidget` with controls for adjusting the display of an
|
||||||
|
:class:`~pyqtgraph.ImageItem`.
|
||||||
|
|
||||||
Includes:
|
Includes:
|
||||||
|
|
||||||
- Image histogram
|
- Image histogram
|
||||||
- Movable region over histogram to select black/white levels
|
- Movable region over the histogram to select black/white levels
|
||||||
- Gradient editor to define color lookup table for single-channel images
|
- Gradient editor to define color lookup table for single-channel images
|
||||||
|
|
||||||
================ ===========================================================
|
Parameters
|
||||||
image (:class:`~pyqtgraph.ImageItem` or ``None``) If *image* is
|
----------
|
||||||
provided, then the control will be automatically linked to
|
image : pyqtgraph.ImageItem, optional
|
||||||
the image and changes to the control will be immediately
|
If provided, control will be automatically linked to the image and changes to
|
||||||
reflected in the image's appearance.
|
the control will be reflected in the image's appearance. This may also be set
|
||||||
fillHistogram (bool) By default, the histogram is rendered with a fill.
|
via :meth:`setImageItem`.
|
||||||
For performance, set ``fillHistogram=False``
|
fillHistogram : bool, optional
|
||||||
rgbHistogram (bool) Sets whether the histogram is computed once over all
|
By default, the histogram is rendered with a fill. Performance may be improved
|
||||||
channels of the image, or once per channel.
|
by disabling the fill. Additional control over the fill is provided by
|
||||||
levelMode 'mono' or 'rgba'. If 'mono', then only a single set of
|
:meth:`fillHistogram`.
|
||||||
black/white level lines is drawn, and the levels apply to
|
levelMode : str, optional
|
||||||
all channels in the image. If 'rgba', then one set of
|
'mono' (default)
|
||||||
levels is drawn for each channel.
|
One histogram with a :class:`~pyqtgraph.LinearRegionItem` is displayed to
|
||||||
gradientPosition 'right' (default) OR 'left'. Which side of the histogram to
|
control the black/white levels of the image. This option may be used for
|
||||||
put the LUT gradient.
|
color images, in which case the histogram and levels correspond to all
|
||||||
================ ===========================================================
|
channels of the image.
|
||||||
|
'rgba'
|
||||||
|
A histogram and level control pair is provided for each image channel. The
|
||||||
|
alpha channel histogram and level control are only shown if the image
|
||||||
|
contains an alpha channel.
|
||||||
|
gradientPosition : str, optional
|
||||||
|
Position of the gradient editor relative to the histogram. Must be one of
|
||||||
|
{'right', 'left', 'top', 'bottom'}. 'right' and 'left' options should be used
|
||||||
|
with a 'vertical' orientation; 'top' and 'bottom' options are for 'horizontal'
|
||||||
|
orientation.
|
||||||
|
orientation : str, optional
|
||||||
|
The orientation of the axis along which the histogram is displayed. Either
|
||||||
|
'vertical' (default) or 'horizontal'.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
sigLookupTableChanged : signal
|
||||||
|
Emits the HistogramLUTItem itself when the gradient changes
|
||||||
|
sigLevelsChanged : signal
|
||||||
|
Emits the HistogramLUTItem itself while the movable region is changing
|
||||||
|
sigLevelChangeFinished : signal
|
||||||
|
Emits the HistogramLUTItem itself when the movable region is finished changing
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
:class:`~pyqtgraph.ImageItem`
|
||||||
|
HistogramLUTItem is most useful when paired with an ImageItem.
|
||||||
|
:class:`~pyqtgraph.ImageView`
|
||||||
|
Widget containing a paired ImageItem and HistogramLUTItem.
|
||||||
|
:class:`~pyqtgraph.HistogramLUTWidget`
|
||||||
|
QWidget containing a HistogramLUTItem for widget-based layouts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sigLookupTableChanged = QtCore.Signal(object)
|
sigLookupTableChanged = QtCore.Signal(object)
|
||||||
sigLevelsChanged = QtCore.Signal(object)
|
sigLevelsChanged = QtCore.Signal(object)
|
||||||
sigLevelChangeFinished = QtCore.Signal(object)
|
sigLevelChangeFinished = QtCore.Signal(object)
|
||||||
|
|
||||||
def __init__(self, image=None, fillHistogram=True, rgbHistogram=False, levelMode='mono', gradientPosition='right'):
|
def __init__(self, image=None, fillHistogram=True, levelMode='mono',
|
||||||
|
gradientPosition='right', orientation='vertical'):
|
||||||
GraphicsWidget.__init__(self)
|
GraphicsWidget.__init__(self)
|
||||||
self.lut = None
|
self.lut = None
|
||||||
self.imageItem = lambda: None # fake a dead weakref
|
self.imageItem = lambda: None # fake a dead weakref
|
||||||
self.levelMode = levelMode
|
self.levelMode = levelMode
|
||||||
self.rgbHistogram = rgbHistogram
|
self.orientation = orientation
|
||||||
self.gradientPosition = gradientPosition
|
self.gradientPosition = gradientPosition
|
||||||
|
|
||||||
|
if orientation == 'vertical' and gradientPosition not in {'right', 'left'}:
|
||||||
|
self.gradientPosition = 'right'
|
||||||
|
elif orientation == 'horizontal' and gradientPosition not in {'top', 'bottom'}:
|
||||||
|
self.gradientPosition = 'bottom'
|
||||||
|
|
||||||
self.layout = QtGui.QGraphicsGridLayout()
|
self.layout = QtGui.QGraphicsGridLayout()
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
self.layout.setContentsMargins(1,1,1,1)
|
self.layout.setContentsMargins(1, 1, 1, 1)
|
||||||
self.layout.setSpacing(0)
|
self.layout.setSpacing(0)
|
||||||
|
|
||||||
self.vb = ViewBox(parent=self)
|
self.vb = ViewBox(parent=self)
|
||||||
self.vb.setMaximumWidth(152)
|
if self.orientation == 'vertical':
|
||||||
self.vb.setMinimumWidth(45)
|
self.vb.setMaximumWidth(152)
|
||||||
self.vb.setMouseEnabled(x=False, y=True)
|
self.vb.setMinimumWidth(45)
|
||||||
self.gradient = GradientEditorItem()
|
self.vb.setMouseEnabled(x=False, y=True)
|
||||||
self.gradient.setOrientation(gradientPosition)
|
else:
|
||||||
|
self.vb.setMaximumHeight(152)
|
||||||
|
self.vb.setMinimumHeight(45)
|
||||||
|
self.vb.setMouseEnabled(x=True, y=False)
|
||||||
|
|
||||||
|
self.gradient = GradientEditorItem(orientation=self.gradientPosition)
|
||||||
self.gradient.loadPreset('grey')
|
self.gradient.loadPreset('grey')
|
||||||
|
|
||||||
|
# LinearRegionItem orientation refers to the bounding lines
|
||||||
|
regionOrientation = 'horizontal' if self.orientation == 'vertical' else 'vertical'
|
||||||
self.regions = [
|
self.regions = [
|
||||||
LinearRegionItem([0, 1], 'horizontal', swapMode='block'),
|
# single region for mono levelMode
|
||||||
LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='r',
|
LinearRegionItem([0, 1], regionOrientation, swapMode='block'),
|
||||||
|
# r/g/b/a regions for rgba levelMode
|
||||||
|
LinearRegionItem([0, 1], regionOrientation, swapMode='block', pen='r',
|
||||||
brush=fn.mkBrush((255, 50, 50, 50)), span=(0., 1/3.)),
|
brush=fn.mkBrush((255, 50, 50, 50)), span=(0., 1/3.)),
|
||||||
LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='g',
|
LinearRegionItem([0, 1], regionOrientation, swapMode='block', pen='g',
|
||||||
brush=fn.mkBrush((50, 255, 50, 50)), span=(1/3., 2/3.)),
|
brush=fn.mkBrush((50, 255, 50, 50)), span=(1/3., 2/3.)),
|
||||||
LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='b',
|
LinearRegionItem([0, 1], regionOrientation, swapMode='block', pen='b',
|
||||||
brush=fn.mkBrush((50, 50, 255, 80)), span=(2/3., 1.)),
|
brush=fn.mkBrush((50, 50, 255, 80)), span=(2/3., 1.)),
|
||||||
LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='w',
|
LinearRegionItem([0, 1], regionOrientation, swapMode='block', pen='w',
|
||||||
brush=fn.mkBrush((255, 255, 255, 50)), span=(2/3., 1.))]
|
brush=fn.mkBrush((255, 255, 255, 50)), span=(2/3., 1.))
|
||||||
|
]
|
||||||
|
self.region = self.regions[0] # for backward compatibility.
|
||||||
for region in self.regions:
|
for region in self.regions:
|
||||||
region.setZValue(1000)
|
region.setZValue(1000)
|
||||||
self.vb.addItem(region)
|
self.vb.addItem(region)
|
||||||
@ -91,111 +142,160 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
region.lines[1].addMarker('|>', 0.5)
|
region.lines[1].addMarker('|>', 0.5)
|
||||||
region.sigRegionChanged.connect(self.regionChanging)
|
region.sigRegionChanged.connect(self.regionChanging)
|
||||||
region.sigRegionChangeFinished.connect(self.regionChanged)
|
region.sigRegionChangeFinished.connect(self.regionChanged)
|
||||||
|
|
||||||
self.region = self.regions[0] # for backward compatibility.
|
# gradient position to axis orientation
|
||||||
|
ax = {'left': 'right', 'right': 'left',
|
||||||
self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self)
|
'top': 'bottom', 'bottom': 'top'}[self.gradientPosition]
|
||||||
self.layout.addItem(self.axis, 0, 0)
|
self.axis = AxisItem(ax, linkView=self.vb, maxTickLength=-10, parent=self)
|
||||||
self.layout.addItem(self.vb, 0, 1)
|
|
||||||
pos = (0, 2) if gradientPosition == 'right' else (2, 0)
|
# axis / viewbox / gradient order in the grid
|
||||||
self.layout.addItem(self.axis, 0, pos[0])
|
avg = (0, 1, 2) if self.gradientPosition in {'right', 'bottom'} else (2, 1, 0)
|
||||||
self.layout.addItem(self.gradient, 0, pos[1])
|
if self.orientation == 'vertical':
|
||||||
self.range = None
|
self.layout.addItem(self.axis, 0, avg[0])
|
||||||
|
self.layout.addItem(self.vb, 0, avg[1])
|
||||||
|
self.layout.addItem(self.gradient, 0, avg[2])
|
||||||
|
else:
|
||||||
|
self.layout.addItem(self.axis, avg[0], 0)
|
||||||
|
self.layout.addItem(self.vb, avg[1], 0)
|
||||||
|
self.layout.addItem(self.gradient, avg[2], 0)
|
||||||
|
|
||||||
self.gradient.setFlag(self.gradient.ItemStacksBehindParent)
|
self.gradient.setFlag(self.gradient.ItemStacksBehindParent)
|
||||||
self.vb.setFlag(self.gradient.ItemStacksBehindParent)
|
self.vb.setFlag(self.gradient.ItemStacksBehindParent)
|
||||||
|
|
||||||
self.gradient.sigGradientChanged.connect(self.gradientChanged)
|
self.gradient.sigGradientChanged.connect(self.gradientChanged)
|
||||||
self.vb.sigRangeChanged.connect(self.viewRangeChanged)
|
self.vb.sigRangeChanged.connect(self.viewRangeChanged)
|
||||||
add = QtGui.QPainter.CompositionMode_Plus
|
|
||||||
|
comp = QtGui.QPainter.CompositionMode_Plus
|
||||||
self.plots = [
|
self.plots = [
|
||||||
PlotCurveItem(pen=(200, 200, 200, 100)), # mono
|
PlotCurveItem(pen=(200, 200, 200, 100)), # mono
|
||||||
PlotCurveItem(pen=(255, 0, 0, 100), compositionMode=add), # r
|
PlotCurveItem(pen=(255, 0, 0, 100), compositionMode=comp), # r
|
||||||
PlotCurveItem(pen=(0, 255, 0, 100), compositionMode=add), # g
|
PlotCurveItem(pen=(0, 255, 0, 100), compositionMode=comp), # g
|
||||||
PlotCurveItem(pen=(0, 0, 255, 100), compositionMode=add), # b
|
PlotCurveItem(pen=(0, 0, 255, 100), compositionMode=comp), # b
|
||||||
PlotCurveItem(pen=(200, 200, 200, 100), compositionMode=add), # a
|
PlotCurveItem(pen=(200, 200, 200, 100), compositionMode=comp), # a
|
||||||
]
|
]
|
||||||
|
|
||||||
self.plot = self.plots[0] # for backward compatibility.
|
self.plot = self.plots[0] # for backward compatibility.
|
||||||
for plot in self.plots:
|
for plot in self.plots:
|
||||||
plot.setRotation(90)
|
if self.orientation == 'vertical':
|
||||||
|
plot.setRotation(90)
|
||||||
self.vb.addItem(plot)
|
self.vb.addItem(plot)
|
||||||
|
|
||||||
self.fillHistogram(fillHistogram)
|
self.fillHistogram(fillHistogram)
|
||||||
self._showRegions()
|
self._showRegions()
|
||||||
|
|
||||||
self.vb.addItem(self.plot)
|
|
||||||
self.autoHistogramRange()
|
self.autoHistogramRange()
|
||||||
|
|
||||||
if image is not None:
|
if image is not None:
|
||||||
self.setImageItem(image)
|
self.setImageItem(image)
|
||||||
|
|
||||||
def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)):
|
def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)):
|
||||||
|
"""Control fill of the histogram curve(s).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
fill : bool, optional
|
||||||
|
Set whether or not the histogram should be filled.
|
||||||
|
level : float, optional
|
||||||
|
Set the fill level. See :meth:`PlotCurveItem.setFillLevel
|
||||||
|
<pyqtgraph.PlotCurveItem.setFillLevel>`. Only used if ``fill`` is True.
|
||||||
|
color : color, optional
|
||||||
|
Color to use for the fill when the histogram ``levelMode == "mono"``. See
|
||||||
|
:meth:`PlotCurveItem.setBrush <pyqtgraph.PlotCurveItem.setBrush>`.
|
||||||
|
"""
|
||||||
colors = [color, (255, 0, 0, 50), (0, 255, 0, 50), (0, 0, 255, 50), (255, 255, 255, 50)]
|
colors = [color, (255, 0, 0, 50), (0, 255, 0, 50), (0, 0, 255, 50), (255, 255, 255, 50)]
|
||||||
for i,plot in enumerate(self.plots):
|
for color, plot in zip(colors, self.plots):
|
||||||
if fill:
|
if fill:
|
||||||
plot.setFillLevel(level)
|
plot.setFillLevel(level)
|
||||||
plot.setBrush(colors[i])
|
plot.setBrush(color)
|
||||||
else:
|
else:
|
||||||
plot.setFillLevel(None)
|
plot.setFillLevel(None)
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
|
# paint the bounding edges of the region item and gradient item with lines
|
||||||
|
# connecting them
|
||||||
if self.levelMode != 'mono' or not self.region.isVisible():
|
if self.levelMode != 'mono' or not self.region.isVisible():
|
||||||
return
|
return
|
||||||
|
|
||||||
pen = self.region.lines[0].pen
|
pen = self.region.lines[0].pen
|
||||||
rgn = self.getLevels()
|
|
||||||
p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0]))
|
mn, mx = self.getLevels()
|
||||||
p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1]))
|
vbc = self.vb.viewRect().center()
|
||||||
gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect())
|
gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect())
|
||||||
|
if self.orientation == 'vertical':
|
||||||
|
p1mn = self.vb.mapFromViewToItem(self, Point(vbc.x(), mn)) + Point(0, 5)
|
||||||
|
p1mx = self.vb.mapFromViewToItem(self, Point(vbc.x(), mx)) - Point(0, 5)
|
||||||
|
if self.gradientPosition == 'right':
|
||||||
|
p2mn = gradRect.bottomLeft()
|
||||||
|
p2mx = gradRect.topLeft()
|
||||||
|
else:
|
||||||
|
p2mn = gradRect.bottomRight()
|
||||||
|
p2mx = gradRect.topRight()
|
||||||
|
else:
|
||||||
|
p1mn = self.vb.mapFromViewToItem(self, Point(mn, vbc.y())) - Point(5, 0)
|
||||||
|
p1mx = self.vb.mapFromViewToItem(self, Point(mx, vbc.y())) + Point(5, 0)
|
||||||
|
if self.gradientPosition == 'bottom':
|
||||||
|
p2mn = gradRect.topLeft()
|
||||||
|
p2mx = gradRect.topRight()
|
||||||
|
else:
|
||||||
|
p2mn = gradRect.bottomLeft()
|
||||||
|
p2mx = gradRect.bottomRight()
|
||||||
|
|
||||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||||
for pen in [fn.mkPen((0, 0, 0, 100), width=3), pen]:
|
for pen in [fn.mkPen((0, 0, 0, 100), width=3), pen]:
|
||||||
p.setPen(pen)
|
p.setPen(pen)
|
||||||
if self.gradientPosition == 'right':
|
|
||||||
p.drawLine(p1 + Point(0, 5), gradRect.bottomLeft())
|
# lines from the linear region item bounds to the gradient item bounds
|
||||||
p.drawLine(p2 - Point(0, 5), gradRect.topLeft())
|
p.drawLine(p1mn, p2mn)
|
||||||
|
p.drawLine(p1mx, p2mx)
|
||||||
|
|
||||||
|
# lines bounding the edges of the gradient item
|
||||||
|
if self.orientation == 'vertical':
|
||||||
|
p.drawLine(gradRect.topLeft(), gradRect.topRight())
|
||||||
|
p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight())
|
||||||
else:
|
else:
|
||||||
p.drawLine(p1 + Point(0, 5), gradRect.bottomRight())
|
p.drawLine(gradRect.topLeft(), gradRect.bottomLeft())
|
||||||
p.drawLine(p2 - Point(0, 5), gradRect.topRight())
|
p.drawLine(gradRect.topRight(), gradRect.bottomRight())
|
||||||
p.drawLine(gradRect.topLeft(), gradRect.topRight())
|
|
||||||
p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight())
|
|
||||||
|
|
||||||
def setHistogramRange(self, mn, mx, padding=0.1):
|
def setHistogramRange(self, mn, mx, padding=0.1):
|
||||||
"""Set the Y range on the histogram plot. This disables auto-scaling."""
|
"""Set the Y range on the histogram plot. This disables auto-scaling."""
|
||||||
self.vb.enableAutoRange(self.vb.YAxis, False)
|
if self.orientation == 'vertical':
|
||||||
self.vb.setYRange(mn, mx, padding)
|
self.vb.enableAutoRange(self.vb.YAxis, False)
|
||||||
|
self.vb.setYRange(mn, mx, padding)
|
||||||
|
else:
|
||||||
|
self.vb.enableAutoRange(self.vb.XAxis, False)
|
||||||
|
self.vb.setXRange(mn, mx, padding)
|
||||||
|
|
||||||
def autoHistogramRange(self):
|
def autoHistogramRange(self):
|
||||||
"""Enable auto-scaling on the histogram plot."""
|
"""Enable auto-scaling on the histogram plot."""
|
||||||
self.vb.enableAutoRange(self.vb.XYAxes)
|
self.vb.enableAutoRange(self.vb.XYAxes)
|
||||||
|
|
||||||
def setImageItem(self, img):
|
def setImageItem(self, img):
|
||||||
"""Set an ImageItem to have its levels and LUT automatically controlled
|
"""Set an ImageItem to have its levels and LUT automatically controlled by this
|
||||||
by this HistogramLUTItem.
|
HistogramLUTItem.
|
||||||
"""
|
"""
|
||||||
self.imageItem = weakref.ref(img)
|
self.imageItem = weakref.ref(img)
|
||||||
img.sigImageChanged.connect(self.imageChanged)
|
img.sigImageChanged.connect(self.imageChanged)
|
||||||
self._setImageLookupTable()
|
self._setImageLookupTable()
|
||||||
self.regionChanged()
|
self.regionChanged()
|
||||||
self.imageChanged(autoLevel=True)
|
self.imageChanged(autoLevel=True)
|
||||||
|
|
||||||
def viewRangeChanged(self):
|
def viewRangeChanged(self):
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def gradientChanged(self):
|
def gradientChanged(self):
|
||||||
if self.imageItem() is not None:
|
if self.imageItem() is not None:
|
||||||
self._setImageLookupTable()
|
self._setImageLookupTable()
|
||||||
|
|
||||||
self.lut = None
|
self.lut = None
|
||||||
self.sigLookupTableChanged.emit(self)
|
self.sigLookupTableChanged.emit(self)
|
||||||
|
|
||||||
def _setImageLookupTable(self):
|
def _setImageLookupTable(self):
|
||||||
if self.gradient.isLookupTrivial():
|
if self.gradient.isLookupTrivial():
|
||||||
self.imageItem().setLookupTable(None) #lambda x: x.astype(np.uint8))
|
self.imageItem().setLookupTable(None)
|
||||||
else:
|
else:
|
||||||
self.imageItem().setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
self.imageItem().setLookupTable(self.getLookupTable)
|
||||||
|
|
||||||
def getLookupTable(self, img=None, n=None, alpha=None):
|
def getLookupTable(self, img=None, n=None, alpha=None):
|
||||||
"""Return a lookup table from the color gradient defined by this
|
"""Return a lookup table from the color gradient defined by this
|
||||||
HistogramLUTItem.
|
HistogramLUTItem.
|
||||||
"""
|
"""
|
||||||
if self.levelMode != 'mono':
|
if self.levelMode != 'mono':
|
||||||
@ -223,7 +323,7 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
def imageChanged(self, autoLevel=False, autoRange=False):
|
def imageChanged(self, autoLevel=False, autoRange=False):
|
||||||
if self.imageItem() is None:
|
if self.imageItem() is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.levelMode == 'mono':
|
if self.levelMode == 'mono':
|
||||||
for plt in self.plots[1:]:
|
for plt in self.plots[1:]:
|
||||||
plt.setVisible(False)
|
plt.setVisible(False)
|
||||||
@ -264,10 +364,10 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
self.plots[i].setVisible(False)
|
self.plots[i].setVisible(False)
|
||||||
# make sure we are displaying the correct number of channels
|
# make sure we are displaying the correct number of channels
|
||||||
self._showRegions()
|
self._showRegions()
|
||||||
|
|
||||||
def getLevels(self):
|
def getLevels(self):
|
||||||
"""Return the min and max levels.
|
"""Return the min and max levels.
|
||||||
|
|
||||||
For rgba mode, this returns a list of the levels for each channel.
|
For rgba mode, this returns a list of the levels for each channel.
|
||||||
"""
|
"""
|
||||||
if self.levelMode == 'mono':
|
if self.levelMode == 'mono':
|
||||||
@ -277,37 +377,47 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
if nch is None:
|
if nch is None:
|
||||||
nch = 3
|
nch = 3
|
||||||
return [r.getRegion() for r in self.regions[1:nch+1]]
|
return [r.getRegion() for r in self.regions[1:nch+1]]
|
||||||
|
|
||||||
def setLevels(self, min=None, max=None, rgba=None):
|
def setLevels(self, min=None, max=None, rgba=None):
|
||||||
"""Set the min/max (bright and dark) levels.
|
"""Set the min/max (bright and dark) levels.
|
||||||
|
|
||||||
Arguments may be *min* and *max* for single-channel data, or
|
Parameters
|
||||||
*rgba* = [(rmin, rmax), ...] for multi-channel data.
|
----------
|
||||||
|
min : float, optional
|
||||||
|
Minimum level.
|
||||||
|
max : float, optional
|
||||||
|
Maximum level.
|
||||||
|
rgba : list, optional
|
||||||
|
Sequence of (min, max) pairs for each channel for 'rgba' mode.
|
||||||
"""
|
"""
|
||||||
|
if None in {min, max} and (rgba is None or None in rgba[0]):
|
||||||
|
raise ValueError("Must specify min and max levels")
|
||||||
|
|
||||||
if self.levelMode == 'mono':
|
if self.levelMode == 'mono':
|
||||||
if min is None:
|
if min is None:
|
||||||
min, max = rgba[0]
|
min, max = rgba[0]
|
||||||
assert None not in (min, max)
|
|
||||||
self.region.setRegion((min, max))
|
self.region.setRegion((min, max))
|
||||||
else:
|
else:
|
||||||
if rgba is None:
|
if rgba is None:
|
||||||
raise TypeError("Must specify rgba argument when levelMode != 'mono'.")
|
rgba = 4*[(min, max)]
|
||||||
for i, levels in enumerate(rgba):
|
for levels, region in zip(rgba, self.regions[1:]):
|
||||||
self.regions[i+1].setRegion(levels)
|
region.setRegion(levels)
|
||||||
|
|
||||||
def setLevelMode(self, mode):
|
def setLevelMode(self, mode):
|
||||||
""" Set the method of controlling the image levels offered to the user.
|
"""Set the method of controlling the image levels offered to the user.
|
||||||
|
|
||||||
Options are 'mono' or 'rgba'.
|
Options are 'mono' or 'rgba'.
|
||||||
"""
|
"""
|
||||||
assert mode in ('mono', 'rgba')
|
if mode not in {'mono', 'rgba'}:
|
||||||
|
raise ValueError(f"Level mode must be one of {{'mono', 'rgba'}}, got {mode}")
|
||||||
|
|
||||||
if mode == self.levelMode:
|
if mode == self.levelMode:
|
||||||
return
|
return
|
||||||
|
|
||||||
oldLevels = self.getLevels()
|
oldLevels = self.getLevels()
|
||||||
self.levelMode = mode
|
self.levelMode = mode
|
||||||
self._showRegions()
|
self._showRegions()
|
||||||
|
|
||||||
# do our best to preserve old levels
|
# do our best to preserve old levels
|
||||||
if mode == 'mono':
|
if mode == 'mono':
|
||||||
levels = np.array(oldLevels).mean(axis=0)
|
levels = np.array(oldLevels).mean(axis=0)
|
||||||
@ -315,18 +425,18 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
else:
|
else:
|
||||||
levels = [oldLevels] * 4
|
levels = [oldLevels] * 4
|
||||||
self.setLevels(rgba=levels)
|
self.setLevels(rgba=levels)
|
||||||
|
|
||||||
# force this because calling self.setLevels might not set the imageItem
|
# force this because calling self.setLevels might not set the imageItem
|
||||||
# levels if there was no change to the region item
|
# levels if there was no change to the region item
|
||||||
self.imageItem().setLevels(self.getLevels())
|
self.imageItem().setLevels(self.getLevels())
|
||||||
|
|
||||||
self.imageChanged()
|
self.imageChanged()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def _showRegions(self):
|
def _showRegions(self):
|
||||||
for i in range(len(self.regions)):
|
for i in range(len(self.regions)):
|
||||||
self.regions[i].setVisible(False)
|
self.regions[i].setVisible(False)
|
||||||
|
|
||||||
if self.levelMode == 'rgba':
|
if self.levelMode == 'rgba':
|
||||||
imax = 4
|
imax = 4
|
||||||
if self.imageItem() is not None:
|
if self.imageItem() is not None:
|
||||||
@ -343,15 +453,15 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
self.regions[0].setVisible(True)
|
self.regions[0].setVisible(True)
|
||||||
self.gradient.show()
|
self.gradient.show()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown level mode %r" % self.levelMode)
|
raise ValueError(f"Unknown level mode {self.levelMode}")
|
||||||
|
|
||||||
def saveState(self):
|
def saveState(self):
|
||||||
return {
|
return {
|
||||||
'gradient': self.gradient.saveState(),
|
'gradient': self.gradient.saveState(),
|
||||||
'levels': self.getLevels(),
|
'levels': self.getLevels(),
|
||||||
'mode': self.levelMode,
|
'mode': self.levelMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
if 'mode' in state:
|
if 'mode' in state:
|
||||||
self.setLevelMode(state['mode'])
|
self.setLevelMode(state['mode'])
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Widget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images.
|
Widget displaying an image histogram along with gradient editor. Can be used to adjust
|
||||||
This is a wrapper around HistogramLUTItem
|
the appearance of images. This is a wrapper around HistogramLUTItem
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ..Qt import QtGui, QtCore
|
from ..Qt import QtGui, QtCore
|
||||||
@ -11,23 +12,30 @@ __all__ = ['HistogramLUTWidget']
|
|||||||
|
|
||||||
|
|
||||||
class HistogramLUTWidget(GraphicsView):
|
class HistogramLUTWidget(GraphicsView):
|
||||||
|
"""QWidget wrapper for :class:`~pyqtgraph.HistogramLUTItem`.
|
||||||
def __init__(self, parent=None, *args, **kargs):
|
|
||||||
|
All parameters are passed along in creating the HistogramLUTItem.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None, *args, **kargs):
|
||||||
background = kargs.pop('background', 'default')
|
background = kargs.pop('background', 'default')
|
||||||
GraphicsView.__init__(self, parent, useOpenGL=False, background=background)
|
GraphicsView.__init__(self, parent, useOpenGL=False, background=background)
|
||||||
self.item = HistogramLUTItem(*args, **kargs)
|
self.item = HistogramLUTItem(*args, **kargs)
|
||||||
self.setCentralItem(self.item)
|
self.setCentralItem(self.item)
|
||||||
self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
|
|
||||||
self.setMinimumWidth(95)
|
self.orientation = kargs.get('orientation', 'vertical')
|
||||||
|
if self.orientation == 'vertical':
|
||||||
|
self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
|
||||||
|
self.setMinimumWidth(95)
|
||||||
|
else:
|
||||||
|
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
|
||||||
|
self.setMinimumHeight(95)
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
return QtCore.QSize(115, 200)
|
if self.orientation == 'vertical':
|
||||||
|
return QtCore.QSize(115, 200)
|
||||||
|
else:
|
||||||
|
return QtCore.QSize(200, 115)
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.item, attr)
|
return getattr(self.item, attr)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user