Now the questions:
- Why is the direct access to GraphicsScene not available? This effectively removes the Control part from the MVC design of the QtGraphics framework? For example, in this way one has to keep references to all the items that are placed whereever on the widget to allow for modification.
- Why is the QtGraphicsItem not the base class, or anyhow present? I can't really follow the argument in the code (of GraphicsItem.py)
- Why are there widgets like HistogramLUTWidget, GradientWidget (the two I'm mostly toying with right now) or the GraphicsLayoutWidget that are only adapter-like placeholders for their respective (graphics)items?
Maybe others have struggled in similar ways like me. My own wish would be to have a toolkit, from which I could build custom data displays (like I'm trying right now) for numpy/scipy data. So to provide single elements (e.g. GraphicsItems), that are (almost) atomic; which then can be used to be placed freely in the corresponding containers. I can see that a lot of things are already there. But there is still some gap between the very nice and informative drawing from the documentation (e.g. http://www.pyqtgraph.org/documentation/_images/plottingClasses.png) and the actual implementation. The examples help, but without some more info on the hierarchy or maybe example(s) on how to construct a custom widget, things get a lot harder, than they should -- concerning how much effort and work already has been put into pyqtgraph.
On Tue, Jun 6, 2017 at 2:08 AM, <mymindis...@gmail.com> wrote:
[...]
This looks great! The only thing I would add is that GraphicsObject inherits from QGraphicsObject as well as GraphicsItem.
Now the questions:
- Why is the direct access to GraphicsScene not available? This effectively removes the Control part from the MVC design of the QtGraphics framework? For example, in this way one has to keep references to all the items that are placed whereever on the widget to allow for modification.Access to the GraphicsScene comes from QGraphicsView.scene().However, I have found it is much easier to keep your own references to the graphics items rather than try to fetch them from the scene.
- Why is the QtGraphicsItem not the base class, or anyhow present? I can't really follow the argument in the code (of GraphicsItem.py)So there are three basic Qt graphics item classes here--QGraphicsItem, QGraphicsObject, and QGraphicsWidget. The latter two are subclasses of QGraphicsItem, and these are really the only two that I use within pyqtgraph. I needed subclasses of these to provide some extra functionality (GraphicsObject and GraphicsWidget), and I also wanted to provide some extra functionality that was shared between the two, hence GraphicsItem.Everything makes sense up until the mystery you discovered--why does GraphicsItem not inherit from QGraphicsItem? The reason is that it is not possible for a Python class to inherit from more than one Qt class. So if GraphicsItem were subclassed from QGraphicsItem, then it would not be possible for GraphicsObject to inherit from both GraphicsItem and QGraphicsObject. It's a mess, but perhaps a necessary consequence of the imperfect translation from Qt's C++ classes to Python classes.
Again, maybe it's just me -- I can't start to write a custom from that documentation. I started to draw UML diagrams (as the one before) and it starts to make more sense to me.
One particular nasty bit is that I haven't found a good way to remove the triangular tick marks in the LUT/Gradient items. The best way I've come up with is to set the size of the tick marks to zero -- by accessing the internal variable, which feels kind of bad. It also introduces some errors when mouse-clicking wildly in the area of the display, where the tick marks should be.
In the end I want to add further things to the above display:
a) fixing a crosshair-like object (using the mouse) and getting the data slice as side histograms in the correct size -- I don't know yet how to draw the histograms in the right size
b) pick a ROI, which is then displayed in a separate, detached QDialog
Ok, I think I have a sense of what is missing. Generally, it is possible and easy to take any of pyqtgraph's components and recombine them in novel ways. What you are trying to do, though, is reach inside the individual components (such as PlotItem) to change the way they look/behave. There is definitely much less support for this kind of use, and it's one area where I would like to see pyqtgraph grow. Some parts can indeed be customized--for example, a lot of the functionality you listed above is provided by ImageView, but you would need to swap out the internal ViewBox if you wanted axes (see: https://groups.google.com/forum/#!topic/pyqtgraph/2KSFXY30WEk). If you found that customizing past that point was too difficult, then it may be easier to build your own from simpler components like ViewBox, AxisItem, ImageItem, etc.I'll think on this some more. Whereas it's relatively easy to predict the ways people will use the existing pyqtgraph components (and write documentation/examples based on those), it's harder to predict all the ways people might want to customize outside of those existing components.
[...]
In the end I want to add further things to the above display:
a) fixing a crosshair-like object (using the mouse) and getting the data slice as side histograms in the correct size -- I don't know yet how to draw the histograms in the right sizeSee "examples/crosshair.py" and https://github.com/pyqtgraph/pyqtgraph/blob/develop/pyqtgraph/graphicsItems/ROI.py#L2233 for crosshair / mouse interaction ideas.See ViewBox.setXLink() and .setYLink() to get the histogram to line up with the image.
b) pick a ROI, which is then displayed in a separate, detached QDialog"examples/imageAnalysis.py" and "examples/ROIExamples.py" have some relevant material.
I'll think on this some more. Whereas it's relatively easy to predict the ways people will use the existing pyqtgraph components (and write documentation/examples based on those), it's harder to predict all the ways people might want to customize outside of those existing components.
This is the way I'm going now. I think that I have the basic layout now covered, and I'm trying to do this "custom building" from bottom up. The biggest nuisance in this is the lack of code/API documentation that includes all inherited functionality. I'm pretty sure there must be something like this for Python (I'm more a native C/C++).
BTW, I've started to write some "internal" documentation about the/my way into pyqtgraph. Still very rough and incomplete, but you might want to have a look. I'm not the only one in our group that is using pyqtgraph, and it seems that I'm set up to become the "go-to - expert".
I'm not sure though, if it might be helpful to someone outside me or my group.
BTW, I've started to write some "internal" documentation about the/my way into pyqtgraph. Still very rough and incomplete, but you might want to have a look. I'm not the only one in our group that is using pyqtgraph, and it seems that I'm set up to become the "go-to - expert".
I'm not sure though, if it might be helpful to someone outside me or my group.I'd love to see it (in any state), and I'm sure others would as well. If we can clean it up then it might make a nice addition to the documentation or an independent article of its own.Luke
"""
Extension to GraphicsWidget displaying a gradient editor, afaiu
"""
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph.functions as fn
from pyqtgraph import GraphicsWidget
from pyqtgraph.graphicsItems.ViewBox import *
from pyqtgraph.graphicsItems.GradientEditorItem import *
from pyqtgraph.graphicsItems.AxisItem import *
from pyqtgraph.graphicsItems.GridItem import *
import pyqtgraph.functions as fn
import numpy as np
pg.graphicsItems.GradientEditorItem.Gradients['inverted'] = {'ticks': [(0.0, (255, 255, 255, 255)), (1.0, (0, 0, 0, 255)),], 'mode': 'rgb'}
pg.graphicsItems.GradientEditorItem.Gradients['highContrast'] = {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 0, 255)),], 'mode': 'rgb'}
class GradientItem(GraphicsWidget):
"""
This is a blatant copy/rewrite of the HistogramLUTItem.
Instead of a histogram and stuff it only provides a
Gradient editor to define color lookup table for single-channel images.
In addition, it can set different predefined gradients by function.
"""
sigLookupTableChanged = QtCore.Signal(object)
sigLevelsChanged = QtCore.Signal(object)
sigLevelChangeFinished = QtCore.Signal(object)
def __init__(self, image=None, fillHistogram=True):
"""
If *image* (ImageItem) is provided, then the control will be automatically linked to the image and changes to the control will be immediately reflected in the image's appearance.
"""
GraphicsWidget.__init__(self)
self.lut = None
self.imageItem = None
self.layout = QtGui.QGraphicsGridLayout()
self.setLayout(self.layout)
self.layout.setContentsMargins(1,1,1,1)
self.layout.setSpacing(0)
self.vb = ViewBox()
self.vb.setMaximumWidth(152)
self.vb.setMinimumWidth(45)
self.vb.setMouseEnabled(x=False, y=False)
self.gradient = GradientEditorItem()
self.gradient.tickSize = 0 # CR: this is sooooo bad, but there is no function !?
self.gradient.setOrientation('right')
self.gradient.loadPreset('highContrast')
self.layout.addItem(self.gradient, 0, 0)
self.range = None
self.gradient.setFlag(self.gradient.ItemStacksBehindParent)
self.vb.setFlag(self.gradient.ItemStacksBehindParent)
self.gradient.sigGradientChanged.connect(self.gradientChanged)
if image is not None:
self.setImageItem(image)
def paint(self, p, *args):
pass
def setImageItem(self, img):
self.imageItem = img
img.setLookupTable(self.getLookupTable) ## send function pointer, not the result
def gradientChanged(self):
if self.imageItem is not None:
if self.gradient.isLookupTrivial():
self.imageItem.setLookupTable(None)
else:
self.imageItem.setLookupTable(self.getLookupTable) ## send function pointer, not the result
self.lut = None
self.sigLookupTableChanged.emit(self)
def getLookupTable(self, img=None, n=None, alpha=None):
if n is None:
if img.dtype == np.uint8:
n = 256
else:
n = 512
if self.lut is None:
self.lut = self.gradient.getLookupTable(n, alpha=alpha)
return self.lut
def setGradientByName(self, name="highContrast"):
if name in pg.graphicsItems.GradientEditorItem.Gradients:
self.gradient.loadPreset(name)