#pyqtgraph
1 messages · Page 2 of 1
so the numpy warnings are valid in warning that we are doing something dubious
btw, the ascii coord graphic for NonUniformImage isn't right. The code takes midpts of the prev and curr index
border cells are handled specially
(I couldn't sleep last night, woke up super early and decided to iron out some minor stuff, there's a reason I left that PR in draft mode 😛 )
I'm considering changing it.... it's been in the library for a bit, but not documented; so I'm feeling ok with the idea of changing the API for it
in fact, it could even be implemented in terms of the new BarGraphItem...
(which would have the added bonus of the bounding rect calculations sorted out)
the tricky issue is that NonUniformImageItem permits NaNs and Infs in the image data
and it even has test images to compare against...
Hi again guys. Does anyone know what happened to pyqtgraph/pyqtgraph#2323 ? It seems kind of abandoned (and unfortunately a bit outdated already), but the concept that is being implemented would be incredibly valuable for a visualization-library like pyqtgraph. Is there anything I can do to help revive that PR?
Tagging @torn coyote for #2323
my understanding of that PR, it presently takes too long to run/build and we hit timeout issues on the readthedocs CI
Oh, wow, I see. No way to increase that timeout limit? Does the generation have to run on readthedocs servers or could it be done in GitHub actions instead? I’ve never used readthedocs for CI, but if there is a way to retain files between runs the CI would be significantly faster for subsequent builds (as the gallery only runs on files with changes/a new hash)
I don't think it's a problem w/ the timeout explicitely, ... for some reason in CI, the screenshot generation is really really slow if I remember right
migrating away from RTD CI for documentation building is not in the cards; RTD works really well for us with very minimal configuration... worst case scenario we just store images in the docs and try to remember to update them periodically (Which is what I believe VisPy docs do, and their docs/examples look amazing
)
Yeah, that sounds like a possible alternative. Automatically compiled examples (and tutorials) would really push pyqtgraph to the next level in terms of user friendliness though.
i think we can get "live" tutorials in the web with jupyter-lite
https://jupyterlite.readthedocs.io/en/latest/
this is the holy grail, we have jupyter support working on binder; there are some exceptions (context menu dialogs and such)... I think there is some issue with capturing the entire window (like I think some of the right side are trimmed out) ... also not sure how responsive it would be
but a lot of the examples would need to be converted to their jupyter compatible equivalents, which I'm not crazy on the idea.
I'm hoping that once webassmebly/pyodine support rolls out for the Qt bindings, we can have better web integration
sphinx-plugin for easy docs integration: https://jupyterlite-sphinx.readthedocs.io/en/latest/
That does indeed look cool, but static example-pages (with images) would be an incredible leap in its own right.
Do we have any idea which part of the CI build is so slow? 7% in 1800 seconds seems horrible 😅
for that PR? if I remember right, the "slow" part was in the sphinx-gallery-qt plugin itself, the iteration through each example was just taking a long time
@torn coyote would likely know better than I would...
Hwat?
I just had a look at the VisPy repo, and it seems like they are doing exactly what @torn coyote has been setting up. I can really only see one big difference, and that is that they have their own image scraper. They are building their docs via github actions, but I guess the scraper is the main difference.
Their scraper does not look particularly fast though. For QtWidgets they are forcing 2*1.5 second timeouts for each screengrab. But maybe only a small part of their codebase uses QtWidgets?
Oh, their execution-time pages are online, so we can check how fast it runs.
Looks like their entire gallery takes roughly 3 min 24 s to compile https://vispy.org/gallery/scene/sg_execution_times.html (2 min 29 s)
https://vispy.org/gallery/gloo/sg_execution_times.html (0 min 21 s)
https://vispy.org/gallery/plotting/sg_execution_times.html (0 min 34 s)
would certainly love help on that PR ... if you can identify a change to make things better, I can cherry-pick a commit from your fork onto that branch
Is NonUniform image really slow or am I doing something wrong?
Even at 100 x 100 it slows down to a crawl
100x100 would likely be pretty brutal, that's drawing 10k rectangles, and we don't have drawRects implemented, so it's drawing one rectangle at a time. That GraphicsItem hasn't gotten much attention (primarily due to how undocumented it is 😆 ) so I wouldn't be surprised if it's not performing sufficiently well.
If we can group rectangles by color, and you have a lot of rectangles that use the same color, there would likely be substantial performance improvements available (but we would need to change the paint method of NonUniformImage to account for that)
pijyoi lobbying to kill NonUniformImage 😆 (note, I'm open to the idea of killing it)
@forest cliff I would give PColorMeshItem a shot and see how that works.
Fully fine with that one too
Will test it out later. Worst case I will fall back to matplotlib for that
btw thanks for all the good maintance work your guys did to pyqtgraph
Btw if a class has the name "image" in it and is only useful up to 50x50 pixel killing it may be the correct thing to do.👍
Added an example at https://github.com/pyqtgraph/pyqtgraph/pull/2599#issuecomment-1432318587 that draws a 512x512 pixel image using BarGraphItem.
👀 https://www.scipy2023.scipy.org/
Interested in resubmitting this year?
first question, when's the due date for submissions? 😆
second question concern, I'm down to submit, but due to timing, and a potential major geographic move for me, I can't lead an effort on this...
second question concern I m down to
I am using the hoverEvent to print out the cursor values of an ImageItem, following the example of imageAnalysis.py. The pos returned from the event seems to be off by half a pixel
To answer my own question, the pos is untransformed. To convert to integer indices, one has to do integer truncation of the pos (as was done in the example)
@obsidian sapphire has done a bunch of work/examination on pixel/mouse position offsets.
good job guys
hello,
has anyone managed to get a pyqtgraph 3D scatter plot to update based on the output of a for loop. I have made my own structure from motion system and I would like to plot the 3D points on a 3D pyqtgraph plot after each iteration of the for loop. Any help would be greatly appreciated
It might be more useful if you could tell us what issue you are encountering in your implementation.
all i wanted to let u guys know is u guys missed a beautiful opportunity to name this project qtpygraph which is like a shorthand for cutiepie, lol
Wise words of a gigachad
Fun fact, there's indeed a qtpy repo that is unrelated to this one but uses the shorthand you noticed.
damn seems pretty good abstraction layer over each of the GUI python frameworks based on qt, thanks
sorry for bringing this old topic, but I was really unaware of that plugin 😮 for PySide I ended up writing a Python script by myself that went through the examples, and rendered the main .rst file and displayed the code.
¿Do you remember what's the slow bit of that sphinx plugin?
A pyside maintainer has entered chat!
The plug-in was created by our very own @torn coyote
Do you happen to have the script handy/viewable?
I spent some time looking deeper into the PR that was discussed in the post you refer to, and I don’t think the execution time of the Sphinx extension is what caused the PR to go stale. My understanding is that there was some trouble getting OpenGL to work properly in the pipeline-build at readthedocs CI. I guess I forgot to report back here with that info.
Oooo that would make sense; also easy enough to test out.
Sure, it's here: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/tools/example_gallery/main.py
The idea is to build what you see in: https://doc.qt.io/qtforpython/examples/index.html
Thank you!!!!
I'm certain it could be better 😛 but at least does the job.
The script generate the .rst files that we later include in the docs.
I’m certain it could be better
If it’s stupid and it works, it’s not stupid.
I spent a few evenings trying to understand what may have caused the problems, but OpenGL drivers for headless Linux is unfortunately too far out of my area of expertise for me to be useful in helping fix the problem 😕
000000000000000000000000000000000000000030
We install the necessary drivers on GitHub actions to make it work, I think we install the same packages on the RTD CI, but would think it would be worthwhile to not even attempt the OpenGL variants and see if that works, and then debug from there.
Yeah, that sounds reasonable. I'll share some more details on the OpenGL issues regardless, as I'm by the computer I did the testing on. In my headless ubuntu setup (WSL) I've managed to replicate what I assume is the behavior described by @torn coyote in the PR. Most of the OpenGL examples actually work, the exceptions are (as far as I can tell, due to very obvious misrenderings):
- GLBarGraphItem
- GLGraphItem
- GLIsosurface
- GLScatterPlotItem
- GLSurfacePlot
In my setup It's a bit hard to tell which exact programs throw the GL errors because the sphinx-build output overwrites some of the lines as they are written, but at least some of these seem to correlate with the errors
how would I plot the time on the x axis ?
will it not show the time on the x axis tho?
At least for me, the plot shows years on the x-axis. And if I zoom in on the right hand side, it starts showing time
Hey everyone! I'm probably approaching the limits of Pyqtgraph in terms of speed in creating new plot items but I would like to hear from more expert people on the matter.
I have got a 2D Numpy array with shape 20x9000, my intention is to be able to allow the user to plot both 20 lines of 9000 points each or 9000 lines of 20 points each.
I am working with PySide 6.4 and I have promoted a QGraphicsView object to PlotWidget as suggested in the official documentation. I add lines to the plot in a for loop as such:
d = np.array(data) # Array that contains the data to be plotted
x = np.array(range(data.shape[1]) # Index for each line
for i in range(data.shape[0]):
plot_widget.plot(x, d[i,:])
The following code works but as I said the 9000x20 case takes a lot to be generated and even removing all the lines with plot_widget.clean() takes almost as much as creating the plot in the first place.
Is there any way workaround that does not require subsampling?
I did saw this Issue on the Github (https://github.com/pyqtgraph/pyqtgraph/issues/1039) but id didn't look conclusive on the matter unfortunately
For 9000x20, you would use the connect="finite" parameter of PlotCurveItem
The example there uses connect=ndarray instead of connect="finite"
connect=‘finite’ is most certainly the way to do it.
@fervent vale thanks for the PR on the segfault. I saw CI was failing but didn’t get to look more closely.
There's also something fishy about test_imageItem.py::test_dividebyzero()
"-5+25" was probably meant to be "-5e+25"
Ugh, yes that would make more sense!
I come home from vacation later today; but I will have driven forever so ill likely be useless. If I’m not too beat, ill look at merging PRs in hopefully 16 hours or so.
Thanks for the pointer! connect=finite returns the correct plot using the following code but end-points "loop around".
def updatePlot(self) -> None:
plot = self.graphicsView.plotItem
plot.enableAutoRange(False)
plot.clear()
d = self.data.to_numpy()
x = np.arange(d.shape[1])
shape = (d.shape[0], d.shape[1])
xdata = np.tile(x, shape[0])
ydata = d
item = pg.PlotCurveItem()
item.setData(xdata.reshape(-1), ydata.reshape(-1), connect="finite")
plot.addItem(item)
Using a connection matrix like the one in the example returns the correct result:
def updatePlot(self) -> None:
plot = self.graphicsView.plotItem
plot.enableAutoRange(False)
plot.clear()
d = self.data.to_numpy()
x = np.arange(d.shape[1])
shape = (d.shape[0], d.shape[1])
xdata = np.tile(x, shape[0])
ydata = d
conn = np.ones(shape, dtype=bool)
conn[...,-1] = False # make sure plots are disconnected
item = pg.PlotCurveItem()
item.setData(xdata.reshape(-1), ydata.reshape(-1), connect=conn.reshape(-1))
plot.addItem(item)
I guess that working with a single PlotCurveItem() it is not possible to have lines having different colors, am I correct?
I must admit I didn't expect to hit the limitations of the library this soon 🥲
I am in the midst of porting a Matlab toolbox to Python and while still slow the same plot is obtained with varying colours in a matter of seconds. What is the bottleneck here?
Does your data have nan entries to indicate where the lines split? connect="finite" creates a line break when encountering nan
No it does not. But the connection matrix method works fine without having to pad the original data with NaNs
Phil just emailed me that the sip.array fixes will be in the next snapshot
connect="finite" relies on nan being present (finite corresponds to np.isfinite checks), but passing a connection matrix explicitly indicates the breaks. That's why it works in the second case
import pyqtgraph as pg
import numpy as np
def generate_data(shape: tuple[int, int]) -> np.ndarray:
"""monotonically increasing data similar to your screenshot"""
data = np.linspace(0, 1, shape[1])[None, :] + np.abs(np.random.normal(size=shape))
return np.cumsum(data, axis=1)
def pad_with_nan(data: np.ndarray) -> np.ndarray:
"""add nan values to the end of each row of data to indicate split locations"""
return np.column_stack([data, np.full((data.shape[0], 1), np.nan)])
def update(curve):
data = generate_data((9000, 20))
data = pad_with_nan(data)
x = np.tile(np.arange(data.shape[1]), (data.shape[0], 1))
curve.setData(x.flatten(), data.flatten())
def main():
pg.mkQApp()
plot = pg.PlotWidget()
curve = pg.PlotCurveItem()
plot.addItem(curve)
plot.show()
timer = pg.QtCore.QTimer()
timer.timeout.connect(lambda: update(curve))
timer.start(1000)
pg.exec()
if __name__ == "__main__":
main()
Seems to update very quickly for me
Yes, I wasn't arguing on the speed of this method at all, sorry if it sounded like that!
What I was trying to confirm is the impossibility of having differently coloured lines using this faster method (compared to the for-loop with one line added for loop)
You are correct, you'll need several lines. However, it is also possible to abuse the ScatterPlotItem for this purpose 🙂 Note that you will have to adjust some of the logic for bounding rect calculation if you use this
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph import functions as fn
import numpy as np
def generate_data(shape: tuple[int, int]) -> np.ndarray:
"""monotonically increasing data similar to your screenshot"""
data = np.linspace(0, 1, shape[1])[None, :] + np.abs(np.random.normal(size=shape))
return np.cumsum(data, axis=1)
def pad_with_nan(data: np.ndarray) -> np.ndarray:
"""add nan values to the end of each row of data to indicate split locations"""
return np.column_stack([data, np.full((data.shape[0], 1), np.nan)])
def line_to_symbol(
x: np.ndarray, y: np.ndarray
) -> tuple[QtGui.QPainterPath, tuple[float, float]]:
position = x.min() + x.ptp() / 2, y.min() + y.ptp() / 2
x = x - position[0]
y = y - position[1]
return fn.arrayToQPath(x, y), position
def update(scatter: pg.ScatterPlotItem):
data = generate_data((9000, 20))
# data = pad_with_nan(data)
x = np.arange(data.shape[1])
cmap = pg.colormap.get("viridis")
symbols, positions = zip(*[line_to_symbol(x, y) for y in data])
positions = np.array(positions).T
colors = cmap.map(np.linspace(0, 1, data.shape[0]))
scatter.setData(
*positions,
symbol=symbols,
brush=None,
pen=colors,
size=1,
pxMode=False,
)
def main():
pg.mkQApp()
plot = pg.PlotWidget()
scatter = pg.ScatterPlotItem()
# Draw each curve as a separate color using a stepwise gradient
plot.addItem(scatter)
plot.show()
timer = QtCore.QTimer()
timer.timeout.connect(lambda: update(scatter))
timer.start(3000)
# timer.setSingleShot(True)
pg.exec()
if __name__ == "__main__":
main()
Damn, this is some sexy stuff 😃
Thank you very much for the effort and the nicely presented code!
when I move the graph from one screen to another with different scaling the graph doesnt seem to correct itself. any way to fix that?
I have tried moving plots between screens of dpi 1 and 2 before, and it does work. Could you share more details of your setup? OS, binding version, pyqtgraph version, code snippet. Does the issue occur with one of the bundled pyqtgraph examples?
this is on my laptop screen running at 1080 p scale at 100 %
when I move it to a 1440p screen with 200 % scaling this is what it does.
when I use the mouse to change the graphs scale it's still not starting at 0 which it should have.
You seem to be using BarGraphItem. Could you provide the full runnable source code?
Regarding adjusting the logic of bounding rect calculation, to avoid having the lines disappears when moving the middle of the graph out of the viewport, I resorted to manually settings self.bounds after setting the data and avoiding to reset them when viewTransformChanged() is called for the ScatterPlotItem object.
First part was quite easy, for the second I had to resort to subclass ScatterPlotItem altogether, I don't know if there's a better way for it.
Here is your example adapted to what I just said:
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui
from pyqtgraph import functions as fn
import numpy as np
def generate_data(shape: tuple[int, int]) -> np.ndarray:
"""monotonically increasing data similar to your screenshot"""
data = np.linspace(0, 1, shape[1])[None, :] + np.abs(np.random.normal(size=shape))
return np.cumsum(data, axis=1)
def line_to_symbol(
x: np.ndarray, y: np.ndarray
) -> tuple[QtGui.QPainterPath, tuple[float, float]]:
position = x.min() + x.ptp() / 2, y.min() + y.ptp() / 2
x = x - position[0]
y = y - position[1]
return fn.arrayToQPath(x, y), position
class FastPlot(pg.ScatterPlotItem):
def viewTransformChanged(self):
self.prepareGeometryChange()
pg.GraphicsObject.viewTransformChanged(self)
def update(scatter: FastPlot):
data = generate_data((9000, 20))
# data = pad_with_nan(data)
x = np.arange(data.shape[1])
cmap = pg.colormap.get("viridis")
symbols, positions = zip(*[line_to_symbol(x, y) for y in data])
positions = np.array(positions).T
colors = cmap.map(np.linspace(0, 1, data.shape[0]))
scatter.setData(
*positions,
symbol=symbols,
brush=None,
pen=colors,
size=1,
pxMode=False,
)
scatter.bounds = [(0, len(x)-1), (data.min(), data.max())]
def main():
pg.mkQApp()
plot = pg.PlotWidget()
scatter = FastPlot()
# Draw each curve as a separate color using a stepwise gradient
plot.addItem(scatter)
plot.show()
update(scatter)
bound_rect = scatter.boundingRect()
plot.setLimits(xMin=bound_rect.left(), xMax=bound_rect.right(), yMin=bound_rect.top(), yMax=bound_rect.bottom())
pg.exec()
if __name__ == "__main__":
main()
(I had to split in 2 messages because of message size restriction)
class FastPlot(pg.ScatterPlotItem):
def dataBounds(self, ax, frac=1.0, orthoRange=None):
if hasattr(self, '_realbounds'):
return self._realbounds[ax]
else:
return super().dataBounds(ax, frac, orthoRange)
def update(scatter: FastPlot):
# ... snip ...
scatter._realbounds = [(x.min(), x.max()), (data.min(), data.max())]
And remove the "plot.setLimits" in your main function
wow
There's this code fragment in ScatterPlotItem:
w, pw = max(itertools.chain([(self._maxSpotWidth, self._maxSpotPxWidth)], self._measureSpotSizes(**kwargs)))
It doesn't seem to be calculating what I think it wants to do because:
In [7]: max(itertools.chain([(1,5)], [(5,3), (6,2)]))
Out[7]: (6, 2)
i.e. the comparison is at a whole tuple level
I think the code fragment would have intended the result to be (6,5) ?
A more "elegant" solution to the ScatterPlotItem bounding box would have been to add two invisible points:
scatter.addPoints(
[x.min(), x.max()],
[data.min(), data.max()],
symbol=['s', 's'],
pen=pg.mkPen(None)
)
I'm not that versed with the scatter plot stuff, I'll have to take a look, but yeah if the intended result is (6, 5) then using max(itertools.chain()) is definitely not going to give the "correct" result.
Unless the logic was to find the largest spot and its associated pix width
The comment for _maxSpotPxWidth says "maximum size of the scale invariant portion of all spots"; so it's independent of _maxSpotWidth
I'm in DLL hell, and I hate everything about computers right now
(completely off topic for pyqtgraph, just venting after spending the better part of a day on this)
With depends.exe I managed to trace down the cause, libxml2
do i perceive correctly that that took 2 hours to track down?
Oh this issue has taken me a lot longer than 2 hours to track down. This was a day long project haha
JUST KIDDING issue was only fixed when I started python from a particular directory, which happened to be where I was testing from, starting python elsewhere didn't work still; and yes, that directory is in my %PATH% 🙃
ok, I don't want to hold up the new release until I can sort out #2418 any more... (same w/ Non-Uniform Image docs, I can sort that out later)
Any other PRs/issues that should be addressed before the next release?
Assuming I am awake after I put the kids to bed, I’ll start on the changelog for the next release
ugh, when the changelog is so long it's annoying to arrange, clearly too long went by before a release was done 😆
14 first time contributors between 0.13.1 and 0.13.2
The immediate next commit would be to bump the version to 0.13.3.dev0?
Yeah forgot to do that; will do that shortly (now done)
thanks for keeping me honest 😄
I haven't even contributed a PR to black in like two months, cut yourself some slack!
haha, there is clearly a historical pattern here occurring, I've noticed my OSS contribution rate slows down from mid November - February going to have to keep this in mind as we approach the end of the year and recognize that this is just something that happens.
@fervent vale good looking out, fixed now
seriously tho, my daughter napping at daycare is absolutely killing my evening time... she's old enough that she can leave her room... and since she naps she's wide awake until later in the evening than I am ... I know it's not going to be like this forever
I have a question. I want to monitor several sensors in a PyQt GUI, but I don't know what the best way to do this is.
Initially, I used QRunnable and read some articles that suggested it may not be the best approach for 24/24 7/7 365/365 monitoring.
Next, I created a QThread class to monitor each device. Do you have any suggestions for the best approach? Thanks
The general way would be for each QThread to emit a signal containing the acquired sensor data and the GUI thread would connect to it and then display it
There's a long thread in github pyqtgraph discussions #1745 that may give you some ideas
Hello, guys!
I'm working on a project to show data in 3d using pyqtgraph and opengl. And now I have a problem that I need to realise a mouse click on the object and get information about this object. I understood that I need to use a rayCasting method, and I got a QVector3D from the screen. But I don't understand what should I do next, how can I find an intersection with other objects? Do your library have a solution to find an object under click in 3d? I tried to use itemsAt, but it doesn't work for me, this method just returned to me all objects.
I also find this code:
# Example item selection code:
#region = (ev.pos().x()-5, ev.pos().y()-5, 10, 10)
#print(self.itemsAt(region))
## debugging code: draw the picking region
#glViewport(*self.getViewport())
#glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
#region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3])
#self.paintGL(region=region)
#self.swapBuffers()
It is a code in GLViewWidget.py. But we don't have a swapBuffers function in GLViewWidget. How can I change it to draw the picking region?
I would also +1 the QThread method for emitting a signal with new data to plot.
our 3d implementation likely is partially buggy; there is not a lot of opengl expertise among current maintainers, so I wouldn't be surprised if there was something wrong happening. At a glance, itemsAt should do what you are asking for it to happen, and if it's not working, I would consider that a bug.
From a read of the docs for QOpenGLWidget, to do painting (or any OpenGL calls) outside of a automatic framework call to paintGL(), first you need to call makeCurrent().
swapBuffers() is a method of QOpenGLContext, an instance of which can be obtained with self.context()
So it may be possible to update the commented out code from QGLWidget code to QOpenGLWidget code.
I found an old testing script for itemAt()
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import numpy as np
class MyGLV(gl.GLViewWidget):
def mouseReleaseEvent(self, ev):
lpos = ev.position() if hasattr(ev, 'position') else ev.localPos()
region = [lpos.x()-5, lpos.y()-5, 10, 10]
# itemsAt seems to take in device pixels
dpr = self.devicePixelRatio()
region = tuple([x * dpr for x in region])
for item in self.itemsAt(region):
print(item.objectName())
pg.mkQApp()
glv = MyGLV()
glv.setCameraParams(elevation=90, azimuth=-90, distance=50)
# X points right, Y points up
glv.show()
side = 8
names = ['red', 'green', 'blue']
for idx, name in enumerate(names):
box = np.zeros((side, side, 4), dtype=np.uint8)
box[..., [idx, 3]] = 255
img = gl.GLImageItem(box)
img.setObjectName(name)
img.translate(-side/2, -side/2, 0) # center the box
img.translate((idx-1)*side*2, (idx-1)*side*1, 0)
glv.addItem(img)
pg.exec()
It no longer works correctly since Qt 6.4 though
So it seems like Qt 6.4 has changed something about OpenGL. We even had a PR #2546 to fix OpenGL for Qt 6.4
Well, that’s annoying!
Yes, I found it too, but also we need to send a parameter to the swapBuffers function (QSurface). I tried to use:
ctx.swapBuffers(ctx.format())
ctx.swapBuffers(ctx)
ctx.swapBuffers()
But nothing works.
I have some working swapBuffers code in #2650
But it will require you to patch your pyqtgraph to add a GLViewWindow
!voice
Can’t talk in voice chat? Check out #voice-verification to get access. The criteria for verifying are specified there.
One key difference between QOpenGLWidget and QOpenGLWindow is that the former undergoes an extra composition step.
The latter has behaviour closer to QGLWidget which GLViewWidget was originally based off
So some of the "less-used" functionality doesn't work with QOpenGLWidget
I would hazard a guess that most users are fine with QOpenGLWidget
!
In #2650, there's a patch that refactors GLViewWidget into GLViewMixin, with the aim of allowing users to swap QOpenGLWindow for QOpenGLWidget. Would that be useful to merge into pyqtgraph (without merging the GLViewWindow bit). Those power users who really need GLViewWindow can write it directly in their own code.
I have a hard time assessing utility in that part of the library, but sounds like this has come up periodically so probably isn't a bad idea
Thanks but I read some articles and the authors said that creating a class that inherits from Qthread is not the best way. For them the best way is to create a Qobject and create a qthread in the UI and use move to thread.
https://github.com/pyqtgraph/pyqtgraph/discussions/2164 does what you describe
Yes
Most of these articles I’ve seen involved using C++, and due to the GIL in Python using QObject.moveToThread() is apparently much trickier. If you see a working implementation of please share!
i saw this example : https://gist.github.com/jazzycamel/8abd37bf2d60cce6e01d
Simple example of the correct way to use (Py)Qt(5) and QThread - threads1.py
#2164 has a more instructive example where setting the QObject parent correctly becomes important. I always use moveToThread rather than inherit from QThread. The latter is a Java pattern, I think.
the qthread functionality on pyside2 was quite broken until 5.14.x if memory serves... maybe my reluctance to using .moveToThread() was due to pyside bindings behaving unexpectedly ...in my (simple) use-cases I've done just fine via subclassing... next time I give it another go I'll try and do without subclassing again
wondering if there are good places in the library to introduce the use of QThreads; ...maybe the IsoCurveItem, I think that guy takes some extra processing steps
The default implementation of QThread::run() executes an event loop. I want to make use of that event loop, so I don't subclass and override run().
If you want to send messages to the thread, then you need the event loop running
pretty sure this guy was receiving messages via signals https://github.com/j9ac9k/barney/blob/68375aeac8299dd0a16c2f36cb52c98f7d683469/src/barney/controllers/AudiotagInterface.py#L26
src/barney/controllers/AudiotagInterface.py line 26
class AudiotagCommunicationThread(QThread):```
(been a while since I worked w/ this code-base, and this is a slimmed down variant of what I worked on internally at a previous company)
another way of me saying please don't judge me too harshly 😄
In order to receive queued connection signals, you need the event loop
If not, your slots are actually being executed by the caller thread
Because you override run() with an empty function, there's no event loop
Or put another way, if you connect queued connection to your slots, they won't get fired
ahh, I rarely use Queued connections, that's probably why I never noticed
But if your slots are being executed by the caller thread, then there was no need to use a QThread...
hmm... pretty sure they were not being executed by the caller thread in this case, as the sql write command could take a while to write in this case. Before I migrated to a QThread, that would result in the GUI hanging... but after migrating to a subclassed QThread the GUI was not impacted further
"It is important to remember that a QThread instance lives in the old thread that instantiated it, not in the new thread that calls run(). This means that all of QThread's queued slots and invoked methods will execute in the old thread. Thus, a developer who wishes to invoke slots in the new thread must use the worker-object approach; new slots should not be implemented directly into a subclassed QThread."
"...the thread will exit after the run function has returned. There will not be any event loop running in the thread unless you call exec()."
from PySide6 import QtCore
class MyThread(QtCore.QThread):
def __init__(self):
super().__init__()
tid = QtCore.QThread.currentThread()
print('init called by', tid)
def direct_slot(self, msg):
tid = QtCore.QThread.currentThread()
print('direct slot called by', tid, msg)
def queued_slot(self, msg):
tid = QtCore.QThread.currentThread()
print('queued slot called by', tid, msg)
def run(self):
pass
class Sender(QtCore.QObject):
signal = QtCore.Signal(object)
app = QtCore.QCoreApplication([])
tid = QtCore.QThread.currentThread()
print('main thread is', tid)
thr = MyThread()
print('child thread is', thr)
sig = Sender()
sig.signal.connect(thr.direct_slot, QtCore.Qt.ConnectionType.DirectConnection)
sig.signal.connect(thr.queued_slot, QtCore.Qt.ConnectionType.QueuedConnection)
thr.start()
sig.signal.emit('MSG1')
QtCore.QTimer.singleShot(100, lambda : sig.signal.emit("MSG2"))
QtCore.QTimer.singleShot(500, app.exit)
print('before exec')
app.exec()
print('after exec')
thr.wait()
both direct_slot and queued_slot are executed by the main thread's event loop
Hmm…. That app Barney definitely does the spectrogram calculation on the non-gui thread; curious what specifically makes it work.
In the example above, the slot is called by the caller thread, which may not necessarily be the gui thread
The callee thread above was useless
simple plotting no longer works with PySide6 6.5 snapshots probably because of https://code.qt.io/cgit/pyside/pyside-setup.git/commit/?id=e8095467f7d0332cc0987e7c541de9906e19fece (implementation of cooperative inheritance)
Anything making use of GraphicsWidgetAnchor will fail
Hmm, it seems that it is an implementation bug of pyqtgraph that is being exposed by this PySide6 6.5 change
Thanks i can try this way to monitor my sensor
@fervent vale nice catch on the mixin issue coming up in pyside 6.5
Doing a regex search, RHS inheritance still exists in the pyoptic and the DateAxisItem_QtDesigner examples. For the former, it's clear there would be double init. For the latter, it's hard to say what would be the right way, especially if it is supposed to be following C++ Qt syntax.
It also seems like LabelItem is needlessly inheriting from GraphicsWidgetAnchor
It’s amazing the number of issues you can find by just taking a closer look at just about any class in the library haha
interestingly, it was a commit exactly 10 years ago to the day (2013/03/26) that added GraphicsWidgetAnchor to LabelItem (w/o making use of any of the former's functionality)
The merge comments say "Made LabelItem a subclass of GraphicsWidgetAnchor", so it was definitely intentional
Wonder if it was needed in Qt4 or something
Hey @copper lily!
You either uploaded a .txt file or entered a message that was too long. Please use our paste bin instead.
https://paste.pythondiscord.com/abemefufam
I tried to code with your help and i have several problems. When i put three QDockWidget in my mainWindow it was impossible to start all the graphs. Do you have an idea why this behaviours occurs ? thanks @fervent vale
Is the use of QDockWidget important to the concept of using QThread?
Okay, so I played around with your script a bit
I don’t know, it is more convinient for the gui.
and it hangs when trying to start all 3 plots
def handleGraphPlot(self):
print (self.strain)
self.strain = [self.strain]
print (self.stress)
self.stress = [self.stress]
self._svs.plot(self.strain, self.stress, pen=pg.mkPen('r', width=2), clear=True)
'''self._lvd.plot(self.ch2, self.ch1, pen=pg.mkPen('r', width=2), clear=True)
self._lvt.plot(self.timeWindow, self.ch1, pen=pg.mkPen('r', width=2), clear=True)
self._dvt.plot(self.timeWindow, self.ch2, pen=pg.mkPen('r', width=2), clear=True)
self._svs.plot([self.ch4], [self.ch3], pen=pg.mkPen('r', width=2), clear=True)'''
self._svs.show()
self.log_data() self._svs.plot(self.strain, self.stress, pen=pg.mkPen('r', width=2), clear=True) this graph is visible autoscalling but not ploting pls someone help
Can you reformat your post using a code block? It makes it far easier to read.
Why clear=True?
pijyoi, what a weird crash on the widgetgroup PR you created... can't help but wonder if the cause of the segfault is something unrelated and the change you made is exacerbating the underlying issue; .... not sure how to determine what the underlying issue would be tho
@rough furnace we just released PySide 6.5.0 let me know if you found some issues once you have some time to play with it and pyqtgraph 🙂
oh oh oh I have something for you
@fervent vale was kind enough to do testing pre-release (congratulations on the release by the way)
we haven't pinpointed the cause, but there is a memory leak that is new with 6.5.0 that is not present in 6.4.3
there is no non-pyqtgraph specific example yet, but the pyqtgraph code here is fairly minimal
Cool, then we can take a look 😄
(and thanks for that @fervent vale 🎉 )
Thanks, we would have opened an issue in the bug tracker, but as we don't have a pyside only script going, I didn't want to post there ...
Congratulations on the release and I really appreciate you reaching out directly @twin spire
hehe I really didn't like gitter, so I was really happy when I found you here 🙂
yeah, I low key hate it
I'm surprised you're not on the (admittedly low-traffic) Qt discord server.
oh wait:
@twin spire I just triggered a CI run with pyside6 6.5, we're getting a segfault on this line here:
https://github.com/pyqtgraph/pyqtgraph/blob/master/.github/workflows/main.yml#L123
[14](https://github.com/pyqtgraph/pyqtgraph/actions/runs/4610114611/jobs/8148181442?pr=2596#step:10:15)
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
[15](https://github.com/pyqtgraph/pyqtgraph/actions/runs/4610114611/jobs/8148181442?pr=2596#step:10:16)
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
[16](https://github.com/pyqtgraph/pyqtgraph/actions/runs/4610114611/jobs/8148181442?pr=2596#step:10:17)
[17](https://github.com/pyqtgraph/pyqtgraph/actions/runs/4610114611/jobs/8148181442?pr=2596#step:10:18)
Available platform plugins are: xcb, eglfs, offscreen, vnc, wayland, minimalegl, linuxfb, vkkhrdisplay, minimal, wayland-egl.
[18](https://github.com/pyqtgraph/pyqtgraph/actions/runs/4610114611/jobs/8148181442?pr=2596#step:10:19)
[19](https://github.com/pyqtgraph/pyqtgraph/actions/runs/4610114611/jobs/8148181442?pr=2596#step:10:20)```
going to do a little more homework here first...
.github/workflows/main.yml line 123
xvfb-run --server-args="-screen 0, 1920x1200x24 -ac +extension GLX +render -noreset" python -m pyqtgraph.util.glinfo```
if things are failing at the xcb level, there might an issue with Qt, better to keep an eye on JIRA in case ther is a regression. Will remember to come back here in case I found something similar
common fixes historically there have been to install a package via apt. We also had someone open an issue with a PySide 6.5 failure they encountered, but I haven't investigated.
Over the next day or so I'll try and get a concise list ...goes without saying, we should do a little more homework first.
Also I should probably configure our CI to run against pre-release wheels to make testing upcoming releases easier
matplotlib is also caught by xcb/pyside6.5 interactions
(In fact, I can't even run the hello world example from https://doc.qt.io/qtforpython/quickstart.html locally with pyside6 v6.5.0 because of xcb plugin issues... even without xvfb-run)
Not only us!
Is there an issue in the mpl repo? Or a PR with a workaround/fix?
Nothing yet, I first noticed it when we had a PR intended to "fix" (by disallowing the current specific version) a different dependency that released yesterday and was causing test failures... when that still caused test failures (which I had been, I saw Qt problems)
I have no fix, can't even run simple pyside-only examples locally
The only "workaround" I would have right now is not allowing pyside 6.5...
Last time I ran into this. Needed an extra apt package installed; @the-compiler historically has been amazing at identifying fixes for these issues
import sys
from PySide6.QtWidgets import QApplication, QLabel
app = QApplication(sys.argv)
label = QLabel("Hello World!")
label.show()
app.exec()
Even this fails with:
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
Available platform plugins are: vkkhrdisplay, minimalegl, linuxfb, wayland, offscreen, xcb, vnc, eglfs, minimal, wayland-egl.
Aborted (core dumped)
Okay, found it:
Running with QT_DEBUG_PLUGINS=1 python pyside_ex.py
Revealed that it was trying to link to libxcb-cursor.so.0, which was not installed
This was fixed by sudo apt install libxcb-cursor0
Specifically this line from that output (Well, and the very similar log right before it):
qt.core.plugin.loader: QLibraryPrivate::loadPlugin failed on "/home/kyle/.pyenv/versions/3.11.0/envs/edge/lib/python3.11/site-packages/PySide6/Qt/plugins/platforms/libqxcb.so" : "Cannot load library /home/kyle/.pyenv/versions/3.11.0/envs/edge/lib/python3.11/site-packages/PySide6/Qt/plugins/platforms/libqxcb.so: (libxcb-cursor.so.0: cannot open shared object file: No such file or directory)"
Nice debugging I think I set that variable in our CI
https://github.com/matplotlib/matplotlib/pull/25619
For reference
For the memory leak, there's a lambda function being used as a slot in WidgetGroup and it holds references to self and a widget. I was able to significantly reduce the amount of memory leaked by avoiding the lambda capture. However there is still some memory growth and it doesn't explain why all other bindings including PySide6 6.4.3 have no such issues
Nice find … this lambda reference situation sounds familiar, I think @obsidian sapphire ran into this when setting up some signals/slot mechanism with parameter trees but I could be remembering wrong
I believe the breakage was lambdas that referenced self (and by breakage I mean reference hold)
Yeah, qt does magic to clean up references, but only for fully declared methods.
In this case, it's a double reference. lambda *args: self.widgetChanged(w, *args)
PySide6 6.5.0 does run out-of-the-box on WSL2 since it uses wayland
I have added a pyqtgraph-free example to #2672 that shows a leak only on PySide6 6.5.0.
I wonder if it was just a happy accident that it didn't leak for PySide < 6.5.0
I'm going to speculate happy accident
@twin spire @fervent vale created a pyqtgraph-free example of the memory leak in question:
https://github.com/pyqtgraph/pyqtgraph/issues/2672#issuecomment-1496888196
looks like CI is green, thanks Kyle (already tagged you on github, don't need to tag you here too 😆 )
Probably should do another release after we verify PyQt6 6.5 works.
looking at those pypy test failures 😬 I'm amazed you're willing to take this on pijyoi, admittedly I'm not sure of the utility here is (but I've never done anything with pypy before so 🤷 )
just to see how far it goes... the major hurdle is the lhs mixin inheritance not working
PyQt6 6.5 snapshots are available for testing
Show and tell: Pyqtgraph-based battlesnakes 🙂
joking aside, you should post to PuPPy 😛
I would guess this is what is happening in Barney
from PySide6 import QtCore
class SenderThread(QtCore.QThread):
signal = QtCore.Signal(object)
def __init__(self):
super().__init__()
def send(self, msg):
self.signal.emit(msg)
class ReceiverThread(QtCore.QThread):
def __init__(self):
super().__init__()
def recv(self, msg):
print(QtCore.QThread.currentThread(), msg)
def run(self):
# thread finishes upon return from run()
pass
app = QtCore.QCoreApplication([])
sender = SenderThread()
receiver = ReceiverThread()
print('main ', QtCore.QThread.currentThread())
print('sender ', sender)
print('receiver', receiver)
# thread affinity of "receiver" QObject is main thread
assert receiver.thread() == QtCore.QThread.currentThread()
sender.signal.connect(receiver.recv)
receiver.finished.connect(lambda: print('BYE'))
receiver.start()
sender.start()
timer = QtCore.QTimer()
QtCore.QTimer.singleShot(500, lambda : sender.send("HELLO"))
QtCore.QTimer.singleShot(1000, app.exit)
app.exec()
for thr in [sender, receiver]:
thr.quit()
thr.wait()
In the above script, "HELLO" is actually printed by the main thread. receiver thread is already finished by the time the signal is emitted
@twin spire based on the activity on our issue tracker, people seem to be quick to be adopting pyside 6.5 😆
https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/widgets/GraphicsView.py#L107
Spawning a QGraphicsWidget here without setting it's parent makes a new top-level widget. Would it make sense to pass parent=self.parent() there?
It avoids (this still happens; I need to refine my testing criteria)QtWidgets.QApplication.topLevelWidgets() from reporting an extra blank widget
If there are no downsides, I'd be happy to make a PR
pyqtgraph/widgets/GraphicsView.py line 107
self.setCentralItem(QtWidgets.QGraphicsWidget())```
I don't think that will do anything:
https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/widgets/GraphicsView.py#L180
http://doc.qt.io/qt-6/qgraphicsscene.html#addItem
Adds or moves the item and all its children to this scene. This scene takes ownership of the item.
pyqtgraph/widgets/GraphicsView.py line 180
self.sceneObj.addItem(item)```
I need to become more robust on the widget/item relationship here, but is there a good spot to look for where the additional top window is being created?
from pyqtgraph import mkQApp, GraphicsView
app = mkQApp()
app.processEvents()
print(f"{app.topLevelWidgets()=}")
# app.topLevelWidgets()=[]
view = GraphicsView()
app.processEvents()
print(f"{app.topLevelWidgets()=}")
# app.topLevelWidgets()=[<PySide2.QtWidgets.QWidget(0x1cbd24bb420) at 0x000001CBD45928C0>, <pyqtgraph.widgets.GraphicsView.GraphicsView(0x1cbd3587150) at 0x000001CBD4592780>]
I thought it was at line 107 during debugging, but the place it appears changes over multiple runs so it's not reported deterministically
I don't think there is anything wrong here, I suspect the QWidget here is the "viewport" (see GraphicsView.useOpenGL)?
also if you think it's an ownership thing, try calling view.close() and then see what the topLevelWidgets are?
i just fell through the rabbit hole --- i ended up here , fast data display you say...
view.viewport() in app.topLevelWidgets()
# False
I thought so too, but I can also set viewport to None and there is still an additional top widget
Also doesn't help to use view.viewport().setParent(parent) or view.viewport().close()
we try
does calling view.close() remove both of the top level widgets?
After view.close() just the GraphicsView is a top level widget
huh, the GraphicsView doesn't get removed from the list of topLevelWidgets ? that's weird...
Probably because the cli still has a reference to it? Or because it was never shown in the first place? same thing happens with win = QMainWindow();win.close();app.topLevelWidgets()
wait are you doing things from interactive mode?
Yeah, but the outputs are the same as running a python script so far
I found that the act of using both setLayout and setGeometry creates the extra top-level QWidget. (pg.GraphicsView uses both together)
from PySide6 import QtCore, QtWidgets
app = QtWidgets.QApplication([])
scene = QtWidgets.QGraphicsScene()
gv = QtWidgets.QGraphicsView(scene)
gw = QtWidgets.QGraphicsWidget()
gw.setLayout(QtWidgets.QGraphicsGridLayout())
gw.setGeometry(QtCore.QRectF(0, 0, 100, 100))
scene.addItem(gw)
gv.show()
print(app.topLevelWidgets())
im very new here
any hoomans here today
Some maintainers and contributors monitor this channel and respond when they can. Do you have a question?
im just doing the basic installs fresh , python3.11 - 64bit , pyserial , openCV ....
QT is payware ?
i have , pyqtgraph installed -- i ask because , sometimes there is a better way
Python Qt bindings are dual licensed LPGL for pyside and GPLv3 for pyqt; and a commercial license is available for both. Commercial licenses may require a commercial Qt license but I’m not a lawyer and I don’t know.
i dont know what to use -
Best way to install is to pip install the Qt bindings of your choice. We make no attempt to show preference towards any particular binding
freeware and workable with minimal headache is my goal
I’ll say that the newly released pyside6 6.5 bindings have an issue with PyQtGraph, issue will be fixed in the next release
can Tkinter be used to send message to the speedy , pyqtgraph stuff for drawing
Python Qt bindings are available for free, but there are license restrictions on what you can do with them. This is not a good place to discuss those restrictions
PyQtGraph needs qt to work. Can’t use PyQtGraph without a Python wt binding.
Unless you’re doing something really complicated you will likely not notice a difference between the bindings other than their respective imports
im just using a breadboard MCU project
pygame will be tooooo slow for what i want to do
nothing commercial just messing around with MCU
Qt would likely work well for you GUI wise. I’ve never used pygame so I can’t compare
pyqt6 6.5 was released, but getting a failure on test_PolyLineROI (at least on macOS)
seems to pass on CI tho 😖
I’d like to do a release on the next few days. I’m going on a holiday where I’ll be inaccessible for over a week starting Saturday.
Anyone have issues in mind that should be addressed before the release? I want to look at the test_PolyLineROI failure more closely.
TIL about Nuitka for compiling python applications (https://github.com/Nuitka/Nuitka)
Specifically configured to work with PyQt/PySide applications and compiles to C++ (python calls, but still in C)! Wild. Author is super responsive and working on getting it compiling with pyqtgraph
I knew it was a thing but didn’t know it worked with Qt stuff; curious how well it works with PyQtGraph, we could consider adding a CI pipeline for it too
https://github.com/Nuitka/Nuitka/issues/2162 and https://github.com/Nuitka/Nuitka/issues/1532 seem to be in the way for now
Output of pip freeze certifi @ file:///C:/b/abs_85o_6fm0se/croot/certifi_1671487778835/work/certifi click==8.1.3 colorama==0.4.6 darkdetect==0.7.1 Nuitka==1.5.5 numpy==1.24.2 ordered-set==4.1.0 pac...
I’d like to do a release on the next few
love the "excellent_report" label 😆
Haha right?
#2658 should get addressed.
Quick question, before I create an issue:
Is this problem already known? EDIT: IT IS ALREADY KNOWN!
Traceback (most recent call last):
File "*********\GidAppTools\.venv\Lib\site-packages\pyqtgraph\examples\ColorBarItem.py", line 85, in <module>
main_window = MainWindow()
^^^^^^^^^^^^
File "*********\GidAppTools\.venv\Lib\site-packages\pyqtgraph\examples\ColorBarItem.py", line 27, in __init__
p1 = gr_wid.addPlot(title="non-interactive")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "*********\GidAppTools\.venv\Lib\site-packages\pyqtgraph\graphicsItems\GraphicsLayout.py", line 72, in addPlot
plot = PlotItem(**kargs)
^^^^^^^^^^^^^^^^^
File "*********\GidAppTools\.venv\Lib\site-packages\pyqtgraph\graphicsItems\PlotItem\PlotItem.py", line 154, in __init__
self.titleLabel = LabelItem('', size='11pt', parent=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "*********\GidAppTools\.venv\Lib\site-packages\pyqtgraph\graphicsItems\LabelItem.py", line 19, in __init__
GraphicsWidget.__init__(self, parent)
File "*********\GidAppTools\.venv\Lib\site-packages\pyqtgraph\graphicsItems\GraphicsWidget.py", line 18, in __init__
QtWidgets.QGraphicsWidget.__init__(self, *args, **kwargs)
TypeError: GraphicsWidgetAnchor.__init__() takes 1 positional argument but 2 were given
reproduceable with the examples:
.) ColorBarItem.py
.) BarGraphItem.py
.) customPlot.py
.) probably many other, but I checked with those
vers: pyqtgraph-0.13.2 (also did a fresh reinstall with --force-reinstall --no-cache-dir)
os: Windows 10
python: Python 3.11.1
other deps: QtPy-2.3.1, PySide6-6.5.0
It is tracked here: https://github.com/pyqtgraph/pyqtgraph/issues/2680
oh sorry, somehow overlooked that,
you want me to delete that block of text to keep the channel clean?
No worries, I think it's fine to leave the history
looks like the test_polyROI failure I'm seeing is OS version dependent, not Qt version dependent
going to start the process for a new release, have a ton of stuff going on today so hopefully I don't mess it up 😄
actually, going to wait for #2689 to be updated, then I'll start the release process 😆
0.13.3 released with pyside6 6.5 compatibility, thank you to all contributors for making this release happen!
Not going to lie. These smaller releases where I can do the change log in 1 minute are nice
Want to thank, for all the effort and the awesome package!
I’m going on a holiday until next Sunday, @mortal grotto now is your opportunity to merge everything while I’m gone 😂
Finally got the conda CI to pass by dropping to Python 3.10. PySide2 is not officially supported on Python 3.11.
If someone happens to be running Fedora 37 (which uses Python 3.11), they could try installing the distro provided PySide2 and see if the tests crash at the same spot
If I was home i would fire up a docker container … but pyside2 is pretty old now, wouldn’t be surprised if there are issues with Python 3.11 (or 3.10). That said the conda forge folks try to maintain comparability
Just saw your comment. Yeah feedstock maintainers will definitely apply patches to try and maintain comparability. They may want to know of this issue. For our purposes I’m good with skipping Python 3.10 on the conda CI pipelines
I tried running Python 3.11/PySide2 on Fedora 37/WSL2 and also miniconda/Windows. No crashes there.
Hello!
I found that shaders will break itemsAt function in pyqtgraph.openGL
If we will set shader='shaded' in GLMeshItem, itemsAt will return all objects with near param = ~0.0:
[(0.3899694386380653, 0), (0.0, 1), (0.4387341480326685, 2), (0.0, 3), (0.0, 4), (0.3882200134425005, 5)]
If we delete shader='shaded', all works good:
[(0.9989592458584716, 1)]
How shaders change the logic of itemsAt? How can I fix it?
Next message I will send a testing code
import QGIS3dViewer.tools.pyqtgraph as pg
import QGIS3dViewer.tools.pyqtgraph.opengl as gl
import numpy as np
class Point:
def __init__(self, x, y, z):
self._x = x
self._y = y
self._z = z
def x(self):
return self._x
def y(self):
return self._y
def z(self):
return self._z
class MyGLV(gl.GLViewWidget):
def mouseReleaseEvent(self, ev):
lpos = ev.position() if hasattr(ev, 'position') else ev.localPos()
region = [lpos.x()-5, lpos.y()-5, 10, 10]
# itemsAt seems to take in device pixels
dpr = self.devicePixelRatio()
region = tuple([x * dpr for x in region])
for item in self.itemsAt(region):
print(item.objectName())
pg.mkQApp()
glv = MyGLV()
glv.setCameraParams(elevation=90, azimuth=-90, distance=50)
# X points right, Y points up
glv.show()
info = {'red': [Point(0, 0, 0), Point(10, 10, 10), Point(5, 10, 0)], 'blue': [Point(10, 20, 15), Point(20, 20, 20), Point(0, 1, 20), Point(-10, -20, -15), Point(0, 0, 0)]}
for name, points in info.items():
for i in range(0, len(points) - 1):
x = points[i].x()
y = points[i].y()
z = points[i].z()
x1 = points[i + 1].x()
y1 = points[i + 1].y()
z1 = points[i + 1].z()
point1 = np.array([x, y, z])
point2 = np.array([x1, y1, z1])
v = point2 - point1
theta = np.arctan2(v[1], v[0])
phi = np.arctan2(np.linalg.norm(v[:2]), v[2])
tr = pg.Transform3D()
tr.translate(*point1)
tr.rotate(theta * 180 / np.pi, 0, 0, 1)
tr.rotate(phi * 180 / np.pi, 0, 1, 0)
md = gl.MeshData.cylinder(rows=1, cols=6, radius=[1, 1], length=np.linalg.norm(v))
m1 = gl.GLMeshItem(meshdata=md,
smooth=False,
shader='shaded')
m1.setTransform(tr)
m1.setObjectName(f'{name}')
glv.addItem(m1)
pg.exec()
Does using https://github.com/pyqtgraph/pyqtgraph/pull/2659 make any difference?
I am using this version, it doesn't help for me
I tried out your script, even with shaders enabled, only one color gets printed
Hmmm... Did you try master version or this one:
https://github.com/pyqtgraph/pyqtgraph/pull/2659
I tried master with PyQt 5.15. Then I rebased #2659 to master and tried it with PySide 6.5.0
a
b
c
d
I appreciate the humour, but this is bordering on the line of spam. This is a partner channel for the pyqtgraph open source project.
Pls some help I want to generate sinewave through dac0 of waveshare high precision adda board with pi4 python pyside2 but increasing in sample rate decreasing the frequency
What do you need help with here? PyQtGraph should handle that no problem, especially if your data is already accessible as a numpy array
def handleSinewave(self):
a = 0.2
f = 0.1
period = 1.0 / f
print("Period", period)
samples_per_period = 1000
wt = period / samples_per_period
num_periods = 10 # number of full periods to generate
num_samples = samples_per_period * num_periods
while True :
for i in range(num_samples):
voltage = 2.446 + a * np.sin(2 * np.pi * f * i / samples_per_period)
voltage = np.clip(voltage, 0, 5)
self.DAC.DAC8532_Out_Voltage(0x30, voltage)
print("voltage", voltage)
time.sleep(wt)
When I increase the sampling rate frequency decreases and as I decreasing the frequency the sinewave dissorted using pi4 8 gb as microcontroller
Waveshare high precision adda board with dac8532 as wave generator
Matplotlib is plotting the same graph but in practical above scenario is happening
I'm still not understanding exactly what the issue is in context to pyqtgraph, maybe some screenshots of the plots in question might help? I don't know much about micro-controllers so if there is an issue there, I won't be of any use, sorry 🙂
@fervent vale look at that! https://bugreports.qt.io/browse/PYSIDE-2299 (lambda reference leak fixed in 6.6.0)
Oh nice. I see that it's a separate issue from referencing self.
looks like now marked to be fixed in 6.5.1
Regarding pyqtgraph/pyqtgraph#2707 I'm not sure if that axes attribute qualifies as public API or not, I'm not seeing a reference to it in the documentation so I'm inclined to say we should just close out this PR. Thanks for posting the alternative method of getting it.
looking at pyqtgraph/pyqtgraph#2694 (using self.sender() ) I have some ancient memory of that only working if you had a @Slot() decorator... but that could have been a Qt4 thing.
@fervent vale is there anything specific I can help with regarding your GLViewWidget PR?
How do the docs look like with that PR?
see for yourself: https://pyqtgraph--2659.org.readthedocs.build/en/2659/
(seriously tho, isn't that cool that you can see the docs for a PR directly?)
more specifically the GLViewWidget: https://pyqtgraph--2659.org.readthedocs.build/en/2659/api_reference/3dgraphics/glviewwidget.html#glviewwidget
I imagine you want the docstrings to get inherited ?
you could try adding :inherited-members: under :members:
I don't mind adding the commit there, but I don't go pushing to other people's branches unless they ask me to 😛
OK I will give it a stab later
Since v5.0, it can take a comma separated list of ancestor classes. It allows to suppress inherited members of several classes on the module at once by specifying the option to automodule directive. (this is for the
:inherited-members;bit, not sure how to add a coma separated list here (I'm a n00b when it comes to sphinx)
so probably :inherited-members: GLViewMixin anyway I'll leave it at that
anything else you think I should give attention to sooner than later? you've been more active on the repo than I have the last few .... last while
#2664 has loads of whitespace modifications.
I know it would be difficult for the author to undo them
But it's really hard to see the diffs
even w/ the "hide whitespace" option on the diff viewer?
this doesn't look too bad (diff view format that is)
Oh that's a feature I didn't know about
it's bitten me before with python not catching changes in indentation
so definitely use it with caution but for browsing a PR with changes this like that it's very useful
I can't seem to get :inherited-members: working correctly. I want it to list the GLViewMixin members but not the QOpenGLWidget members
it's supposed to work by specifying the base class that you do not want, but various combinations of specifying QOpenGLWidget doesn't seem to work
fully qualified would be PyQt6.QtOpenGLWidgets.QOpenGLWidget
Not sure, I’ll mess with it locally on my system today and see if I can get the intended behavior
Hi guys
I want to draw a graph , but have no clue how to do so , can someone help me
Is that like an iso plot? Is there an analogous plot in matplotlib you can point to as a reference?
No code needed, but if you look at the matplotlib examples, is there something there that looks like what you want to draw?
Nope
This is like the output code for one of the algorithm we run
And btw is there any way I can label regions like the top right triangle as t1 , the trapezium as some t2 and so on
Labeling might take extra work but if you can represent the regions as polygons, you can use PColorMeshItem
Ahh I see, I think if you look at the examples and look at the isometric plot that can draw what you want
yeah, I see what you mean, I'll see if I can tweak this to get it right...
that's probably not what we want....
ok, got it
GLViewWidget
============
.. autoclass:: pyqtgraph.opengl.GLViewWidget
:members:
:inherited-members: QOpenGLWidget
.. automethod:: pyqtgraph.opengl.GLViewWidget.__init__
ok, i'm off to bed 💤
Hello friends i am new to this community .
I was creating a Amazon price tracker .
For that i want to make a bot which take the url of the current page after searching for the product . And i want to host it on python anywhere but , for that can't use gui i want to use headless browser . Which i have never worked with , the normal code is running good but headless i am having error .
This is the code.
'''import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
def get_the_price_data_from_selenium(url):
# Path to the ChromeDriver executable
chrome_driver_path = "D:/Codebase2.0/100Days_of_code/Day47/Amazon_price_tracker/Web_Amz_prz_tkr/chromedriver.exe"
# chrome options
# chrome_options = Options()
# chrome_options.add_argument("--headless")
# Initialize the ChromeDriver
service = Service(chrome_driver_path)
# driver = webdriver.Chrome(service=service, options=chrome_options)
driver = webdriver.Chrome(service=service)
# Implicit wait
driver.implicitly_wait(time_to_wait=20)
# Navigate to the website
driver.get('https://keepa.com/#!product/10-B0BYZ26QGB')
print("1. Navigated to the website.")
# Find the search icon element and click it
# search_icon = driver.find_element(By.ID, "showSearchBar")
# search_icon.click()
search_icon = driver.find_element(By.XPATH, '//a[@id="showSearchBar"]')
search_icon.click()
print("2. Clicked on the search icon.")
# Add a delay to allow the search bar to load
time.sleep(2)
# Now search bar has changed to search input
search_bar = driver.find_element(By.ID, "searchInput")
search_bar.send_keys(url)
search_bar.send_keys(Keys.ENTER)
print("3. Entered search query and pressed ENTER.")
# Wait for the search results to load
wait = WebDriverWait(driver, 5)
product_url = driver.current_url
print("4. Obtained the current URL.")
print(product_url)
# Close the browser
driver.quit()
print("5. Browser closed.")
test_url = "https://amzn.eu/d/0MsiF3d"
get_the_price_data_from_selenium(test_url)
'''
Amazon price history charts, price drop alerts, price watches, daily drops and browser extensions.
Is there a way to get GLViewMixin.__init__ to be documented?
Another thing is that he parent argument actually still works by cooperative multi-inheritance, but it doesn't belong to the doc-string of GLViewMixin either.
Ok, I took the easy way out and just put the docstring into GLViewWidget
I found a possibly breaking change. The devicePixelRatio and rotationMethod arguments are keyword only in this PR due to the cooperative multi-inheritance
Previously, they could have been positional, although that usage would have been awkward
Yeah I agree, that would be awkward. I’m fine with the change to keyword only
You know, maybe we’re overthinking this documentation wise.
We can have GlViewMixin documented, have a warning saying this class is not intended to be inherited by end users, and not part of the official API .., that way we can highlight how we are inheriting the base QOpenGL widget as well.
not sure what I am doing wrong, but GLViewMixin is getting documented wrongly as GLViewWidget
I’ll take a look after I get the kids to bed 👍
no clue how/why it's got them confused (maybe a sphinx bug?) but I think the following generates results that are sufficient:
GLViewWidget
============
.. autoclass:: pyqtgraph.opengl.GLViewWidget
:members:
:show-inheritance:
:inherited-members: QOpenGLWidget
.. automethod:: pyqtgraph.opengl.GLViewWidget.__init__
.. autoclass:: pyqtgraph.opengl.GLViewWidget::GLViewMixin
.. warning:: This class is not intended for users to inherit or use
directly, and is not considered part of the public API.
:members:
.. automethod:: pyqtgraph.opengl.GLViewWidget::GLViewMixin.__init__
on re-reading your pijyoi, realized that GLViewMixin was intended for inheriting for use of QOpenGLWindow, so that warning is perhaps not great completely off base, sorry about that.
let me re-read and I'll try and suggest something that might be better
I think the intention was that the advanced user could do that. But there was no promise that the interface would be stable.
something along those lines should be put there...
The intention of this class is to provide users who want to use ``QOpenGLWindow`` instead of ``QOpenGLWidget`` but retain the benefits of ``GLViewWidget``. Usage of this class should be considered experimental for the time being as it may change without warning in future releases.
updated the comment section.
so, ugh... what should get my attention next in the library? probably that styling PR?
The problem with unnecessary whitespace modifications is that you are prone to merge conflicts
Yeah; that will certainly cause issues, at just we no longer have 150+ open PRs.
I’m down to look at other PRs first
does this look ...not right? this is manipulating a list as it's being iterated over... any time I've tried this before it has blown up on me 😆
pyqtgraph/exporters/SVGExporter.py line 108
items = [item]```
I think specifically because it is only ever appended to, it may actually just work and be much easier to manage than doing multiple containers. It’s basically just a breadth first node list
was thinking before I went to bed that issue 2661 would be straight forward before I went to bed... ugh, was way off base...
if I figure out what's wrong w/ this issue and make a PR for it, I may redo that bit of code...
The real problems with modifying containers you are looping over is if you modify contents at or before the index you are looking at… changing (and expecting it to be visited), inserting, or removing will cause weird problems and “don’t do that” is an easier rule of thumb, but doesn’t actually capture the nuance of what will just work
and with this attempt, I think I'm going to call it a night 😆
ok, ended up being not too difficult to implement what I wanted to ... still need to handle the scaling case and do a bunch more code cleanup
I don't like having this logic in the exporter where it must know about a variety of the different graphics items...
if isinstance(item, PlotCurveItem):
# manipulate positioning as such
elif isinstance(item, ScatterPlotItem):
# manipulate positioning of data different way...
elif isinstance(item, TextItem):
# manipulate positioning of text in a different way...
we're having to manipulate the positioning here due to precision issues involving SVGs, but the if/elif/elif/elif... structure doesn't seem great here.
Regarding #2728, what is the meaning of dx = max(rect.right(), 0.) - min(rect.left(), 0.)? E.g., if right==2000 and left==1000, then dx==2000?
i copied that logic over from the method that was commented out; but in you're right, that doesn't look correct
I still hate that I'm having to call setData, I should try applying a QTransform to the curve and see if that works (and apply the inverse transform after the SVG is written)
decided to browse for problematic usages of QTransform.inverted() and I came across this one here:
looks like non-cosmetic pens might be problematic for pyqtgraph...
src/gui/painting/qpaintengineex.cpp lines 375 to 380
if (pen.isCosmetic()) {
clipRect = d->exDeviceRect;
cpRect.translate(xf.dx(), xf.dy());
} else {
clipRect = xf.inverted().mapRect(QRectF(d->exDeviceRect));
}```
i keep thinking the SVG PR I'm working on (for shifting plot items back forwards the origin and scaling them between -0.5, 0.5) that I shouldn't need to call PlotCurveItem.setData but merely call PlotCurveItem.setTransform but I couldn't get that working to save my life...
if someone else wants to have another go at it, the closest I got was taking the current PR ...
and having the following differences...
if isinstance(item, PlotCurveItem):
rect = item.viewRect()
x_range = rect.right() - rect.left()
dx = rect.left() + (x_range / 2)
y_range = rect.top() - rect.bottom()
dy = rect.bottom() + y_range / 2
sx = 1 / abs(x_range)
sy = 1 / abs(y_range)
# to undo the change we roll out...
preserve_original = item.transform()
# move towards origin
center = QtGui.QTransform(sx, 0, 0, sy, dx, dy)
item.setTransform(center)
...
# after the QPainter does it's paint operation...
doc = xml.parseString(arr.data())
if isinstance(item, PlotCurveItem):
item.setTransform(preserve_original)
The following test fails on PySide6 6.5.1 : pytest -v .\tests\parametertree\test_parametertypes.py::test_pen_settings
Almost all the pen related settings in PlotSpeedTest.py no longer work. Only changing color works.
I rather suspect it's the bug fix for the lambda leak that is causing this
It seems like all the various pen properties are wrongly calling "setColor"
And "setColor" happens to be the first property of pen that is being created.
Nice catch, we can probably just remove the workaround at this point, or do you think we should keep it being an if statement of a pyside version check
No, I meant that it's PySide's bugfix for the leak that's causing this new issue
Oh, I misread (just woke up) I’ll try and take a look in a few hours
This is the pyqtgraph-free MWE that shows the issue present in PySide 6.5.1, and is the reason for pyqtgraph's pen parameter failure
from PySide6 import QtCore
class MySignals(QtCore.QObject):
sigA = QtCore.Signal()
sigB = QtCore.Signal()
sigC = QtCore.Signal()
x = MySignals()
sigs = [x.sigA, x.sigB, x.sigC]
for idx, sig in enumerate(sigs):
sig.connect(lambda x=idx: print(x))
for sig in sigs:
sig.emit()
I won’t be on a computer for a few hours but I’m curious if there would be different behavior if the slot has the @Slot decorator.
it makes no difference
figured that was a long shot :/
I have modified the example, and it seems like it should break a lot of code
or maybe not... it's code with multiple signals that connect to on-the-fly created slots
Mpl also having failures due to qt, currently on a plane (on the ground), so haven’t looked at it at all to see if it is related or not:
The weekly build with nightly wheels from numpy and pandas has failed. Check the logs for any updates that need to be made in matplotlib. https://github.com/matplotlib/matplotlib/actions/runs/50972...
Another error occurring in PySide 6.5.1
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore
app = pg.mkQApp()
pw = pg.PlotWidget()
pw.addItem(pg.GradientEditorItem())
pw.show()
# following timer is resulting in:
# TypeError: sigGradientChangeFinished(PyObject) needs 1 argument(s), 0 given!
QtCore.QTimer.singleShot(500, app.quit)
pg.exec()
This code exit pattern is used to test the pyqtgraph examples, with the result that any example that includes a GradientEditorItem will fail
ok, I have the pyqtgraph-free MWE
from PySide6 import QtCore
class Parent(QtCore.QObject):
sigParent = QtCore.Signal(object)
class Child(Parent):
sigChild = QtCore.Signal(object)
def __init__(self):
super().__init__()
self.sigParent.connect(self.sigChild.emit)
app = QtCore.QCoreApplication()
c = Child()
# following timer is resulting in:
# TypeError: sigGradientChangeFinished(PyObject) needs 1 argument(s), 0 given!
QtCore.QTimer.singleShot(500, app.quit)
app.exec()
this signal-slot connection pattern is present in GradientEditorItem
This one seems to be a pyqtgraph anti-pattern. Shouldn't it just be self.sigParent.connect(self.sigChild)?
Yeah
Signals can be connected to other signals, not to their emit method
I’m actually surprised that ever worked
Maybe @mortal grotto could review #2734 regarding the changes to pen parameter
diff LGTM; I'll let @mortal grotto merge tho
LGTM too 👍
Thank you @fervent vale !!
Should we do another release with this fix? Maybe we wait for pyqt6 6.5.1 to be released and make sure it works there?
Should raise a bug report against PySide for the inner function conflation
I’ll make a bug report using your MWE if you’re good with that
anything else we should consider merging before doing another release?
Looks like someone already made the bug report PYSIDE-2346
Oh nice, am putting the kids to bed in an hour or so so was going to submit then
pyqt6 6.5.1 is now out... I know because mpl tests started failing....
😆
gah and the pypi search page had the old version listed still so I fooled myself into thinking that wasn't it for a couple minutes and started scratching my head like "what changed???"
amateur move there, gotta go to that "release history" page 😉
no, I've never been duped like that before 😆
triggering a CI rrun .... we'll see what happens 😬
We had:
- Deprecation warning regarding
QtCore.Qt.AA_EnableHighDpiScaling - GLIBC version mismatch?
- somehow loading both qt6.5.0 and qt6.5.1?
- Subprocess calls which failed, but not sure why from just gh actions logs, could just be the above, could potentially be the same problem we had with pyside6.
our regular test suite passed, we got some warnings but they're numpy related
we're all green here regarding PyQt6 6.5.1
the pyqt6 6.5.1 wheels contain symbols from a glibc version greater than the wheel states it supports... so that's a thing...
I would post to the mail list, Phil is pretty responsive for things like that
for x in *abi3.so; do objdump -t $x | grep GLIBC_ ; done | sort | uniq | grep -v "2.2.5"
0000000000000000 F *UND* 0000000000000000 __stack_chk_fail@GLIBC_2.4
0000000000000000 F *UND* 0000000000000000 hypotf@GLIBC_2.35
0000000000000000 F *UND* 0000000000000000 memcpy@GLIBC_2.14
QtGui.abi3.so has a dependency on hypotf@GLIBC_2.35
So pypi provided wheel PyQt6 6.5.1 doesn't run on Ubuntu 20.04 which has glibc 2.31
I guess it wouldn't run on the RHEL 9.x family either, since that's on glibc 2.34
if this can't run on ubuntu 20.04 or RHEL 9, I imagine that there will be some complaints about this in short order.
leave it to me to completely blow up the scope of a PR to fix an issue pyqtgraph/pyqtgraph#2728
realized that the exporter checks for the existance of GraphicsItem.generateSvg() and if it exists, it runs that for SVG output.. .realize that's not documented anywhere, so I create an empty method in GraphicsItem, only to realize that none of the exporters are documented 🤦♂️
also not sure how I feel about these annotations 😬
I think I'm somewhat partial for that... although wish that the return type was a bit more expanded...
@plush fulcrum if you're looking at type-hints for mpl, I am sure you must be shuddering at how your huge function signatures are going to render...
type hints in stub files are not picked up by sphinx (at least by default), and we turned rendering in the docs off for now for e.g. pyplot which is is typed inline (as the docstring has the info already, and the rendering was not great...)
The mode to try to put them in the docstring did not play well with numpydoc, I believe (possible it could be as simple as reordering sphinx extensions, but at least in initial testing)
With numpy doc I’m having a hard time detailing the return type that is tuple[Element, list[Element]]
ugh, I'm going to have to create a named tuple, aren't I...
I suppose this looks ok, the "return time"
fixed the SVG background color issue, but the scale value on the other hand is going to be tricky 😬
"Overwrite" should be spelt "override"
yes, yes it should 😆
just sorted out the sizing issue on the SVG export, hoping that after I merge this PR it will be a long time before I have to look at it again
computer says no
There is traffic on this issue on the mail list now.
managed to also fix pyqtgraph/pyqtgraph#1849 on this PR, in hindsight I think that would have been a good first issue, oh well.
@plush fulcrum saw a message on the pyqt mail list that the latest snapshot of pyqt6 had the mismatched libc issue sorted out
story of my life right now, finish chores/exercise realize that I can get around my issue in pyqtgraph/pyqtgraph#2418 by using a separate object to capture timing information, but that object should run on a separate thread, and periodically it can fire off a signal with what should be on the title.
I sit down, fire up my editor, hop on the branch, start looking at the code and realize "you know what, going to bed on time sounds great"
@fervent vale @mortal grotto if you two have some time, would appreciate if you could test #2418 on your machines again; instead of doing weird things w/ QEventLoop, I decide to handle timing in a separate QThread.
In this PR, sigPaintFinished was added to PlotCurveItem. Then another standalone 2 signals are created: paintStarted and paintFinished. PlotCurveItem::sigPaintFinished -> paintFinished. But paintStarted gets emitted by PlotSpeedTest. Why not have a PlotCurveItem::sigPaintStarted? Then the 2 standalone signals are not needed at all.
Or maybe a single signal within PlotCurveItem would suffice: sigPaintDuration
That would eliminate measurement error due to signal overhead
Ooo good idea
the PR certainly needs more cleanup; curious if the QThread implementation is causing issues, if not I'll also adopt it to the scatter plot speed test as well
But is there really any need for a QThread? The setTitle goes back to the main gui thread. So sigPaintDuration could just as well be connected to a main thread callback using a QueuedConnection
the need for qthread has to do with being able to accurately measure the time between when setData is called and when paint finishes... previously I attempted to do this with introducing some blocking code via QEventLoop.exec(), and used sigPaintFinished to "unblock" the code, and thus measure the elapsed time.
unfortunately as you and ntjess noticed, that didn't work as intended
I probably should update the original post on that PR and state more clearly what I'm trying to achieve.
I have a simple implementation here https://github.com/pijyoi/pyqtgraph/commits/alternate_fps
instead of measuring time, just count the number of frames
instead of calling processEvents, just let the regular event loop run
there's no need to keep an averaging window
the large averaging window was there due to the jitter per frame
there is no need to measure "accurately" the time elapsed per frame
Thanks for posting, I’m traveling today but will try and take a closer look on Monday.
Hello!
I have a lot of lines that I want to draw as a cyllinder.
I used gl.MeshData.cylinde for that:
md = gl.MeshData.cylinder(rows=1, cols=cols, radius=[radius, radius], length=np.linalg.norm(v))
m1 = gl.GLMeshItem(meshdata=md,
smooth=False,
shader='myShader',
color=color_rgb)
m1.setTransform(tr)
But I want to combine them to optimize drawing. How can I do that? How can I update meshData to one big mesh?
@keen crater I don't know the answer to your question, just wanted you to know I'm not ignoring you ...I just don't use the 3D aspects of the library much myself...
new pypy just dropped: https://www.pypy.org/posts/2023/06/pypy-v7312-release.html
not seeing anything in the changelog regarding multiple inheritance tho
just realized that in Qt 6.5, using fusion application style, it automatically detects and applies the windows dark theme 🎊
The multiple inheritance issue is PySide's problem
So think the test suite went from routinely segfault and to getting runtime warnings for non-common platforms…
s390x may be uncommon, but aarch64 is getting mainstream
yeah, as much as I use macOS, I don't have access to one of the M-processors yet; still chugging away on my 2019 16" i9 macbook pro
Just wanted to state my appreciation to any PyQtGraph developers / contributors here. I'm using PyQtGraph for the first time, and it rocks!
Thanks @undone flame I’m curious what project/problem you had that caused you to give PyQtGraph a look, and what parts of the library you’re using!
Early stages of an audio app, using PyQtGraph to provide a visual representations of the waveform. Initially I tried VisPy, which has excellent performance, but I was finding it tricky to accomplish what I was trying to do. I then gave PyQtGraph a go as it seemed to be a bit more mature. I'm very impresses with it, and great to see that it has a good amount of documentation, and it looks like it will flexible and fast enough for my needs.
my involvement w/ pyqtgraph started out of audio work too funny enough
i haven't touched it in a while, but you may find this guy of interest: https://github.com/j9ac9k/barney
our documentation could be much better, I'm quite jealous of the vispy examples documentation
if you ever figure out how to use QAudioOutput with a numpy array, in a way that is consistently low latency, be sure to let me know!
@fervent vale I’m in a location with awful internet connectivity, will comment more tomorrow once I get home.
I need to remove the remaining calls to processEvents()
The 0 interval update timer only gets fired when all other events have completed
Hence, each call to update() will naturally result in one repaint, without having to force it
:incoming_envelope: :ok_hand: applied timeout to @fickle robin until <t:1687574719:f> (10 minutes) (reason: duplicates spam - sent 4 duplicate messages).
The <@&831776746206265384> have been alerted for review.
I have SSH access to a M1 machine if you have one-off M1 testing needs. LMK if you want help!
appreciate the offer, so far numpy/qt bindings on the M-chips has been reliable and we haven't run into much in the way of platform specific issues ...but they do periodically happen. If I think it would be of help to have access to a machine, I'll be sure to reach out
@fervent vale ran into an issue with numpy in Qt/internals.py that's specifically for numpy 1.22.4 (1.22.3 and 1.23.5 seem to be fine)
https://github.com/pyqtgraph/pyqtgraph/pull/2755#issuecomment-1605862483
looks like this is the PR responsible: https://github.com/numpy/numpy/pull/21446
If it was a fix backported to 1.22.4, shouldn't it appear in newer versions?
🤷♀️ i get no error on 1.23.0
Going to run through the test suite one more time to make sure…
yea; test suite for 1.23.0 passes, ... I'll run a few more patch versions of 1.23.x ... also curious if this is an issue on python 3.10 or just 3.9
There's a sip 6.7.8 if else branch prior to the error
Need to check which branch is being taken
PyQt5 5.15.9
PyQt5-Qt5 5.15.2
PyQt5-sip 12.12.1
Okay, so it's taking the >= 6.7.8 branch
It should be sufficient to make the code go through the sip voidptr branch
you suggest modifying this if-statement? https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/Qt/internals.py#L177
pyqtgraph/Qt/internals.py line 177
if sip.SIP_VERSION >= 0x60708:```
But if numpy >= 1.23 works fine, then there must be another patch that didn't get backported
Yes line 177
The else branch is actually the workaround path
Where an extra voidptr gets instantiated
But I would consider it a bug (numpy or sip) that a sip.array can't be used directly with frombuffer
i agree it's a bug, and not a bug in our code.... and if it wasn't the most recent patch version for numpy 1.22 i would ignore it...
unfortunately have to wait until jan 1st before we can go numpy 1.23+ 😦
i guess I'll add an and condition there that checks for the numpy version...
i feel like I'm dirtying your pretty code 😆
if (sip.SIP_VERSION >= 0x60708 and
np.__version__ != "1.22.4" # workaround for numpy/sip issue
):
but it works
since fixing tox; noticing another intermediate error (completely unrelated)
def test_mouseDragEventSnap():
plt = pg.GraphicsView()
plt.show()
resizeWindow(plt, 200, 200)
vb = pg.ViewBox()
plt.scene().addItem(vb)
vb.resize(200, 200)
QtTest.QTest.qWaitForWindowExposed(plt)
QtTest.QTest.qWait(100)
# A Rectangular roi with scaleSnap enabled
initial_x = 20
initial_y = 20
roi = pg.RectROI((initial_x, initial_y), (20, 20), scaleSnap=True,
translateSnap=True, snapSize=1.0, movable=True)
vb.addItem(roi)
app.processEvents()
# Snap size roundtrip
assert roi.snapSize == 1.0
roi.snapSize = 0.2
assert roi.snapSize == 0.2
roi.snapSize = 1.0
assert roi.snapSize == 1.0
# Snap position check
snapped = roi.getSnapPosition(pg.Point(2.5, 3.5), snap=True)
assert snapped == pg.Point(2.0, 4.0)
# Only drag in y direction
roi_position = roi.mapToView(pg.Point(initial_x, initial_y))
mouseDrag(plt, roi_position, roi_position + pg.Point(0, 10),
QtCore.Qt.MouseButton.LeftButton)
> assert roi.pos() == pg.Point(initial_x, 19)
E assert Point(20.000000, 20.000000) == Point(20.000000, 19.000000)
E Use -v to get more diff
tests/graphicsItems/test_ROI.py:228: AssertionError
I'm actually surprised this numpy error hasn't been reported yet, this isn't some old version of numpy here...
Looking at the backport PR, from the point of view of numpy, it's not their bug either...
In [15]: sa = sip.array(QtCore.QPointF, 2)
In [16]: memoryview(sa)
---------------------------------------------------------------------------
BufferError Traceback (most recent call last)
Cell In[16], line 1
----> 1 memoryview(sa)
BufferError: format has not been specified
the numpy backported PR converts the object into a Python memoryview if it was not a numpy array to begin with
So the raised error is from Python
in newer numpy, a different condition is used to decide whether to make the buffer into a memoryview
https://github.com/numpy/numpy/blob/maintenance/1.23.x/numpy/core/src/multiarray/ctors.c#L3690-L3705
The "issue" as it were, is that we are using sip.array of non-primitive-scalar type, so the buffer doesn't have a format code set
whereas going through a sip.voidptr makes it into a "byte" type
which then passes through "memoryview" w/o error
I'll reword the comment as such that i'm not pointing the finger at numpy so much
is there any change that the sip module can (or should?) make to minimize likelihood of future issues?
I was mistaken, the exception "format has not been specified" is raised by sip, inside sip_array.c
As sip.array(s) of non-scalar types don't have a format, sip raises an exception if the buffer requestor asks for the "format" field to be populated
(There's a bug in sip here. A leak will occur if this exception is raised)
It could be argued that sip is being overly strict here, and that it could simply set the format as "B" (for bytes) in such a case
Python's PyBuffer_FillInfo will populate format as "B", so there's a precedent for that
The code in question is in PyQt6_sip-13.5.1.tar.gz::sip_array.c::sipArray_getbuffer
i assume the bug with sip is still there w/ the current version?
13.5.1 is the latest available from pypi
But I would imagine it would still be there in the snapshots
hmm...ok, probably should post something on the mail list about it...
To be clear, Python "memoryview(sip.array(QPointF, 2))" is sufficient to trigger the issue. No numpy needed
love you were able to scrap some of the calls to .processEvents()!
love this diff so much, moving the timing functionality to utils.py makes everything better, and the examples are less cluttered... there is uniformity across the benchmarks...
Strangely, infiniteline_performance.py isn't listed in utils.py
Not quite convinced of its usefulness though
Feel free to delete it; we clearly have gone this long without it
I see it's from PR286. Supposed to demonstrate the improvements due to caching bounding rect
… yeah I’m good with removing that
@fervent vale you considerirng any other changes on the fps update PR?
No more changes. I'm at work now anyway
I'm starting to come around to the idea of removing NonUniformImage
I'm not there yet 😆 (but getting there)
realizing the first/last elements are treated differently ...
# left, right, bottom, top
l = x[0] if i == 0 else (x[i - 1] + x[i]) / 2
r = (x[i] + x[i + 1]) / 2 if i < x.size - 1 else x[-1]
b = y[0] if j == 0 else (y[j - 1] + y[j]) / 2
t = (y[j] + y[j + 1]) / 2 if j < y.size - 1 else y[-1]
hmm...I see matplotlib has also a NonUniformImage that seems to accept data in the same way...
The last 2 examples in https://github.com/pyqtgraph/pyqtgraph/pull/2599 show how a BarGraphItem can almost implement NonUniformImage
oh right; you had mentioned that...
The border edges and the non-finite handling behavior are the missing parts
you think the thing to do would be to scrap the current implementation, and create a new NonUniformImage that subclasses from BarGraphItem and has the appropriate methods?
Feels weird, but if it works well, it works well
It would be busy-work for little gain
Speaking of weird, there's drawing functionality in ImageItem, which probably doesn't belong there
It looks like NonUniformImage::setLookupTable expects a HistogramLUTItem as argument. This differs from ImageItem and PColorMeshItem which take a numpy array for method of the same name
I think that is overly coupling NonUniformImage with HistogramLUTItem. Unfortunately, the NonUniformImage example makes use of it.
given that NonUniformImage hasn't been in the docs, I'm good with putting very aggressive deprecations to methods/arguments... one of the methods I don't like the name of, there is no setData method, ... tl;dr it needs work (which I'm happy to do).
The NonUniformImage::generatePicture is non-vectorized. It is prevented from doing so because the colormap / lut is not quantized to a set number of entries.
Something like what PColorMeshItem does would be good. Take either a colorMap instance or allow the user to set the numpy lut directly.
Was playing around with a ScaleBar today, and it works really well.
However, I would like the user to be able to toggle the scale bar on or off,
is it possible to delete a ScaleBar after it has been created?
i cant able to install python in my computer
I think our Legend has functionality to turn stuff on/off, but sure how much it makes sense to have a scale bar be part of the legend item... but that would be the first place I would look!
oh you meant delete, not just hide? sure if you have a reference to the scale bar, you should be able to do GraphicsLayout.removeItem(scaleBarInstance)
somehow missed that this should be raise ValueError(...) https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/graphicsItems/PColorMeshItem.py#L201
pyqtgraph/graphicsItems/PColorMeshItem.py line 201
ValueError('Data must been sent as (z) or (x, y, z)')```
Thanks for the quick reply,
In this case the scale bar is in a ViewBox, and trying ViewBox.removeItem(img_scale) gives the following answer
Traceback (most recent call last):
File "code\libs\create_gui.py", line 505, in show_scale_bar
self.viewbox.removeItem(self.img_scale)
File "Python310\site-packages\pyqtgraph\graphicsItems\ViewBox\ViewBox.py", line 429, in removeItem
item.setParentItem(None)
File "Python310\site-packages\pyqtgraph\graphicsItems\ScaleBar.py", line 67, in setParentItem
self.anchor(itemPos=anchor, parentPos=anchor, offset=offset)
File "Python310\site-packages\pyqtgraph\graphicsItems\GraphicsWidgetAnchor.py", line 37, in anchor
raise Exception("Cannot anchor; parent is not set.")
Exception: Cannot anchor; parent is not set.
self.viewbox.removeItem(self.img_scale)**
I think I might just do show/hide for now, as this seem to work well
Huh, that error shouldn’t be happening. Can you create a minimal example with it occurring? I’d like to take a closer look
PColorMeshItem::setLookupTable was implemented to take a list of QColors; vs ImageItem::setLookupTable taking a numpy array
and this difference actually got coded into ColorBarItem
I was making a breaking change to NonUniformImage::setLookupTable so that it accepts a numpy array. Then NonUniformImage no longer needs to know anything about HistogramLUTItem. Instead, NonUniformImage can be duck-typed to HistogramLUTItem::setImageItem
Then I thought, why not make it compatible to ColorBarItem::setImageItem?
And that's when I saw the discrepancy with PColorMeshItem::setLookupTable, which I rather consider to be an error
A more general method (on top of have setLookupTable take numpy arrays) would have been PColorMeshItem::setColorMap, from where a list of QColor(s) could have been obtained, if that was the more-convenient internal format
I'm good w/ breaking changes, ... if this is planned work, we should probably commit a warning to the repo right now, saying what won't work in the future
not sure if you saw the PyQt mail list, but Phil replied saying the sip_array.c::sipArray_getbuffer issues will be addressed in the next snapshot
for my changes to NonUniformItem; I'm going to ignore the LUT stuff for the time being; I'm making enough changes as it is in this PR 😅
I would certainly love to normalize setLookupTable and setColorMap throughout the library to have the same function signatures; but the scope of the nonuniformitem PR I'm working on has already grown.
I'm largely emulating a lot of what you did w/ PColorMeshItem
from PySide2 import QtCore, QtWidgets
import pyqtgraph
import sys
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.imv = pyqtgraph.ImageView()
self.view_box = self.imv.getView()
self.setCentralWidget(self.imv)
self.scale_bar = pyqtgraph.ScaleBar(5, offset = (-30,-30))
self.scale_bar.setParentItem(self.view_box)
def remove_scale_bar(self):
print('############ removing ###########')
self.view_box.removeItem(self.scale_bar)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
QtCore.QTimer.singleShot(1000, main_window.remove_scale_bar)
sys.exit(app.exec_())
Here you go 🙂
There is indeed a bug in ScaleBar (and also LegendItem), It assumes setParentItem is not being used to remove the parent.
@fervent vale brilliant use of np.pad
loving all the comments in the NonUniformItem.generatePicutre() ...thanks so much for adding those!
https://bugreports.qt.io/browse/QTBUG-74407
looks like there is planned work to move the QRhi abstraction layer to QPainter; ... unfortunately from the looks of things there hasn't been a lot of work here recently 😦
@fervent vale I'm curious how much (if any?) performance speed up there would be with moving these nested for loops into a list comprehension
polys = []
for r in range(nrows):
for c in range(ncols):
bl = points[(r+0)*(ncols+1)+(c+0)]
tl = points[(r+0)*(ncols+1)+(c+1)]
br = points[(r+1)*(ncols+1)+(c+0)]
tr = points[(r+1)*(ncols+1)+(c+1)]
poly = (bl, br, tr, tl)
polys.append(poly)
to
polys = [
(
points[(r+0)*(ncols+1)+(c+0)],
points[(r+1)*(ncols+1)+(c+0)],
points[(r+1)*(ncols+1)+(c+1)],
points[(r+0)*(ncols+1)+(c+1)]
) for r in range(nrows) for c in range(ncols)
]
I'll be the first to admit it's not as readable, but list-comprehensions are usually a bit faster than the generic for loops
actually, i can benchmark that myself after I get the kids to bed...
at least for the use-case demonstrated in the example, that gets invoked once only
oh, that's not worth it then
actually it's already quite confusing to me... there's the X,Y axis transposition. I am not even sure if bl,br are bottom and tl,tr is top
in the scipy ascent image display example, the image has to be transposed to be displayed correctly
at least in the double for loop, it's explicit what the programmer's intent (rightly or wrongly) of br, tl, br, tr was
actually, in the case of polygons, it doesn't really matter, provided the points have a line connecting them?
there's clockwise and anti-clockwise
for OpenGL it matters
it determines the direction of the normal
ahh, makes sense
for NUImg, it's clear that setLookupTable(HistogramLUTItem) is the wrong api to expose
for PCMI, it's not so clear that setLookupTable() shouldn't be able to take either an ndarray or a list of QColor
Is it that difficult to support both? We eventually need to convert the colors to QColor instances anyway
it's simpler to support both
it's more a question of whether there should be consistency across the various image-like items
using ImageItem as the "standard" may be the wrong thing to do too, beause ImageItem predated ColorBarItem
Ugh yeah kind of stuff is tricky; happy to support what is easy until complains/contributions come in to expand functionality
should I revert the changes I made to ColorBarItem then? let it "know" that PCMI prefers list of qcolor
having setLookupTable support ndarray lets it potentially inter-operate with HistogramLUTItem
ok finally got the kids to bed..sort of... ok, let me peak at your diff for colorbaritem
PCMI natively prefers a list of QColor. Having ColorBarItem pass it an ndarray just makes PCMI convert it back to list of QColor. However, setting colormap is likely to be once off.
i'm indifferent about it, but was just wondering if this is the sort of thing that we could make cleaner w/ the fancy switch syntax that python 3.10 introduces
that doesn't answer your question, I'm honestly indifferent about it; I haven't heard any gripes on performance on these items, so I suppose I would opt for the simplest use-case
ok, I will limit this PR to not "fix" any api. (besides remove the deprecated cmap argument)
can you think of a better way of reassigning the methods from ViewBox to PlotItem?
## Wrap a few methods from viewBox.
#Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive
#because we had a reference to an instance method (creating wrapper methods at runtime instead).
for m in ['setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', # NOTE:
'setAutoVisible', 'setDefaultPadding', 'setRange', 'autoRange', 'viewRect', 'viewRange', # If you update this list, please
'setMouseEnabled', 'setLimits', 'enableAutoRange', 'disableAutoRange', # update the class docstring
'setAspectLocked', 'invertY', 'invertX', 'register', 'unregister']: # as well.
def _create_method(name):
def method(self, *args, **kwargs):
return getattr(self.vb, name)(*args, **kwargs)
method.__name__ = name
return method
locals()[m] = _create_method(m)
del _create_method
the code-checker doesn't recognizing that these methods exist in PlotItem.
I don't even understand the comment about keeping viewbox alive
why would creating it at runtime make a difference?
I don't know; but keep in mind this comment/code was written a long time ago, python behavior could have changed since then...this was likely written for python 2.6
i think the comment implies there would be an extra reference hold on the viewbox with using setattr
ok, maybe I get it. the inner method captures "self", but "self.vb" is dereferenced at runtime. so there's no extra reference to the viewbox held
what do you think of having a setData method to NonUniformImage? ... not that I expect this plot type to be rapidly updated or anything...
(I'm handling the merge conflicts now on my PR 2614 now that I merged your changes)
it's useful even if the data is only ever updated once. right now, it wouldn't be possible to build an app that loads data to be viewed
there's some scope creep. if you add setData(), do you also need to inform HistogramLUTItem and ColorBarItem?
HLI looks for a 'sigImageChanged'
don't know if CBI works
i can certainly emit that easily enough
yeah i'm not sure either; I'm starting to fall asleep so Im going to call it a night, thanks for making the changes to NUI and PCMI
btw, the coords that the user provides to NonUniformImage are not the pixel centers. E.g. given x coords 1,5,11, the center polygon will have left edge 3 and right edge 8, which is centered at 5.5
Ugh eventually I’ll get that ascii art right
it is correct that the user is supposed to pass the coords of their sampled data
however it is wrong to interpret that the drawn polygon has the sampled data coord as its center
we are using the shading of the rectangles as a rendering of "nearest" interpolation
it's counter-intuitive... especially when there aren't many data points to begin with
Maybe I should do away with the ascii art haha
so it's non-uniform sampling data points, but it's not non-uniform rectangular pixels
@fervent vale if you have a screenshot of a pyqtgraph based application you develop professionally, that you would like to include in a poster board session at the SciPy conference, please share it with @mortal grotto ...he'll be there next week
If I did have such an app, I am sure I wouldn't be permitted to showcase it
Fair enough!
I feel like I'm doing something really silly;... I'm collecting benchmark data using a slight variant of MultiPlotSpeedTest.py but I can't seem to show any difference with downsampling.
I set it explicitly in the code:
plot.getPlotItem().setDownsampling(ds=True, auto=True, mode="peak")
oh; setting it further down the line...
Traceback (most recent call last):
File "C:\Users\ogi\source\repos\pyqtgraph\pyqtgraph\graphicsItems\PlotItem\PlotItem.py", line 945, in updateDownsampling
c.setDownsampling(ds, auto, method)
^^^^^^^^^^^^^^^^^
AttributeError: 'PlotCurveItem' object has no attribute 'setDownsampling'
welp, that might explain it!
sure enough, changing my curve from being a PlotCurveItem to a PlotDataItem was sufficient
The recent searchsorted fix was helpful in getting good performance with down sampling. I recently had to plot 10e6 samples and it made a big difference
I had to manually patch my copy of pyqtgraph though. It was on a Windows 7 system which limits it to Python 3.8
yeah 10e6 is where things started getting great in our testing
chart
with the improvements you made pijyoi, I was able to test far more combinations than the last time I did this...
and now, exporting to SVG, I see a bug in my SVG fix from before 🤦♂️
ok bedtime....
Oooof… sorry that our adherence to nep-29 is causing you trouble. Don’t hesitate to ask for a release before I remove support for a version of something
Well, can't be blaming anyone for still using an EOL OS!
haha, true; still don't mind doing stuff like that if it helps you...
you've done a lot of work w/ PlotCurveItem, do you think that the curve downsampling functionality should be migrated to that object from PlotDataItem?
Downsampling also affects ScatterPlotItem
this is the best opening image ever: https://pypi.org/project/picachooser/
redid the asv image benchmarking..now using the fancy parametrization asv offers
Cuda takes precedence over numba.
ahh; good to know that there's no point in doing both 😅
easy fix!
alright, going to bed, queued up a run of all the sizes...that should give me the data I need to regenerate the plot
this is interesting; cupy is slowest by far with a uint16 dtype, no LUT, and use_levels=False
screenshot of table, I need to get into a spreadsheet/pandas dataframe but now I need to do the job that pays me money 😄
it's a bit dissapointing that asv doesn't make the raw results easier to access, no csv; ... there is a json file, probably would a little time to write a parser for it...
For cuda mode, PlotSpeedTest is benchmarking the scenario where the user image data initially resides on gpu memory
For uint16, no lut and no levels, the image data can be immediately wrapped into a GrayScale16 QImage with no processing
So cuda mode for this case does nothing besides transferring the image data from gpu memory back to cpu memory.
This transfer rate is occurring at 4GB/s on your test machine based on your graph
Numba doesn't kick in and is the same as Numpy for this case.
If your gfx card was PCIe 3.0 16x lanes, an expected transfer rate from gpu to cpu would be ~10GB/s
hmm... my GPU is on a PCIe 4.0 with 16x lanes ...
this isn't using PlotSpeedTest; but in renderImageItem.py
https://github.com/pyqtgraph/pyqtgraph/blob/master/benchmarks/renderImageItem.py#L18-L23
(I should create a PR to update this config with what I'm running locally)
benchmarks/renderImageItem.py lines 18 to 23
def renderQImage(*args, **kwargs):
imgitem = pg.ImageItem(axisOrder='row-major')
if 'autoLevels' not in kwargs:
kwargs['autoLevels'] = False
imgitem.setImage(*args, **kwargs)
imgitem.render()```
makes sense, any suggestions on how to rework the benchmark to take that into account? (just created a PR with my working config) ...or I guess, there would be no taking it into account, as if I have cuda array that I want to draw with no levels and no LUT, it's going to get transferred to regular memory no matter what :/
On further reflection, I think I'm going to skip that use-case (cupy + no lut + no levels), as you said, only thing it benchmarks is transfer speed from GPU memory to CPU memory
So if you are using renderImageItem.py, then you are measuring the round trip cpu- gpu-cpu and you obtained a transfer rate of 8GB/sec
why would it be a round trip? I pre-generate the relevant arrays (cp.array or np.array) and cache them prior to the benchmarking:
just realized I had uint16 LUTs, not uint8 ones 😬
I work w/ the image stuff of the library so little, I'm glad I'm doing this benchmarking... normally I'm so confused by the image stuff 😂
So if there's no round trip, then 4GB/s on PCIe 4.0 16x is only about 20% of (practical) expected
HistogramLUTItem returns 512-entry LUTs if the image data is not uint8. This bumps up the LUT to uint16
i don't typically evaluate performance on the hardware that much but sure enough, bus interface is PCIe 4.0 x16
mentioning the LUT as I saw this: https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/graphicsItems/ImageItem.py#L568-L572
pyqtgraph/graphicsItems/ImageItem.py lines 568 to 572
warnings.warn(
"Using non-uint8 LUTs is an undocumented accidental feature and may "
"be removed at some point in the future. Please open an issue if you "
"instead believe this to be worthy of protected inclusion in pyqtgraph.",
DeprecationWarning, stacklevel=2)```
There's some potential for confusion here
- the number of entries of the lut
- the bit depth of each entry of the lut
The warning refers to (2)
ok, yeah, I specified the bit-depth of each entry of the lut in the test (originally) as uint16;
In the benchmarking, uint16 lut refers to (1)
oh?
Let's take a float type image. HistogramLUTItem will provide a 512-entry lut
oh oh oh
I see what's happening
i mis-read my own code (or I should say, I misread Martin's code I copied/pasted)
So float first gets releveled to a 512 indexed image, which means it becomes uint16
Then this uint16 indexed image is replaced with the lut entries to become an RGB uint8 image
It's possible to have luts other than 256 or 512 entries
do you think there is value in benchmarking size of LUTS that are both uint8 and uint16? or should I just benchmark the uint16 case (and the None case)
256 entry is the more common value used for colormaps. But 512 entry is the one that people will encounter while using ImageView widget with float data
easy enough to do both
But calling them 8-bit or 16-bit is a big source of confusion
lut_length ?
You could even pass in a 70_000 entry lut and then ImageItem should create an uint32 indexed interim image
Or perhaps it doesn't
Of course, in Qt6, there's support for RGBA64 QImage. That would be the real 16-bit depth
Thanks for the comments on that PR
this is new... right click and select export, no dialog comes up, but get this in the console:
XXX lineno: 135, opcode: 149
XXX lineno: 313, opcode: 151
Error in sys.excepthook:
SystemError: unknown opcode
Original exception was:
SystemError: unknown opcode
XXX lineno: 148, opcode: 150
XXX lineno: 313, opcode: 151
Error in sys.excepthook:
SystemError: unknown opcode
Original exception was:
SystemError: unknown opcode
XXX lineno: 58, opcode: 150
XXX lineno: 313, opcode: 151
Error in sys.excepthook:
SystemError: unknown opcode
looks like I was doing something silly w/ my instantiation of QApplication
here is the recreated plot on my machine from the first paper; it's remarkable how well numba fits between cuda and pure numpy performance wise.. also remarkale how little impact having a LUT has (if you have levels) once your images get large
pijyoi. as likely super obvious I haven't looked at the imageitem code much, but if I am following things correctly before, historically, almost all calls to ImageItem.setImage() would eventually get routed through makeARGB; but over recent years; you've implemented various code optimization paths that uses input conditions that are suitable for direct rendering, and sets the format accordingly, bypassing...
now, the only time makeARGB is called is when _try_make_qimage returns None, meaning we have a non-supported configuration
in your comment on the asv PR, you mentioned that hopefully none of the parametrized inputs would call makeARGB, but if I'm reading this right, wouldn't any combination that uses levels or LUTs with 3 channel data, regardless of dtype, cause makeARGB to run?
Right, 3 channel data with levels is not short circuited. But I think the asv tests did not test this case to begin with. I am not sure what operation multichannel data with LUT is supposed to represent, or if it is even supported by makeARGB.
By default QImage uses ARGB32 format internally, so that's probably why we have a makeARGB. But that's also the cause of the additional complexity in handling endian-ness
Oh that’s right, that asv test only does 1 channel data
test_ImageItemFormat.py enumerates all the short circuited cases
From there I see that a 3-channel uint8/uint16/float32 image with a single levels for all 3-channels and no lut does get short circuited
It's not that 3-channel data with their individual levels couldn't be short-circuited. It was a matter of optimising only the more common cases. You can see that there are enough combinations as it were.
I think 3 channel data with individual levels couldn't be made fast if the processing code were to handle each channel separately. Unfortunately, handling them in one pass could only be done in numba
From your timings chart, numpy is processing at 500 megapix/sec, while numba is processing at 1000 megapix/sec
Numba is able to do the rescale operation in 1 memory pass
appreciate the context/explanations 👍
looking through the docs, RGBA64 was added in Qt 5.12?
oh wait, we're using RGBA64 format in the repo already...
oh, but what's new in Qt 6.2 is Format_RGBA32FPx4
According to test_ImageItemFormat.py, if the user passes in an RGBA64 image with no levels and no lut, then it gets wrapped into RGBA64 with no processing.
Similarly, uint16 single channel with no levels and no lut gets wrapped into Grayscale16
yeah; got that part ... was thinking once I clean up the asv code I may make a PR attempt to implement Format_RGBA32FPx4 since I'm already looking at this
If any processing takes place, the final output will be of dtype uint8. We don't output a higher bit depth
I think normally for floating point output, whether for image or for audio, the data is expected to be normalized to (0.0, 1.0) and (-1.0, 1.0) respectively
So if the user were to pass in 4-channel floating point data, to wrap and pass it through with no further processing would require the input data to be pre-normalized to (0.0, 1.0)
yeah, I would imagine (0, 1)
when I clean up the ASV stuff and am ready to merge it, I'll poke at the float32 format as see what happens when you pass values outside of that range
and furthermore, it doesn't make sense to apply levels to the alpha channel
...yeah not sure what the expected behavior there would be for that 😬
It might have been useful to have a GrayscaleFP32 format
but less so for RGBA32FPx4
for scientific data, that is
I should poke at Luke and see if he deals w/ those formats ever, ... he's knows that neuroscience imaging equipment pretty well...
if you are thinking of monochannel input being processed to RGBA32FPx4 output, that would only make sense if the LUT colormap was in float
in audio data, yeah, I deal w/ single channel data values, and LUTs and Levels
but you would end up with a QImage that's 4x the size of your original mono-channel input
I think I'm still missing something here; so in ImageItem._try_make_qimage() it's looking at attributes of the incoming data, levels, lut and trying to see if there is a QImage format that can be used, where we can create a QImage from the data directly, avoiding any kind of processing; ...
what I'm missing here is why wouldn't it be beneficial to add checks for the RGBA32FPx4 format; see if the incoming data conforms to that, and if so, set the format to that, and generate a QImage that way (like with RGB888, Grayscale8, Indexed8 and so on
RGBA32FPx4 has stricter requirements than uint8 and uint16. The data has to be pre-normalized to (0, 1)
ahh, so issue is w/ the other formats, we're effectively bound by the limit of values that can be represented, so that pyqtgraph can effectively cover all the cases w/ those data formats...
In the current pyqtgraph API, there is a requirement that float data comes with levels
oh, yeah, that too 😆
So, if the user just so happened to have a fully-compatible RGBA32FPx4 data array, they can't communicate this fact to pyqtgraph
got it, that makes sense
In fact, that's true for mono-channel float too. The user can't communicate that they have already pre-scaled to (0, 1)
wonder if we can put in the docs to use levels=[float("nan"), float("nan")] to indicate that data is pre-scaled accordingly.
feels weird and out of place, guess this is just a solution looking for a problem as this hasn't been requested by anyone
the float needs levels requirement is in makeARGB
it could possibly be changed to "levels is None for float means (0, 1)", but that could just end up being more surprising behavior
yeah, i'm convinced that absent a good use case that this isn't a good use of time/effort.
looking at test_ImageItemFormat.py, for LUTs that have <= 256 entries, the code will output an Format_Indexed8 image
This results in much lower memory usage than an RGB[AX]8888 image
In the first timings table that you posted on 2023/07/13, you will see that small LUTs have a significant timing advantage over big LUTs
The thing is that HistogramLUTItem defaults to giving big LUTs (512-entry) for float images
So for someone using ImageView to interact with their data, they don't get this small LUT speed advantage
You think we should change the default LUT entries for HistogramLUTItem ?
@elder eagle thanks for providing help for folks asking pyqtgraph questions in the help channel; every few weeks I remember to search this server for mentions of pyqtgraph outside of this channel...
if someone asks a tougher question, feel free to direct them here 👍
Maybe the question to ask is: under what situations would it be advantageous to use a 512-entry LUT vs a 256-entry one. The CET and the matplotlib colormaps all come as 256-entry
I don't know the answer to this one, but this code is pretty old, and likely Luke would have an answer (if one exists)... he'll answer on slack when pinged (eventually)
VideoSpeedTest even asks for a 4096-entry LUT
That…. Seems excessive
Hi, I have a question on the GUI side of things. I noticed that when I have a HistogramLUTItem connected to an ImageItem I can click on the ticks to bring up a colour selector to change the colour on the tick (which is a great feature btw). If I then click somewhere else and the colour selector window ends up behind my program, then it's not brought forward by clicking on the tick again. Is this intentional, and, in that case, are there any workarounds you can think of?
Adding self.colorDialog.raise_() to the end of GradientEditorItem::raiseColorDialog() seems to achieve what you want.
Awesome, thanks!
I am working on a HistogramColorMapItem that fills a gap not served by HistogramLUTItem nor ColorBarItem. In scientific images, it is useful to see a histogram so that the user can adjust the levels to remove the noise or background. ColorBarItem doesn't have that and HistogramLUTItem assumes gradients with small number of stops rather than 256-entry colormaps
The ability to edit the gradient on-the-fly isn't needed
TIL about Glumpy, a numpy <-> opengl plotting library
wonder if there is a collaboration to be had or things we can 'borrow' from each other. Author is fairly active on social media
hmm... the images look to be the same as vispy?
I tried out glumpy maybe 10 years ago. I think it was to do a pcolormeshitem.
pcolormeshitem can be really fast on opengl
the pyqtgraph one is relatively slow
I like how it seems to handle a variety of non-linear transformations
back then I was using wxPython
In preparation for an upcoming move, we have started doing “bucket list” items on weekends with the kids; going to be slow to review/merge stuff.
filed a support request w/ github to take a look and see why github doesn't identify pyqtgraph as being used as a dependency on any library (hence we don't have a "used by" counter)
The discord button link reports "invalid server"
Saw that, when I tried clicking on it, it seemed to still work?
used by section in our project page now works!
hmm, CodeQL doesn't like my (first) use of the walrus operator?
I’ll try and take a look closer today. Generally i view CodeQL warnings/errors as things I should take a closer look at.
if (cmap := get_cmap()) is None:
cmap = default_cmap
grad = cmap.getGradient() # cmap potentially not initialized
heh, i think before the SVG fixes, using QGraphicsRectItem would result in a bad SVG export, but because ColorBarItem used ImageItem under the hood, that's why the SVG export worked there (I in theory have fixed that now so it should be a non-issue now.
ColorBarItem uses 256 as its 100% full scale range, which I think seems to be partially caused by the use of ImageItem
You will find these values sprinkled in the code: 256, 63, 191, 64
what is 191 supposed to represent?
It's about 3/4 of 256
I think it's tricky. If 1.0 maps to 255, then what do 0.25, 0.50 and 0.75 map to?
In pyqtgraph, when rescaling, each lut entry gets an equal proportion of the pie. E.g. in a 4-entry LUT, 0..0.25 maps to 0 while 0.75..1 maps to 3
What exactly is the difference between a lut and a gradient. My understanding is that is LUT is a color map with points on the scale mapping to specific colors, and values between adjacent points in the LUT have their color interpolated. Does a gradient just have fewer “fixed” points?
Or with the LUT is there no interpolation?
For a LUT, the data is quantized to the number of entries of the LUT and then mapping occurs with no interpolation
For a gradient, the data is not quantized and each data point is smoothly interpolated to the stops defining the gradient
pg.ColorMap internal structure is a gradient
You can ask it to give you a LUT from the gradient
However, all the on disk colormaps are defined with 256 entries, which get loaded by ColorMap as a gradient with 256 stops
I'm getting it, I don't have a clue of what should be the preferred or default behavior tho, I just don't do that much scientific work w/ images.
taking a peek at stackoverflow, who is relent95 that's answering so many pyqtgraph questions I wonder
It seems like HistogramLevelsItem would fit this use case
when chatting w/ Thomas (mpl maintainer), sounds like they just do LUTs, no interpolation
QGraphicsPixmapItem; ...TIL...didn't know that was a thing 😂 probably the right item to draw for your PR
getting a surprising amount of activity (not a lot, but more than none, hence surprising) with the #pyqtgraph hashtag on mastodon
Is the following True or False?
cupy.zeros(10).dtype == numpy.float64
thanks. otherwise there would have been difference in behavior if the user (unknowingly) received an cupy array and wanted to check whether it was a certain type.
there is such a code in HistogramLUTItem::getLookupTable
hey i jus finished a bootcamp course in python from udemy what should i do now to move ahead?
Not sure what channel is more appropriate for that question, but probably not this channel, as this channel is specific to the PyQtGraph project
alright
where could i learn pyqt6
any youtube videos?
I don’t vouch for it, but there are a number of tutorials on pythonguis.com I know they have books there too (again, don’t know if they’re any good)
pythonguis is good
that's from where I learned pyqt6
@fervent vale looks like there is movement on the pypy support in pyside https://bugreports.qt.io/browse/PYSIDE-1991
I must say, it's only after working on #2779 that I am now understanding pyqtgraph's UML inheritance diagram. I don't think I understood before the difference between GraphicsWidget and GraphicsObject
haha it's so handy, right?!
this was a constant sore point with me, so when it got proposed, I knew it had to merged, even if the diagram was not generated in CI, but pre-generated in a proprietary application
Hi @rough furnace, I just noticed that the link in the readme to this discord server is broken. Thought I’d let you know
hey there, thanks for pointing that out. I did see that, but as far as I could tell, the link worked; but as I'm already on this server it's tough to tell, I suppose I could try leaving the server and see if that link brings me back here and to this channel...
@fervent vale multiple inheritance has never worked for us with pypyside, correct?
on that issue, Christian Tismer is asking a question which implies that they think that issue was resolved at some point... but if memory serves from your CI runs, it never worked in our use-case?
LHS inheritance has crashed PyPySide since its first release
The fix at the time was to switch GraphicsObject and GraphicsWidget to inherit GraphicsItem on the RHS
On the other hand RHS inheritance does not allow method overriding in {C,Py}PySide
So if a Qt class has a virtual method, RHS inheritance will not override that method
Apparently, PyQt supports method overriding with inheritance on the RHS
But that feature is contrary to python method resolution order
just so I'm clear, LHS inheritance would be where the Qt object we're inheriting is on the left?
class MyObject(QObject, object)
or wait, I have it backwards...
Python Mixin on the left. Qt class on the right
According to the last section of the above link, L and D colormaps should have been named with 2 digits, with a leading 0 if necessary
Our files are named without the leading 0
Ahh. Should git mv them then…
In fact, we can't rename them either, because the well known colormap name (e.g. "CET-L2") is derived from the filename
o....yeah that won't work then 😬
Could have something to do with the recent change to i vite links from discord. It should not change anything, but just creating a new unlimited invite and putting it in the docs, shouldn‘t hurt.
Discords api is a mess…
Maybe! I’ll update it later today.
perhaps we should consider different default parameters for line plot update benchmark:
greater than 2,000 fps isn't a particularly meaningful number 😂
Hello #pyqtgraph ,
I would like to use PySide2 with python 2.7, however Pip install requiert higher Python version.
I have being told to build Pyside2 for python 2.7 but I have no idea how to do.
Does anyone can explain me how to do ?
Am I on the correct channel ?
We can’t help with building of pyside packages; although I thought some versions of pyside2 supported Python 2.7 so I’d be surprised if you had to build from source
Do you know any guide on how to build packages from source ?
The pyside wiki gives instructions but if you’re looking for an older version for Python 2.7 you may have a hard time finding it
Thanks ! I will give a look
@fervent vale I saw your comment; I’m good with a rename of the existing color map to gradient, throw a warning in the docstring or something like that. Need to review that PR more closely for sure.
1 behavior changing decision: change HistogramLUTItem to return 256-entry LUT instead of 512-entry?
The change can also be made in ImageItem to explicitly request 256-entry LUT
<@&267628507062992896> yo, please nuke the above ☝️ (EDIT: thank you!!)
realized I tagged the wrong group, my apologies
pijyoi, I finally added your fork to my remotes on my new laptop, saw you had a now several years back for trying to adopt modern opengl stuff; would that be an effort you would like to continue w/ some collaboration?
(also going through your color map display item PR now)
Looking at the code, I was trying to learn some trivial modern opengl to see if the RawImageWidget could run on OpenGL ES on the raspberry pi.
I briefly looked at OpenGL ES, but ruled it out as I had the impression it was aimed more for mobile platforms, didn't consider the raspberry pi; which I think there is a good argument to try and support hardware acceleration there
Actually the 64-bit distros for rpi come with Qt compiled for OpenGL (not ES)
i've been slowly going through https://learnopengl.com I am hoping that we can use it to handle non-linear transformations
I think using opengl native painting will eventually stop working on Qt?
I would be surprised if that got removed from Qt, but I did see a comment discussing QRhi support for QPainter, which is apparently being worked on, would a lot like the current OpenGL QPainter support
oh, but if 64-bit distros of RPi come w/ Qt compiled for OpenGL, then yeah, we can stick to OpenGL 3.3-4.1
The real "meat" of the colormap PR is ColorMapMenu and ColorMapDisplayMixin
They reside in widgets/ColorMapButton.py
They can be used to build a colormap menu for use in other places
Like for ColorMapDisplayItem (which could actually be removed from the PR)
If the old gradients are to be considered "legacy", I would suggest that the new "turbo" colormap to not be included in gradient form.
Had to put the review on hold. With repurposing names the one concern I have is we have no warning or deprecation period assigned. I need to review more closely to better evaluate what is most appropriate
(Put on hold due to taking my daughter for a walk on the beach).
So what's the preferred method of showing a ColorMap as a GraphicsItem, I guess that would be ColorBarItem? Right now the PR shows PColorMeshItem using ColorBarDisplayItem, and NonUniformImage uses HistogramLUTItem;
For image applications where a histogram is visually useful to interactively adjust the levels, HistogramLUTItem is still the item to use
All existing uses of HistogramLUTItem continue to work but now have access to the lut colormaps. Including those from mpl and colorcet
ColorBarDisplayItem is more of an example of how else ColorMapMenu could be used
For bigger applications that include use of a parameter tree, the colormap could be selected from the ColorMap parameter
At the moment I don't have applications where ColorBarItem would be a good fit
But a HistogramLUTItem without the ability to adjust the stops is usually what I need
If taking over the "colormap" parameter is not a good idea, it can be reverted. And use a better name than "colormapex"
i think it's fine
test
what are you testing?
sorry i forgot to delete - it was someone automating their account and they're now banned
haha it's ok, figured it was something like that, I was mostly trolling 😛
if I'm reading about geometry shaders right; looks like we can handle non-linear transformations that way? likely can't handle arbitrary transformations, but can pre-define them that way?
i guess you can say I'm now an opengl expert /s
pijyoi, I think you're right to target RawImageGLWidget for testing the waters w/ modern opengl tho
also, to get more opengl benefit, ideally (ignoring the amount of work involved) wouldn't we want to move makeARGB computation to the fragment shader?
lastly, I'm not sure there is much benefit for keeping support for opengl < 4.1; opengl 4.1 has been out for a long time. On apple machines, macOS 10.9 added support for OpenGL 3.3, 4.0, and 4.1; I'm fairly sure you cannot get a pyqtgraph supported version of numpy on OS X 10.9 (not without substantial work anyway).
probably should see what version of linux added support for OpenGL 4.1
looks like OpenGL 4.1 is supported on Nvidia Geforce 400 series cards, AMD 5000 series cards (so looks like my nvidia Geforce GT 330M in my mid-2010 macbook pro wouldn't get the cut 😂 , it only goes to 3.3)
not sure what the situation is on more embedded platforms like the Raspberry Pi
makeARGB() was created at a time when there wasn't an Format_Indexed8
not sure how that helps for RawImageGLWidget tho, in the paintGL method there is an explicit call to fn.makeARGB(img, *args, **kwds), and the output from that gets eventually relayed to
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.image)
suppose we could "fastpath" stuff here by having some codepaths that match up w/ the texture arguments
o, the format options aren't particularly helpful to us, only one that likely impacts us is GL_RGBA anyway...
One slowdown is in the output of ARGB32 format
The generic code to make populate the numpy array to make ARGB32 is slow
this could actually be done by converting the cmaplut to ARGB
then the output of np.take would already be correct
(or in this case, RGBA)
actually makeARGB() could be entirely removed in favour of makeRGBA()
there are 2 useful formats for OpenGL Texture
RGBA and the single channel format
but really, who's using RawOpenGLImageWidget?
nobody 😄
but figure its a good place to tinker w/ modern opengl stuff and see if there is much benefit in trying to modernize the opengl stuff elsewhere
for context, I cherry-picked the first commit on your modern-opengl branch
Not seeing anything about raspberry pi supporting non-ES versions of OpenGL, not sure if we should try supporting OpenGL ES just for the Raspberry Pi tho... then again, I have no idea what we give up from going from standard OpenGL to OpenGL ES
Wrong channel
The RPI3 hardware supports both OpenGL 2.0 and ES 2.0. What matters is whether the Qt library is compiled for Desktop GL or ES. There was also the issue of the OEM graphics drivers being 32-bit. So on 64-bit distros, Qt is compiled for Desktop GL. I am not sure whether GL is running software emulated in that scenario though
Do you know of an easy way to check if it’s software emulation or actual hardware acceleration? I don’t have a RPi 3 or 4, but I can likely track one down for testing
I think the info that glinfo.py prints out would tell you. Specifically the GL_VENDOR and GL_RENDERER
I see that the turbo cmap and gradient got merged. Would it be right to say that gradients aren't considered "legacy"?
I really need to get to making that user survey, I honestly have no idea how often parts of the library are used. I suspect acq4 uses gradients … but likely for legacy reasons
isOpenGLES: False
VENDOR: Mesa/X.org
RENDERER: llvmpipe (LLVM 11.0.1, 128 bits)
VERSION: 3.1 Mesa 20.3.5
Raspberry Pi OS 64-bits on an RPI3 over an ssh connection (X11 forwarding). Using distro provided Qt and PyQt5 5.15.2. We can see that Qt is compiled for Desktop GL. But OpenGL is emulated because of ssh.
Too much of a hassle to plug in a monitor and keyboard.
When connecting over VNC
isOpenGLES: False
VENDOR: Broadcom
RENDERER: VC4 V3D 2.1
VERSION: 2.1 Mesa 20.3.5
hmm.... in that case I think we should probably preserve the current OpenGL implementation which works on OpenGL 2.1, and add a codepath for "modern" GL (4.1?) ... there are very few devices out there that support 3.3, 4.0 but not 4.1 (but they do exist)
Modern OpenGL refers to >= 3.3 right?
An old 2014 thread where Vispy was the way going forward
I was thinking back on the issue involving font lettering in OpenGL being not as sharp as the non-OpenGL variant, seems like most recommendations are to use FreeType, ... which seems like that's what matplotlib depends on as well...
I think that was because the non-OpenGL engine knew how to use sub-pixel rendering?
Yeah sounds about right, we may not be able to use quainter::drawtext
When using the OpenGL renderer
pijyoi, maybe you know this, but I can't understand what would be the purpose of using the QOpenGLWidget/QOpenGLWindow but using paint() methods instead of paintGL() ... I know with paint() you can reuse the non-opengl draw code, but why would anyone want to do that?
I suppose GLPainter.py example would be one use case. Do all the OpenGL rendering followed by a single over-painting pass.
just like last time you posted here, this is still the wrong channel
if you need help, take a close read at #❓|how-to-get-help
what is bro saying
imagine pyqtgraph+shader code
sort of working on that now ....
literally, in my text editor right now:
just getting a sense for how deep the rabit hole is for thick lines in "modern" opengl, @torn minnow do you have much familiarity with working with opengl?
i'm right now going through this GitHub looking at how to generate thick lines: https://github.com/mhalber/Lines
Don't want to use the CPU based method due to by far the slowest implementation, I can't use the SSBO since that's a OpenGL 4.3+ feature (and with macOS I'm stuck on OpenGL <=4.1) which leaves the Geometry Shader Lines, the Instancing Lines and the Texture Buffer Lines methods; anyone here think that we should lean towards one method more than another?