diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 3936e926..bc983118 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -824,9 +824,14 @@ def rescaleData(data, scale, offset, dtype=None): setConfigOptions(useWeave=False) #p = np.poly1d([scale, -offset*scale]) - #data = p(data).astype(dtype) - d2 = data-offset - np.multiply(d2, scale, out=d2, casting="unsafe") + #d2 = p(data) + d2 = data - float(offset) + d2 *= scale + + # Clip before converting dtype to avoid overflow + if dtype.kind in 'ui': + lim = np.iinfo(dtype) + d2 = np.clip(d2, lim.min, lim.max) data = d2.astype(dtype) return data @@ -875,8 +880,8 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False): channel). The use of this feature requires that levels.shape[0] == data.shape[-1]. scale The maximum value to which data will be rescaled before being passed through the lookup table (or returned if there is no lookup table). By default this will - be set to the length of the lookup table, or 256 is no lookup table is provided. - For OpenGL color specifications (as in GLColor4f) use scale=1.0 + be set to the length of the lookup table, or 255 if no lookup table is provided. + For OpenGL color specifications (as in GLColor4f) use scale=1.0. lut Optional lookup table (array with dtype=ubyte). Values in data will be converted to color by indexing directly from lut. The output data shape will be input.shape + lut.shape[1:]. @@ -884,7 +889,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False): Note: the output of makeARGB will have the same dtype as the lookup table, so for conversion to QImage, the dtype must be ubyte. - Lookup tables can be built using GradientWidget. + Lookup tables can be built using ColorMap or GradientWidget. useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures). The default is False, which returns in ARGB order for use with QImage (Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order @@ -918,6 +923,13 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False): scale = lut.shape[0] else: scale = 255. + + if lut is not None: + dtype = lut.dtype + elif scale == 255: + dtype = np.ubyte + else: + dtype = np.float ## Apply levels if given if levels is not None: @@ -931,16 +943,26 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False): minVal, maxVal = levels[i] if minVal == maxVal: maxVal += 1e-16 - newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=int) + newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=dtype) data = newData else: minVal, maxVal = levels if minVal == maxVal: maxVal += 1e-16 if maxVal == minVal: - data = rescaleData(data, 1, minVal, dtype=int) + data = rescaleData(data, 1, minVal, dtype=dtype) else: - data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int) + lutSize = 2**(data.itemsize*8) + if data.dtype in (np.ubyte, np.uint16) and data.size > lutSize: + # Rather than apply scaling to image, scale the LUT for better performance. + ind = np.arange(lutSize) + indr = rescaleData(ind, scale/(maxVal-minVal), minVal, dtype=dtype) + if lut is None: + lut = indr + else: + lut = lut[indr] + else: + data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=dtype) profile() diff --git a/pyqtgraph/tests/test_functions.py b/pyqtgraph/tests/test_functions.py index 4ef2daf0..7ed7fffc 100644 --- a/pyqtgraph/tests/test_functions.py +++ b/pyqtgraph/tests/test_functions.py @@ -111,6 +111,70 @@ def test_subArray(): assert np.all(bb == cc) +def test_rescaleData(): + dtypes = map(np.dtype, ('ubyte', 'uint16', 'byte', 'int16', 'int', 'float')) + for dtype1 in dtypes: + for dtype2 in dtypes: + data = (np.random.random(size=10) * 2**32 - 2**31).astype(dtype1) + for scale, offset in [(10, 0), (10., 0.), (1, -50), (0.2, 0.5), (0.001, 0)]: + if dtype2.kind in 'iu': + lim = np.iinfo(dtype2) + lim = lim.min, lim.max + else: + lim = (-np.inf, np.inf) + s1 = np.clip(float(scale) * (data-float(offset)), *lim).astype(dtype2) + s2 = pg.rescaleData(data, scale, offset, dtype2) + assert s1.dtype == s2.dtype + if dtype2.kind in 'iu': + assert np.all(s1 == s2) + else: + assert np.allclose(s1, s2) + + +def test_makeARGB(): + + # uint8 data tests + + im1 = np.array([[1,2,3], [4,5,8]], dtype='ubyte') + im2, alpha = pg.makeARGB(im1, levels=(0, 6)) + assert im2.dtype == np.ubyte + assert alpha == False + assert np.all(im2[...,3] == 255) + assert np.all(im2[...,:3] == np.array([[42, 85, 127], [170, 212, 255]], dtype=np.ubyte)[...,np.newaxis]) + + im3, alpha = pg.makeARGB(im1, levels=(0.0, 6.0)) + assert im3.dtype == np.ubyte + assert alpha == False + assert np.all(im3 == im2) + + im2, alpha = pg.makeARGB(im1, levels=(2, 10)) + assert im2.dtype == np.ubyte + assert alpha == False + assert np.all(im2[...,3] == 255) + assert np.all(im2[...,:3] == np.array([[0, 0, 31], [63, 95, 191]], dtype=np.ubyte)[...,np.newaxis]) + + im2, alpha = pg.makeARGB(im1, levels=(2, 10), scale=1.0) + assert im2.dtype == np.float + assert alpha == False + assert np.all(im2[...,3] == 1.0) + assert np.all(im2[...,:3] == np.array([[0, 0, 31], [63, 95, 191]], dtype=np.ubyte)[...,np.newaxis]) + + # uint8 input + uint8 LUT + lut = np.arange(512).astype(np.ubyte)[::2][::-1] + im2, alpha = pg.makeARGB(im1, lut=lut, levels=(2, 10)) + assert im2.dtype == np.ubyte + assert alpha == False + assert np.all(im2[...,3] == 255) + assert np.all(im2[...,:3] == np.array([[0, 0, 31], [63, 95, 191]], dtype=np.ubyte)[...,np.newaxis]) + + # uint8 data + uint16 LUT + + # uint8 data + float LUT + + # uint16 data tests + + im1 = np.array([[1,2,3], [4,5,8]], dtype='ubyte') + if __name__ == '__main__': test_interpolateArray() \ No newline at end of file