Git Product home page Git Product logo

Comments (7)

pijyoi avatar pijyoi commented on May 26, 2024

I would guess that #2863 is the one affecting you.

  1. Are you able to bisect the code to confirm that #2863 is the relevant PR affecting you?
  2. Are you using a hi-dpi display? If yes, in the following lines, if you change self._dpr to your screen device pixel ratio, does it make a difference?
    def __init__(self):
    self._dpr = 1.0
    self.clear()

ScatterPlotItem starts off assuming DPR=1.0, and if that turns out to be incorrect, it has to rebuild the symbol cache. Effectively, this means that the time to first render should be slower for users with a hi-dpi display.
Now, pyqtgraph library could perhaps try to probe for the primary screen's DPR and use that. That, however, is not entirely foolproof, as the user may have multiple screens with different DPRs.

Indeed, ScatterPlotItem does not know the screen on which it will display until just before painting occurs. And furthermore, even after being displayed, the user could drag the window to the other screen.

dpr = self.fragmentAtlas.devicePixelRatio()
if widget is not None and (dpr_new := widget.devicePixelRatioF()) != dpr:
# force a re-render if dpr changed
dpr = dpr_new
self.fragmentAtlas.setDevicePixelRatio(dpr)
self.fragmentAtlas.clear()
self.data['sourceRect'] = 0
self.updateSpots()

from pyqtgraph.

aarpon avatar aarpon commented on May 26, 2024

Thanks for the quick reply!

I am indeed on a hi-dpi display and that may explain the slow down on my machine. In general, I am supporting users on Windows, Linux and macOS with any number of screens, so I would indeed need to check for the DPI before I plot.

I will check that the pull request you mention is indeed the one that affects the rendering performance on my machine and come back to you asap.

from pyqtgraph.

pijyoi avatar pijyoi commented on May 26, 2024

The following script takes < 2.5 secs to run on a particular system with hidpi.

import time

import numpy as np
import pyqtgraph as pg

npoints = 200_000
nbrushes = 600

rng = np.random.default_rng(0)
xy = rng.standard_normal(size=(2, npoints))
unique_colors = rng.integers(256, size=(nbrushes, 3))
unique_brushes = [pg.mkBrush(*rgb) for rgb in unique_colors]
random_choice = rng.integers(nbrushes, size=npoints)
random_brushes = [unique_brushes[x] for x in random_choice]

pg.mkQApp()

scatter = pg.ScatterPlotItem(
    size=5,
    pen=pg.mkPen(None),
    brush=pg.mkBrush(255, 255, 255, 128),  # To be overridden in the next call depending on a setting
    hoverable=True,
    hoverSymbol="s",
    hoverSize=5,
    hoverPen=pg.mkPen("w", width=2),
    hoverBrush=None,
)

t0 = time.perf_counter()
# scatter.addPoints(x=xy[0], y=xy[1], brush=random_choice)  # SLOW
scatter.addPoints(x=xy[0], y=xy[1], brush=random_brushes)   # FAST
t1 = time.perf_counter()
print(f'{t1 - t0:.3f}')

pg.mkQApp()
win = pg.PlotWidget()
win.show()
win.addItem(scatter)
pg.exec()

The following should explain why:

def _mkBrush(*args, **kwargs):
"""
Wrapper for fn.mkBrush which avoids creating a new QBrush object if passed one as its
sole argument. This is used to avoid unnecessary cache misses in SymbolAtlas which
uses the QBrush object id in its key.
"""

from pyqtgraph.

pijyoi avatar pijyoi commented on May 26, 2024

set PYQTGRAPHPROFILE="ScatterPlotItem.paint"

SLOW version (with dpi mismatch)

9.933
> Entering ScatterPlotItem.paint
  prep: 11133.5924 ms
  draw: 164.4934 ms
< Exiting ScatterPlotItem.paint, total time: 11298.1008 ms

FAST version (with dpi mismatch)

0.710
> Entering ScatterPlotItem.paint
  prep: 369.4633 ms
  draw: 47.4142 ms
< Exiting ScatterPlotItem.paint, total time: 416.8903 ms

FAST version (with dpi matched)

0.709
> Entering ScatterPlotItem.paint
  prep: 31.1583 ms
  draw: 48.1068 ms
< Exiting ScatterPlotItem.paint, total time: 79.2849 ms

In the FAST version, a matched dpi would have saved only ~340ms.
Is that worth optimizing for?

The optimization would be adding one line after the instantiation of SymbolAtlas.

self.fragmentAtlas = SymbolAtlas()
self.fragmentAtlas.setDevicePixelRatio(QtGui.QGuiApplication.primaryScreen().devicePixelRatio())

from pyqtgraph.

aarpon avatar aarpon commented on May 26, 2024

Thanks a lot! I will try it out first thing on Monday.

from pyqtgraph.

aarpon avatar aarpon commented on May 26, 2024

Hi,

when using the suggested fast method (#2960 (comment)) to create just one QBrush per unique identifier and have all other points with the same id reference it, I get the following results:

pyQtGraph 0.13.3: 748 +/- 5ms
pyQtGraph 0.13.4: 748 +/- 6ms

down from ca. 19.6 seconds.

Additionally, If I change the device pixel ratio of SymbolAtlas via self.fragmentAtlas.setDevicePixelRatio(QtGui.QGuiApplication.primaryScreen().devicePixelRatio()) (which on my machine maps to 1.5), I get the following results:

pyQtGraph 0.13.4: 757 +/- 4ms

Apparently, changing the device pixel ratio even (very minimally) slows down the rendering.

All in all, reducing the number of QBrush object instantiations more than solves my performance issue with colouring points in the ScatterPlotItem (from 19 down to a quarter of a second), and any additional optimization seem to bring only negligible improvements (or even regressions).

from pyqtgraph.

pijyoi avatar pijyoi commented on May 26, 2024

I had a typo in my instructions (edited):

set PYQTGRAPHPROFILE="ScatterPlotItem.paint"

from pyqtgraph.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.