def test_arrayToQPath(xs, ys, connect, expected): path = arrayToQPath(xs, ys, connect=connect) for i in range(path.elementCount()): with suppress(NameError): # nan elements add two line-segments, for simplicity of test config # we can ignore the second segment if (eq(element.x, np.nan) or eq(element.y, np.nan)): continue element = path.elementAt(i) assert eq(expected[i], (element.type, element.x, element.y))
def check_param_types(param, types, map_func, init, objs, keys): """Check that parameter setValue() accepts or rejects the correct types and that value() returns the correct type. Parameters ---------- param : Parameter instance types : type or tuple of types The allowed types for this parameter to return from value(). map_func : function Converts an input value to the expected output value. init : object The expected initial value of the parameter objs : dict Contains a variety of objects that will be tested as arguments to param.setValue(). keys : list The list of keys indicating the valid objects in *objs*. When param.setValue() is teasted with each value from *objs*, we expect an exception to be raised if the associated key is not in *keys*. """ val = param.value() if not isinstance(types, tuple): types = (types, ) assert val == init and type(val) in types # test valid input types good_inputs = [objs[k] for k in keys if k in objs] good_outputs = map(map_func, good_inputs) for x, y in zip(good_inputs, good_outputs): param.setValue(x) val = param.value() if not (eq(val, y) and type(val) in types): raise Exception( "Setting parameter %s with value %r should have resulted in %r (types: %r), " "but resulted in %r (type: %r) instead." % (param, x, y, types, val, type(val))) # test invalid input types for k, v in objs.items(): if k in keys: continue try: param.setValue(v) except (TypeError, ValueError, OverflowError): continue except Exception as exc: raise Exception("Setting %s parameter value to %r raised %r." % (param, v, exc)) raise Exception( "Setting %s parameter value to %r should have raised an exception." % (param, v))
def check_param_types(param, types, map_func, init, objs, keys): """Check that parameter setValue() accepts or rejects the correct types and that value() returns the correct type. Parameters ---------- param : Parameter instance types : type or tuple of types The allowed types for this parameter to return from value(). map_func : function Converts an input value to the expected output value. init : object The expected initial value of the parameter objs : dict Contains a variety of objects that will be tested as arguments to param.setValue(). keys : list The list of keys indicating the valid objects in *objs*. When param.setValue() is teasted with each value from *objs*, we expect an exception to be raised if the associated key is not in *keys*. """ val = param.value() if not isinstance(types, tuple): types = (types,) assert val == init and type(val) in types # test valid input types good_inputs = [objs[k] for k in keys if k in objs] good_outputs = map(map_func, good_inputs) for x,y in zip(good_inputs, good_outputs): param.setValue(x) val = param.value() if not (eq(val, y) and type(val) in types): raise Exception("Setting parameter %s with value %r should have resulted in %r (types: %r), " "but resulted in %r (type: %r) instead." % (param, x, y, types, val, type(val))) # test invalid input types for k,v in objs.items(): if k in keys: continue try: param.setValue(v) except (TypeError, ValueError, OverflowError): continue except Exception as exc: raise Exception("Setting %s parameter value to %r raised %r." % (param, v, exc)) raise Exception("Setting %s parameter value to %r should have raised an exception." % (param, v))
def setLevels(self, levels, update=True): """ Set image scaling levels. Can be one of: * [blackLevel, whiteLevel] * [[minRed, maxRed], [minGreen, maxGreen], [minBlue, maxBlue]] Only the first format is compatible with lookup tables. See :func:`makeARGB <pyqtgraph.makeARGB>` for more details on how levels are applied. """ if levels is not None: levels = np.asarray(levels) if not fn.eq(levels, self.levels): self.levels = levels self._effectiveLut = None if update: self.updateImage()
def arrayToQPath(x, y, connect='all'): """Convert an array of x,y coordinats to QPainterPath as efficiently as possible. The *connect* argument may be 'all', indicating that each point should be connected to the next; 'pairs', indicating that each pair of points should be connected, or an array of int32 values (0 or 1) indicating connections. """ ## Create all vertices in path. The method used below creates a binary format so that all ## vertices can be read in at once. This binary format may change in future versions of Qt, ## so the original (slower) method is left here for emergencies: # path.moveTo(x[0], y[0]) # if connect == 'all': # for i in range(1, y.shape[0]): # path.lineTo(x[i], y[i]) # elif connect == 'pairs': # for i in range(1, y.shape[0]): # if i%2 == 0: # path.lineTo(x[i], y[i]) # else: # path.moveTo(x[i], y[i]) # elif isinstance(connect, np.ndarray): # for i in range(1, y.shape[0]): # if connect[i] == 1: # path.lineTo(x[i], y[i]) # else: # path.moveTo(x[i], y[i]) # else: # raise Exception('connect argument must be "all", "pairs", or array') ## Speed this up using >> operator ## Format is: ## numVerts(i4) ## 0(i4) x(f8) y(f8) <-- 0 means this vertex does not connect ## 1(i4) x(f8) y(f8) <-- 1 means this vertex connects to the previous vertex ## ... ## cStart(i4) fillRule(i4) ## ## see: https://github.com/qt/qtbase/blob/dev/src/gui/painting/qpainterpath.cpp ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') path = QtGui.QPainterPath() n = x.shape[0] # create empty array, pad with extra space on either end arr = np.empty(n + 2, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')]) # write first two integers byteview = arr.view(dtype=np.ubyte) byteview[:16] = 0 byteview.data[16:20] = struct.pack('>i', n) # Fill array with vertex values arr[1:-1]['x'] = x arr[1:-1]['y'] = y # inf/nans completely prevent the plot from being displayed starting on # Qt version 5.12.3; these must now be manually cleaned out. isfinite = None qtver = [int(x) for x in QtVersion.split('.')] if qtver >= [5, 12, 3]: isfinite = np.isfinite(x) & np.isfinite(y) if not np.all(isfinite): # credit: Divakar https://stackoverflow.com/a/41191127/643629 mask = ~isfinite idx = np.arange(len(x)) idx[mask] = -1 np.maximum.accumulate(idx, out=idx) first = np.searchsorted(idx, 0) if first < len(x): # Replace all non-finite entries from beginning of arr with the first finite one idx[:first] = first arr[1:-1] = arr[1:-1][idx] # decide which points are connected by lines if eq(connect, 'all'): arr[1:-1]['c'] = 1 elif eq(connect, 'pairs'): arr[1:-1]['c'][::2] = 0 arr[1:-1]['c'][ 1::2] = 1 # connect every 2nd point to every 1st one elif eq(connect, 'finite'): # Let's call a point with either x or y being nan is an invalid point. # A point will anyway not connect to an invalid point regardless of the # 'c' value of the invalid point. Therefore, we should set 'c' to 0 for # the next point of an invalid point. if isfinite is None: isfinite = np.isfinite(x) & np.isfinite(y) arr[2:]['c'] = isfinite elif isinstance(connect, np.ndarray): arr[1:-1]['c'] = connect else: raise Exception( 'connect argument must be "all", "pairs", "finite", or array') arr[1]['c'] = 0 # the first vertex has no previous vertex to connect byteview.data[-20:-16] = struct.pack('>i', 0) # cStart byteview.data[-16:-12] = struct.pack('>i', 0) # fillRule (Qt.OddEvenFill) # create datastream object and stream into path ## Avoiding this method because QByteArray(str) leaks memory in PySide # buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here path.strn = byteview.data[16:-12] # make sure data doesn't run away try: buf = QtCore.QByteArray.fromRawData(path.strn) except TypeError: buf = QtCore.QByteArray(bytes(path.strn)) ds = QtCore.QDataStream(buf) ds >> path return path
def test_eq(): eq = pg.functions.eq zeros = [0, 0.0, np.float64(0), np.float32(0), np.int32(0), np.int64(0)] for i, x in enumerate(zeros): for y in zeros[i:]: assert eq(x, y) assert eq(y, x) assert eq(np.nan, np.nan) # test class NotEq(object): def __eq__(self, x): return False noteq = NotEq() assert eq(noteq, noteq) # passes because they are the same object assert not eq(noteq, NotEq()) # Should be able to test for equivalence even if the test raises certain # exceptions class NoEq(object): def __init__(self, err): self.err = err def __eq__(self, x): raise self.err noeq1 = NoEq(AttributeError()) noeq2 = NoEq(ValueError()) noeq3 = NoEq(Exception()) assert eq(noeq1, noeq1) assert not eq(noeq1, noeq2) assert not eq(noeq2, noeq1) with pytest.raises(Exception): eq(noeq3, noeq2) # test array equivalence # note that numpy has a weird behavior here--np.all() always returns True # if one of the arrays has size=0; eq() will only return True if both arrays # have the same shape. a1 = np.zeros((10, 20)).astype('float') a2 = a1 + 1 a3 = a2.astype('int') a4 = np.empty((0, 20)) assert not eq(a1, a2) # same shape/dtype, different values assert not eq(a1, a3) # same shape, different dtype and values assert not eq( a1, a4 ) # different shape (note: np.all gives True if one array has size 0) assert not eq(a2, a3) # same values, but different dtype assert not eq(a2, a4) # different shape assert not eq(a3, a4) # different shape and dtype assert eq(a4, a4.copy()) assert not eq(a4, a4.T) # test containers assert not eq({'a': 1}, {'a': 1, 'b': 2}) assert not eq({'a': 1}, {'a': 2}) d1 = {'x': 1, 'y': np.nan, 3: ['a', np.nan, a3, 7, 2.3], 4: a4} d2 = deepcopy(d1) assert eq(d1, d2) d1_ordered = OrderedDict(d1) d2_ordered = deepcopy(d1_ordered) assert eq(d1_ordered, d2_ordered) assert not eq(d1_ordered, d2) items = list(d1.items()) assert not eq(OrderedDict(items), OrderedDict(reversed(items))) assert not eq([1, 2, 3], [1, 2, 3, 4]) l1 = [d1, np.inf, -np.inf, np.nan] l2 = deepcopy(l1) t1 = tuple(l1) t2 = tuple(l2) assert eq(l1, l2) assert eq(t1, t2) assert eq(set(range(10)), set(range(10))) assert not eq(set(range(10)), set(range(9)))