Move and Update test-data repo into pyqtgraph repo
To reduce complexity, and make it easier to add more images and tests, the images in the `test-data` repository should be merged with the main repository. Furthermore, we can remove a lot of the subprocess work in the image_testing.py file, as we no longer need to have it interact with git. The images are not the same. Images were regenerated with Qt6, and now have proper big and little endian handling thanks to @pijyoi Second commit is a slightly modified variant of 2e135ab282d6007b34a3854921be54d0e9efb241 authored by @pijyoi it is to convert qimages to RGBA8888 for testing. Image files were regenerated images for the big/little handling Fixed issue with bogus test from test_NonUniformImage and generated a new image
7
.github/workflows/main.yml
vendored
@ -40,11 +40,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout test-data
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: pyqtgraph/test-data
|
||||
path: .pyqtgraph/test-data
|
||||
- name: Setup Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
@ -106,7 +101,7 @@ jobs:
|
||||
- name: Run Tests
|
||||
run: |
|
||||
mkdir $SCREENSHOT_DIR
|
||||
pytest pyqtgraph examples -v \
|
||||
pytest tests examples -v \
|
||||
--junitxml pytest.xml \
|
||||
shell: bash
|
||||
- name: Upload Screenshots
|
||||
|
@ -1511,17 +1511,10 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
|
||||
return ndarray_to_qimage(imgData, imgFormat)
|
||||
|
||||
|
||||
def imageToArray(img, copy=False, transpose=True):
|
||||
"""
|
||||
Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied.
|
||||
By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if
|
||||
the QImage is collected before the array, there may be trouble).
|
||||
The array will have shape (width, height, (b,g,r,a)).
|
||||
"""
|
||||
fmt = img.format()
|
||||
img_ptr = img.bits()
|
||||
def qimage_to_ndarray(qimg):
|
||||
img_ptr = qimg.bits()
|
||||
|
||||
if QT_LIB.startswith('PyQt'):
|
||||
if hasattr(img_ptr, 'setsize'): # PyQt sip.voidptr
|
||||
# sizeInBytes() was introduced in Qt 5.10
|
||||
# however PyQt5 5.12 will fail with:
|
||||
# "TypeError: QImage.sizeInBytes() is a private method"
|
||||
@ -1529,14 +1522,37 @@ def imageToArray(img, copy=False, transpose=True):
|
||||
# PyQt5 5.15, PySide2 5.12, PySide2 5.15
|
||||
try:
|
||||
# 64-bits size
|
||||
nbytes = img.sizeInBytes()
|
||||
nbytes = qimg.sizeInBytes()
|
||||
except (TypeError, AttributeError):
|
||||
# 32-bits size
|
||||
nbytes = img.byteCount()
|
||||
nbytes = qimg.byteCount()
|
||||
img_ptr.setsize(nbytes)
|
||||
|
||||
arr = np.frombuffer(img_ptr, dtype=np.ubyte)
|
||||
arr = arr.reshape(img.height(), img.width(), 4)
|
||||
depth = qimg.depth()
|
||||
if depth in (8, 24, 32):
|
||||
dtype = np.uint8
|
||||
nchan = depth // 8
|
||||
elif depth in (16, 64):
|
||||
dtype = np.uint16
|
||||
nchan = depth // 16
|
||||
else:
|
||||
raise ValueError("Unsupported Image Type")
|
||||
shape = qimg.height(), qimg.width()
|
||||
if nchan != 1:
|
||||
shape = shape + (nchan,)
|
||||
return np.frombuffer(img_ptr, dtype=dtype).reshape(shape)
|
||||
|
||||
|
||||
def imageToArray(img, copy=False, transpose=True):
|
||||
"""
|
||||
Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied.
|
||||
By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if
|
||||
the QImage is collected before the array, there may be trouble).
|
||||
The array will have shape (width, height, (b,g,r,a)).
|
||||
"""
|
||||
arr = qimage_to_ndarray(img)
|
||||
|
||||
fmt = img.format()
|
||||
if fmt == img.Format_RGB32:
|
||||
arr[...,3] = 255
|
||||
|
||||
|
@ -4,7 +4,7 @@ xvfb_height = 1080
|
||||
# use this due to some issues with ndarray reshape errors on CI systems
|
||||
xvfb_colordepth = 24
|
||||
xvfb_args=-ac +extension GLX +render
|
||||
faulthandler_timeout = 30
|
||||
faulthandler_timeout = 60
|
||||
|
||||
filterwarnings =
|
||||
# re-enable standard library warnings
|
||||
|
@ -5,7 +5,7 @@ import pytest
|
||||
from pyqtgraph.Qt import QtCore, QtGui, QtTest
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.tests import assertImageApproved, TransposedImageItem
|
||||
from tests.image_testing import assertImageApproved, TransposedImageItem
|
||||
try:
|
||||
import cupy
|
||||
except ImportError:
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QtTest
|
||||
from pyqtgraph.tests import mouseDrag, mouseMove
|
||||
from tests.ui_testing import mouseDrag, mouseMove
|
||||
pg.mkQApp()
|
||||
|
||||
|
@ -2,7 +2,7 @@ import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtTest
|
||||
from pyqtgraph.graphicsItems.NonUniformImage import NonUniformImage
|
||||
from pyqtgraph.tests import assertImageApproved
|
||||
from tests.image_testing import assertImageApproved
|
||||
from pyqtgraph.colormap import ColorMap
|
||||
import pyqtgraph.functions as fn
|
||||
import pytest
|
||||
@ -93,7 +93,7 @@ def test_NonUniformImage_colormap():
|
||||
|
||||
image = NonUniformImage(x, y, Z, border=fn.mkPen('g'))
|
||||
|
||||
cmap = ColorMap(pos=[0.0, 1.0], color=[(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)])
|
||||
cmap = ColorMap(pos=[0.0, 1.0], color=[(0, 0, 0), (255, 255, 255)])
|
||||
image.setColorMap(cmap)
|
||||
|
||||
viewbox.addItem(image)
|
@ -1,6 +1,6 @@
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.tests import assertImageApproved
|
||||
from tests.image_testing import assertImageApproved
|
||||
|
||||
|
||||
def test_PlotCurveItem():
|
@ -3,9 +3,10 @@ import sys
|
||||
import numpy as np
|
||||
import pytest
|
||||
import pyqtgraph as pg
|
||||
import platform
|
||||
from pyqtgraph.Qt import QtCore, QtGui, QtTest
|
||||
from pyqtgraph.tests import assertImageApproved, mouseMove, mouseDrag, mouseClick, TransposedImageItem, resizeWindow
|
||||
import pytest
|
||||
from tests.image_testing import assertImageApproved
|
||||
from tests.ui_testing import mouseMove, mouseDrag, mouseClick, resizeWindow
|
||||
|
||||
app = pg.mkQApp()
|
||||
pg.setConfigOption("mouseRateLimit", 0)
|
||||
@ -39,17 +40,20 @@ def test_getArrayRegion_axisorder():
|
||||
|
||||
|
||||
def check_getArrayRegion(roi, name, testResize=True, transpose=False):
|
||||
# on windows, edges corner pixels seem to be slightly different from other platforms
|
||||
# giving a pxCount=2 for a fudge factor
|
||||
if isinstance(roi, (pg.ROI, pg.RectROI)) and platform.system() == "Windows":
|
||||
pxCount = 2
|
||||
else:
|
||||
pxCount=-1
|
||||
|
||||
|
||||
initState = roi.getState()
|
||||
|
||||
#win = pg.GraphicsLayoutWidget()
|
||||
win = pg.GraphicsView()
|
||||
win.show()
|
||||
resizeWindow(win, 200, 400)
|
||||
# Don't use Qt's layouts for testing--these generate unpredictable results.
|
||||
#vb1 = win.addViewBox()
|
||||
#win.nextRow()
|
||||
#vb2 = win.addViewBox()
|
||||
|
||||
# Don't use Qt's layouts for testing--these generate unpredictable results.
|
||||
# Instead, place the viewboxes manually
|
||||
vb1 = pg.ViewBox()
|
||||
win.scene().addItem(vb1)
|
||||
@ -97,7 +101,7 @@ def check_getArrayRegion(roi, name, testResize=True, transpose=False):
|
||||
vb2.enableAutoRange(True, True)
|
||||
|
||||
app.processEvents()
|
||||
assertImageApproved(win, name+'/roi_getarrayregion', 'Simple ROI region selection.')
|
||||
assertImageApproved(win, name+'/roi_getarrayregion', 'Simple ROI region selection.', pxCount=pxCount)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
roi.setPos(0, False)
|
||||
@ -106,38 +110,33 @@ def check_getArrayRegion(roi, name, testResize=True, transpose=False):
|
||||
rgn = roi.getArrayRegion(data, img1, axes=(1, 2))
|
||||
img2.setImage(rgn[0, ..., 0])
|
||||
app.processEvents()
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_halfpx', 'Simple ROI region selection, 0.5 pixel shift.')
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_halfpx', 'Simple ROI region selection, 0.5 pixel shift.', pxCount=pxCount)
|
||||
|
||||
roi.setAngle(45)
|
||||
roi.setPos([3, 0])
|
||||
rgn = roi.getArrayRegion(data, img1, axes=(1, 2))
|
||||
img2.setImage(rgn[0, ..., 0])
|
||||
app.processEvents()
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_rotate', 'Simple ROI region selection, rotation.')
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_rotate', 'Simple ROI region selection, rotation.', pxCount=pxCount)
|
||||
|
||||
if testResize:
|
||||
roi.setSize([60, 60])
|
||||
rgn = roi.getArrayRegion(data, img1, axes=(1, 2))
|
||||
img2.setImage(rgn[0, ..., 0])
|
||||
app.processEvents()
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_resize', 'Simple ROI region selection, resized.')
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_resize', 'Simple ROI region selection, resized.', pxCount=pxCount)
|
||||
|
||||
img1.setPos(0, img1.height())
|
||||
img1.setTransform(QtGui.QTransform().scale(1, -1).rotate(20), True)
|
||||
rgn = roi.getArrayRegion(data, img1, axes=(1, 2))
|
||||
img2.setImage(rgn[0, ..., 0])
|
||||
app.processEvents()
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_img_trans', 'Simple ROI region selection, image transformed.')
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_img_trans', 'Simple ROI region selection, image transformed.', pxCount=pxCount)
|
||||
|
||||
vb1.invertY()
|
||||
rgn = roi.getArrayRegion(data, img1, axes=(1, 2))
|
||||
img2.setImage(rgn[0, ..., 0])
|
||||
app.processEvents()
|
||||
# on windows, one edge of one ROI handle is shifted slightly; letting this slide with pxCount=10
|
||||
if pg.Qt.QT_LIB in {'PyQt4', 'PySide'}:
|
||||
pxCount = 10
|
||||
else:
|
||||
pxCount=-1
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_inverty', 'Simple ROI region selection, view inverted.', pxCount=pxCount)
|
||||
|
||||
roi.setState(initState)
|
||||
@ -146,7 +145,7 @@ def check_getArrayRegion(roi, name, testResize=True, transpose=False):
|
||||
rgn = roi.getArrayRegion(data, img1, axes=(1, 2))
|
||||
img2.setImage(rgn[0, ..., 0])
|
||||
app.processEvents()
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_anisotropic', 'Simple ROI region selection, image scaled anisotropically.')
|
||||
assertImageApproved(win, name+'/roi_getarrayregion_anisotropic', 'Simple ROI region selection, image scaled anisotropically.', pxCount=pxCount)
|
||||
|
||||
# allow the roi to be re-used
|
||||
roi.scene().removeItem(roi)
|
@ -3,68 +3,32 @@
|
||||
"""
|
||||
Procedure for unit-testing with images:
|
||||
|
||||
1. Run unit tests at least once; this initializes a git clone of
|
||||
pyqtgraph/test-data in ~/.pyqtgraph.
|
||||
|
||||
2. Run individual test scripts with the PYQTGRAPH_AUDIT environment variable set:
|
||||
Run individual test scripts with the PYQTGRAPH_AUDIT environment variable set:
|
||||
|
||||
$ PYQTGRAPH_AUDIT=1 python pyqtgraph/graphicsItems/tests/test_PlotCurveItem.py
|
||||
|
||||
Any failing tests will display the test results, standard image, and the
|
||||
differences between the two. If the test result is bad, then press (f)ail.
|
||||
If the test result is good, then press (p)ass and the new image will be
|
||||
saved to the test-data directory.
|
||||
|
||||
To check all test results regardless of whether the test failed, set the
|
||||
environment variable PYQTGRAPH_AUDIT_ALL=1.
|
||||
|
||||
3. After adding or changing test images, create a new commit:
|
||||
|
||||
$ cd ~/.pyqtgraph/test-data
|
||||
$ git add ...
|
||||
$ git commit -a
|
||||
|
||||
4. Look up the most recent tag name from the `testDataTag` global variable
|
||||
below. Increment the tag name by 1 and create a new tag in the test-data
|
||||
repository:
|
||||
|
||||
$ git tag test-data-NNN
|
||||
$ git push --tags origin master
|
||||
|
||||
This tag is used to ensure that each pyqtgraph commit is linked to a specific
|
||||
commit in the test-data repository. This makes it possible to push new
|
||||
commits to the test-data repository without interfering with existing
|
||||
tests, and also allows unit tests to continue working on older pyqtgraph
|
||||
versions.
|
||||
Any failing tests will display the test results, standard image, and the
|
||||
differences between the two. If the test result is bad, then press (f)ail.
|
||||
If the test result is good, then press (p)ass and the new image will be
|
||||
saved to the test-data directory.
|
||||
|
||||
To check all test results regardless of whether the test failed, set the
|
||||
environment variable PYQTGRAPH_AUDIT_ALL=1.
|
||||
"""
|
||||
|
||||
|
||||
# This is the name of a tag in the test-data repository that this version of
|
||||
# pyqtgraph should be tested against. When adding or changing test images,
|
||||
# create and push a new tag and update this variable. To test locally, begin
|
||||
# by creating the tag in your ~/.pyqtgraph/test-data repository.
|
||||
testDataTag = 'test-data-8'
|
||||
|
||||
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import base64
|
||||
import subprocess as sp
|
||||
import warnings
|
||||
import numpy as np
|
||||
|
||||
if sys.version[0] >= '3':
|
||||
import http.client as httplib
|
||||
import urllib.parse as urllib
|
||||
else:
|
||||
import httplib
|
||||
import urllib
|
||||
from ..Qt import QtGui, QtCore, QtTest, QT_LIB
|
||||
from .. import functions as fn
|
||||
from .. import GraphicsLayoutWidget
|
||||
from .. import ImageItem, TextItem
|
||||
from pathlib import Path
|
||||
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
from pyqtgraph import functions as fn
|
||||
from pyqtgraph import GraphicsLayoutWidget
|
||||
from pyqtgraph import ImageItem, TextItem
|
||||
|
||||
|
||||
tester = None
|
||||
@ -101,6 +65,21 @@ def getTester():
|
||||
return tester
|
||||
|
||||
|
||||
def getImageFromWidget(widget):
|
||||
|
||||
# just to be sure the widget size is correct (new window may be resized):
|
||||
QtGui.QApplication.processEvents()
|
||||
|
||||
qimg = QtGui.QImage(widget.size(), QtGui.QImage.Format.Format_ARGB32)
|
||||
qimg.fill(QtCore.Qt.GlobalColor.transparent)
|
||||
painter = QtGui.QPainter(qimg)
|
||||
widget.render(painter)
|
||||
painter.end()
|
||||
|
||||
qimg = qimg.convertToFormat(QtGui.QImage.Format.Format_RGBA8888)
|
||||
return fn.qimage_to_ndarray(qimg).copy()
|
||||
|
||||
|
||||
def assertImageApproved(image, standardFile, message=None, **kwargs):
|
||||
"""Check that an image test result matches a pre-approved standard.
|
||||
|
||||
@ -108,10 +87,6 @@ def assertImageApproved(image, standardFile, message=None, **kwargs):
|
||||
to compare the images and decide whether to fail the test or save the new
|
||||
image as the standard.
|
||||
|
||||
This function will automatically clone the test-data repository into
|
||||
~/.pyqtgraph/test-data. However, it is up to the user to ensure this repository
|
||||
is kept up to date and to commit/push new images after they are saved.
|
||||
|
||||
Run the test with the environment variable PYQTGRAPH_AUDIT=1 to bring up
|
||||
the auditing GUI.
|
||||
|
||||
@ -131,43 +106,28 @@ def assertImageApproved(image, standardFile, message=None, **kwargs):
|
||||
comparison (see ``assertImageMatch()``).
|
||||
"""
|
||||
if isinstance(image, QtGui.QWidget):
|
||||
w = image
|
||||
|
||||
# just to be sure the widget size is correct (new window may be resized):
|
||||
# just to be sure the widget size is correct (new window may be resized):
|
||||
QtGui.QApplication.processEvents()
|
||||
|
||||
graphstate = scenegraphState(w, standardFile)
|
||||
qimg = QtGui.QImage(w.size(), QtGui.QImage.Format.Format_ARGB32)
|
||||
qimg.fill(QtCore.Qt.GlobalColor.transparent)
|
||||
painter = QtGui.QPainter(qimg)
|
||||
w.render(painter)
|
||||
painter.end()
|
||||
|
||||
image = fn.imageToArray(qimg, copy=False, transpose=False)
|
||||
|
||||
# the standard images seem to have their Red and Blue swapped
|
||||
if sys.byteorder == 'little':
|
||||
# transpose B,G,R,A to R,G,B,A
|
||||
image = image[..., [2, 1, 0, 3]]
|
||||
else:
|
||||
# transpose A,R,G,B to A,B,G,R
|
||||
image = image[..., [0, 3, 2, 1]]
|
||||
graphstate = scenegraphState(image, standardFile)
|
||||
image = getImageFromWidget(image)
|
||||
|
||||
if message is None:
|
||||
code = inspect.currentframe().f_back.f_code
|
||||
message = "%s::%s" % (code.co_filename, code.co_name)
|
||||
|
||||
# Make sure we have a test data repo available, possibly invoking git
|
||||
dataPath = getTestDataRepo()
|
||||
# Make sure we have a test data repo available
|
||||
dataPath = getTestDataDirectory()
|
||||
|
||||
# Read the standard image if it exists
|
||||
stdFileName = os.path.join(dataPath, standardFile + '.png')
|
||||
if not os.path.isfile(stdFileName):
|
||||
stdImage = None
|
||||
else:
|
||||
pxm = QtGui.QPixmap()
|
||||
pxm.load(stdFileName)
|
||||
stdImage = fn.imageToArray(pxm.toImage(), copy=True, transpose=False)
|
||||
qimg = QtGui.QImage(stdFileName)
|
||||
qimg = qimg.convertToFormat(QtGui.QImage.Format.Format_RGBA8888)
|
||||
stdImage = fn.qimage_to_ndarray(qimg).copy()
|
||||
del qimg
|
||||
|
||||
# If the test image does not match, then we go to audit if requested.
|
||||
try:
|
||||
@ -191,18 +151,13 @@ def assertImageApproved(image, standardFile, message=None, **kwargs):
|
||||
image = fn.downsample(image, sr[0], axis=(0, 1)).astype(image.dtype)
|
||||
|
||||
assertImageMatch(image, stdImage, **kwargs)
|
||||
|
||||
|
||||
if bool(os.getenv('PYQTGRAPH_PRINT_TEST_STATE', False)):
|
||||
print(graphstate)
|
||||
|
||||
|
||||
if os.getenv('PYQTGRAPH_AUDIT_ALL') == '1':
|
||||
raise Exception("Image test passed, but auditing due to PYQTGRAPH_AUDIT_ALL evnironment variable.")
|
||||
except Exception:
|
||||
|
||||
if stdFileName in gitStatus(dataPath):
|
||||
print("\n\nWARNING: unit test failed against modified standard "
|
||||
"image %s.\nTo revert this file, run `cd %s; git checkout "
|
||||
"%s`\n" % (stdFileName, dataPath, standardFile))
|
||||
if os.getenv('PYQTGRAPH_AUDIT') == '1' or os.getenv('PYQTGRAPH_AUDIT_ALL') == '1':
|
||||
sys.excepthook(*sys.exc_info())
|
||||
getTester().test(image, stdImage, message)
|
||||
@ -210,20 +165,18 @@ def assertImageApproved(image, standardFile, message=None, **kwargs):
|
||||
print('Saving new standard image to "%s"' % stdFileName)
|
||||
if not os.path.isdir(stdPath):
|
||||
os.makedirs(stdPath)
|
||||
img = fn.makeQImage(image, alpha=True, transpose=False)
|
||||
img.save(stdFileName)
|
||||
qimg = fn.ndarray_to_qimage(image, QtGui.QImage.Format.Format_RGBA8888)
|
||||
qimg.save(stdFileName)
|
||||
del qimg
|
||||
else:
|
||||
if stdImage is None:
|
||||
raise Exception("Test standard %s does not exist. Set "
|
||||
"PYQTGRAPH_AUDIT=1 to add this image." % stdFileName)
|
||||
else:
|
||||
if os.getenv('TRAVIS') is not None:
|
||||
saveFailedTest(image, stdImage, standardFile, upload=True)
|
||||
elif os.getenv('CI') is not None:
|
||||
standardFile = os.path.join(os.getenv("SCREENSHOT_DIR", "screenshots"), standardFile)
|
||||
saveFailedTest(image, stdImage, standardFile)
|
||||
print(graphstate)
|
||||
raise
|
||||
if os.getenv('CI') is not None:
|
||||
standardFile = os.path.join(os.getenv("SCREENSHOT_DIR", "screenshots"), standardFile)
|
||||
saveFailedTest(image, stdImage, standardFile)
|
||||
print(graphstate)
|
||||
raise
|
||||
|
||||
|
||||
def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50.,
|
||||
@ -249,8 +202,8 @@ def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50.,
|
||||
pxThreshold : float
|
||||
Minimum value difference at which two pixels are considered different
|
||||
pxCount : int or None
|
||||
Maximum number of pixels that may differ. Default is 0 for Qt4 and
|
||||
1% of image size for Qt5.
|
||||
Maximum number of pixels that may differ. Default is 0, on Windows some
|
||||
tests have a value of 2.
|
||||
maxPxDiff : float or None
|
||||
Maximum allowed difference between pixels
|
||||
avgPxDiff : float or None
|
||||
@ -264,12 +217,7 @@ def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50.,
|
||||
assert im1.dtype == im2.dtype
|
||||
|
||||
if pxCount == -1:
|
||||
if QT_LIB in {'PyQt5', 'PySide2', 'PySide6', 'PyQt6'}:
|
||||
# Qt5 generates slightly different results; relax the tolerance
|
||||
# until test images are updated.
|
||||
pxCount = int(im1.shape[0] * im1.shape[1] * 0.01)
|
||||
else:
|
||||
pxCount = 0
|
||||
pxCount = 0
|
||||
|
||||
diff = im1.astype(float) - im2.astype(float)
|
||||
if imgDiff is not None:
|
||||
@ -292,9 +240,7 @@ def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50.,
|
||||
assert corr >= minCorr
|
||||
|
||||
|
||||
def saveFailedTest(data, expect, filename, upload=False):
|
||||
"""Upload failed test images to web server to allow CI test debugging.
|
||||
"""
|
||||
def saveFailedTest(data, expect, filename):
|
||||
# concatenate data, expect, and diff into a single image
|
||||
ds = data.shape
|
||||
es = expect.shape
|
||||
@ -310,7 +256,7 @@ def saveFailedTest(data, expect, filename, upload=False):
|
||||
diff = makeDiffImage(data, expect)
|
||||
img[2:2+diff.shape[0], -diff.shape[1]-2:-2] = diff
|
||||
|
||||
png = makePng(img)
|
||||
png = makePng(data) # change `img` to `data` to save just the failed image
|
||||
directory = os.path.dirname(filename)
|
||||
if not os.path.isdir(directory):
|
||||
os.makedirs(directory)
|
||||
@ -318,38 +264,15 @@ def saveFailedTest(data, expect, filename, upload=False):
|
||||
png_file.write(png)
|
||||
print("\nImage comparison failed. Test result: %s %s Expected result: "
|
||||
"%s %s" % (data.shape, data.dtype, expect.shape, expect.dtype))
|
||||
if upload:
|
||||
uploadFailedTest(filename, png)
|
||||
|
||||
|
||||
def uploadFailedTest(filename, png):
|
||||
commit = runSubprocess(['git', 'rev-parse', 'HEAD'])
|
||||
name = filename.split(os.path.sep)
|
||||
name.insert(-1, commit.strip())
|
||||
filename = os.path.sep.join(name)
|
||||
|
||||
host = 'data.pyqtgraph.org'
|
||||
conn = httplib.HTTPConnection(host)
|
||||
req = urllib.urlencode({'name': filename,
|
||||
'data': base64.b64encode(png)})
|
||||
conn.request('POST', '/upload.py', req)
|
||||
response = conn.getresponse().read()
|
||||
conn.close()
|
||||
|
||||
print("Uploaded to: \nhttp://%s/data/%s" % (host, filename))
|
||||
if not response.startswith(b'OK'):
|
||||
print("WARNING: Error uploading data to %s" % host)
|
||||
print(response)
|
||||
|
||||
|
||||
def makePng(img):
|
||||
"""Given an array like (H, W, 4), return a PNG-encoded byte string.
|
||||
"""
|
||||
io = QtCore.QBuffer()
|
||||
qim = fn.makeQImage(img.transpose(1, 0, 2), alpha=False)
|
||||
qim = fn.ndarray_to_qimage(img, QtGui.QImage.Format.Format_RGBX8888)
|
||||
qim.save(io, 'PNG')
|
||||
png = bytes(io.data().data())
|
||||
return png
|
||||
return bytes(io.data().data())
|
||||
|
||||
|
||||
def makeDiffImage(im1, im2):
|
||||
@ -467,155 +390,18 @@ class ImageTester(QtGui.QWidget):
|
||||
|
||||
|
||||
def getTestDataRepo():
|
||||
"""Return the path to a git repository with the required commit checked
|
||||
out.
|
||||
|
||||
If the repository does not exist, then it is cloned from
|
||||
https://github.com/pyqtgraph/test-data. If the repository already exists
|
||||
then the required commit is checked out.
|
||||
"""
|
||||
global testDataTag
|
||||
|
||||
if os.getenv("CI"):
|
||||
dataPath = os.path.join(os.environ["GITHUB_WORKSPACE"], '.pyqtgraph', 'test-data')
|
||||
else:
|
||||
dataPath = os.path.join(os.path.expanduser('~'), '.pyqtgraph', 'test-data')
|
||||
gitPath = 'https://github.com/pyqtgraph/test-data'
|
||||
gitbase = gitCmdBase(dataPath)
|
||||
|
||||
if os.path.isdir(dataPath):
|
||||
# Already have a test-data repository to work with.
|
||||
|
||||
# Get the commit ID of testDataTag. Do a fetch if necessary.
|
||||
try:
|
||||
tagCommit = gitCommitId(dataPath, testDataTag)
|
||||
except NameError:
|
||||
cmd = gitbase + ['fetch', '--tags', 'origin']
|
||||
print(' '.join(cmd))
|
||||
sp.check_call(cmd)
|
||||
try:
|
||||
tagCommit = gitCommitId(dataPath, testDataTag)
|
||||
except NameError:
|
||||
raise Exception("Could not find tag '%s' in test-data repo at"
|
||||
" %s" % (testDataTag, dataPath))
|
||||
except Exception:
|
||||
if not os.path.exists(os.path.join(dataPath, '.git')):
|
||||
raise Exception("Directory '%s' does not appear to be a git "
|
||||
"repository. Please remove this directory." %
|
||||
dataPath)
|
||||
else:
|
||||
raise
|
||||
|
||||
# If HEAD is not the correct commit, then do a checkout
|
||||
if gitCommitId(dataPath, 'HEAD') != tagCommit:
|
||||
print("Checking out test-data tag '%s'" % testDataTag)
|
||||
sp.check_call(gitbase + ['checkout', testDataTag])
|
||||
|
||||
else:
|
||||
print("Attempting to create git clone of test data repo in %s.." %
|
||||
dataPath)
|
||||
|
||||
parentPath = os.path.split(dataPath)[0]
|
||||
if not os.path.isdir(parentPath):
|
||||
os.makedirs(parentPath)
|
||||
|
||||
if os.getenv('TRAVIS') is not None or os.getenv('CI') is not None:
|
||||
# Create a shallow clone of the test-data repository (to avoid
|
||||
# downloading more data than is necessary)
|
||||
os.makedirs(dataPath)
|
||||
cmds = [
|
||||
gitbase + ['init'],
|
||||
gitbase + ['remote', 'add', 'origin', gitPath],
|
||||
gitbase + ['fetch', '--tags', 'origin', testDataTag,
|
||||
'--depth=1'],
|
||||
gitbase + ['checkout', '-b', 'master', 'FETCH_HEAD'],
|
||||
]
|
||||
else:
|
||||
# Create a full clone
|
||||
cmds = [['git', 'clone', gitPath, dataPath]]
|
||||
|
||||
for cmd in cmds:
|
||||
print(' '.join(cmd))
|
||||
rval = sp.check_call(cmd)
|
||||
if rval == 0:
|
||||
continue
|
||||
raise RuntimeError("Test data path '%s' does not exist and could "
|
||||
"not be created with git. Please create a git "
|
||||
"clone of %s at this path." %
|
||||
(dataPath, gitPath))
|
||||
|
||||
return dataPath
|
||||
warnings.warn(
|
||||
"Test data data repo has been merged with the main repo"
|
||||
"use getTestDataDirectory() instead, this method will be removed"
|
||||
"in a future version of pyqtgraph",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
return getTestDataDirectory()
|
||||
|
||||
|
||||
def gitCmdBase(path):
|
||||
return ['git', '--git-dir=%s/.git' % path, '--work-tree=%s' % path]
|
||||
|
||||
|
||||
def gitStatus(path):
|
||||
"""Return a string listing all changes to the working tree in a git
|
||||
repository.
|
||||
"""
|
||||
cmd = gitCmdBase(path) + ['status', '--porcelain']
|
||||
return runSubprocess(cmd, stderr=None, universal_newlines=True)
|
||||
|
||||
|
||||
def gitCommitId(path, ref):
|
||||
"""Return the commit id of *ref* in the git repository at *path*.
|
||||
"""
|
||||
cmd = gitCmdBase(path) + ['show', ref]
|
||||
try:
|
||||
output = runSubprocess(cmd, stderr=None, universal_newlines=True)
|
||||
except sp.CalledProcessError:
|
||||
print(cmd)
|
||||
raise NameError("Unknown git reference '%s'" % ref)
|
||||
commit = output.split('\n')[0]
|
||||
assert commit[:7] == 'commit '
|
||||
return commit[7:]
|
||||
|
||||
|
||||
def runSubprocess(command, return_code=False, **kwargs):
|
||||
"""Run command using subprocess.Popen
|
||||
|
||||
Similar to subprocess.check_output(), which is not available in 2.6.
|
||||
|
||||
Run command and wait for command to complete. If the return code was zero
|
||||
then return, otherwise raise CalledProcessError.
|
||||
By default, this will also add stdout= and stderr=subproces.PIPE
|
||||
to the call to Popen to suppress printing to the terminal.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command : list of str
|
||||
Command to run as subprocess (see subprocess.Popen documentation).
|
||||
**kwargs : dict
|
||||
Additional kwargs to pass to ``subprocess.Popen``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
stdout : str
|
||||
Stdout returned by the process.
|
||||
"""
|
||||
# code adapted with permission from mne-python
|
||||
use_kwargs = dict(stderr=None, stdout=sp.PIPE)
|
||||
use_kwargs.update(kwargs)
|
||||
|
||||
p = sp.Popen(command, **use_kwargs)
|
||||
output = p.communicate()[0]
|
||||
|
||||
# communicate() may return bytes, str, or None depending on the kwargs
|
||||
# passed to Popen(). Convert all to unicode str:
|
||||
output = '' if output is None else output
|
||||
output = output.decode('utf-8') if isinstance(output, bytes) else output
|
||||
|
||||
if p.returncode != 0:
|
||||
print(output)
|
||||
err_fun = sp.CalledProcessError.__init__
|
||||
if 'output' in inspect.getfullargspec(err_fun).args:
|
||||
raise sp.CalledProcessError(p.returncode, command, output)
|
||||
else:
|
||||
raise sp.CalledProcessError(p.returncode, command)
|
||||
|
||||
return output
|
||||
def getTestDataDirectory():
|
||||
dataPath = Path(__file__).absolute().parent / "images"
|
||||
return dataPath.as_posix()
|
||||
|
||||
|
||||
def scenegraphState(view, name):
|
||||
@ -632,7 +418,7 @@ def scenegraphState(view, name):
|
||||
|
||||
def itemState(root):
|
||||
state = str(root) + '\n'
|
||||
from .. import ViewBox
|
||||
from pyqtgraph import ViewBox
|
||||
state += 'bounding rect: ' + str(root.boundingRect()) + '\n'
|
||||
if isinstance(root, ViewBox):
|
||||
state += "view range: " + str(root.viewRange()) + '\n'
|
||||
@ -647,7 +433,7 @@ def transformStr(t):
|
||||
|
||||
|
||||
def indent(s, pfx):
|
||||
return '\n'.join([pfx+line for line in s.split('\n')])
|
||||
return '\n'.join(pfx+line for line in s.split('\n'))
|
||||
|
||||
|
||||
class TransposedImageItem(ImageItem):
|
BIN
tests/images/imageitem/bool.png
Normal file
After Width: | Height: | Size: 622 B |
BIN
tests/images/imageitem/gradient_mono_byte.png
Normal file
After Width: | Height: | Size: 660 B |
BIN
tests/images/imageitem/gradient_mono_byte_levels.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
tests/images/imageitem/gradient_mono_int.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
tests/images/imageitem/gradient_mono_int_levels.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
tests/images/imageitem/gradient_rgba_byte.png
Normal file
After Width: | Height: | Size: 718 B |
BIN
tests/images/imageitem/gradient_rgba_byte_levels.png
Normal file
After Width: | Height: | Size: 671 B |
BIN
tests/images/imageitem/gradient_rgba_float.png
Normal file
After Width: | Height: | Size: 749 B |
BIN
tests/images/imageitem/gradient_rgba_float_additive.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
tests/images/imageitem/gradient_rgba_float_alpha.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
tests/images/imageitem/init.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tests/images/imageitem/levels1.png
Normal file
After Width: | Height: | Size: 647 B |
BIN
tests/images/imageitem/lut.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
tests/images/imageitem/monochrome.png
Normal file
After Width: | Height: | Size: 622 B |
BIN
tests/images/imageitem/resolution_with_downsampling_x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
tests/images/imageitem/resolution_with_downsampling_y.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
tests/images/imageitem/resolution_without_downsampling.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
tests/images/nonuniform_image/colormap-3x3.png
Normal file
After Width: | Height: | Size: 656 B |
BIN
tests/images/nonuniform_image/lut-3x3.png
Normal file
After Width: | Height: | Size: 668 B |
BIN
tests/images/plotcurveitem/connectall.png
Normal file
After Width: | Height: | Size: 885 B |
BIN
tests/images/plotcurveitem/connectarray.png
Normal file
After Width: | Height: | Size: 788 B |
BIN
tests/images/plotcurveitem/connectfinite.png
Normal file
After Width: | Height: | Size: 547 B |
BIN
tests/images/plotcurveitem/connectpairs.png
Normal file
After Width: | Height: | Size: 770 B |
BIN
tests/images/roi/baseroi/roi_getarrayregion.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
tests/images/roi/baseroi/roi_getarrayregion_anisotropic.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
tests/images/roi/baseroi/roi_getarrayregion_halfpx.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
tests/images/roi/baseroi/roi_getarrayregion_img_trans.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
tests/images/roi/baseroi/roi_getarrayregion_inverty.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
tests/images/roi/baseroi/roi_getarrayregion_resize.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
tests/images/roi/baseroi/roi_getarrayregion_rotate.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
tests/images/roi/ellipseroi/roi_getarrayregion.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
tests/images/roi/ellipseroi/roi_getarrayregion_anisotropic.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
tests/images/roi/ellipseroi/roi_getarrayregion_halfpx.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
tests/images/roi/ellipseroi/roi_getarrayregion_img_trans.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
tests/images/roi/ellipseroi/roi_getarrayregion_inverty.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
tests/images/roi/ellipseroi/roi_getarrayregion_resize.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
tests/images/roi/ellipseroi/roi_getarrayregion_rotate.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
tests/images/roi/polylineroi/closed_clear.png
Normal file
After Width: | Height: | Size: 627 B |
BIN
tests/images/roi/polylineroi/closed_click_segment.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
tests/images/roi/polylineroi/closed_drag_handle.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
tests/images/roi/polylineroi/closed_drag_new_handle.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
tests/images/roi/polylineroi/closed_drag_roi.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
tests/images/roi/polylineroi/closed_hover_handle.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
tests/images/roi/polylineroi/closed_hover_roi.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
tests/images/roi/polylineroi/closed_hover_segment.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
tests/images/roi/polylineroi/closed_init.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
tests/images/roi/polylineroi/closed_setpoints.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
tests/images/roi/polylineroi/closed_setstate.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
tests/images/roi/polylineroi/open_clear.png
Normal file
After Width: | Height: | Size: 627 B |
BIN
tests/images/roi/polylineroi/open_click_segment.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tests/images/roi/polylineroi/open_drag_handle.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
tests/images/roi/polylineroi/open_drag_new_handle.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
tests/images/roi/polylineroi/open_drag_roi.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
tests/images/roi/polylineroi/open_hover_handle.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
tests/images/roi/polylineroi/open_hover_roi.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
tests/images/roi/polylineroi/open_hover_segment.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
tests/images/roi/polylineroi/open_init.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
tests/images/roi/polylineroi/open_setpoints.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
tests/images/roi/polylineroi/open_setstate.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
tests/images/roi/polylineroi/roi_getarrayregion.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
tests/images/roi/polylineroi/roi_getarrayregion_anisotropic.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
tests/images/roi/polylineroi/roi_getarrayregion_halfpx.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
tests/images/roi/polylineroi/roi_getarrayregion_img_trans.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
tests/images/roi/polylineroi/roi_getarrayregion_inverty.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
tests/images/roi/polylineroi/roi_getarrayregion_rotate.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
tests/images/roi/rectroi/roi_getarrayregion.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
tests/images/roi/rectroi/roi_getarrayregion_anisotropic.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
tests/images/roi/rectroi/roi_getarrayregion_halfpx.png
Normal file
After Width: | Height: | Size: 6.8 KiB |