a91953e93d
* refactor out _ndarray_to_qimage()
* combine levels back with lut
* make use of Grayscale8, RGB888 and Indexed8 QImage formats
Grayscale8 and RGB888 images are those that are ready for display
without further processing.
* add Grayscale16
* apply the efflut early for uint16 mono/rgb, uint8 rgb
* ndarray indexing is faster than np.take
* handle uint16 rgb(a) with no levels same as levels=[0, 65535]
* add support for Format_RGBA64
* fix: support colormaps of shape (h, 1)
* check ImageItem uint8 and uint16 QImage formats
* uint16 mono with rgb lut -> RGBX8888
* got width and height swapped in array dimensions
* set ImageItem as row-major
* no need to form a 1d 32-bit lut for array indexing
you can index (y, x) into a lookup table of shape (nentry, 3) or
(nentry, 4) and get an output of shape (y, x, 3) or (y, x, 4)
* Revert "no need to form a 1d 32-bit lut for array indexing"
This reverts commit 45cf3100de
.
* distinguish between levels_lut and colors_lut
this allows uint16 images with user lut to be rendered as
Format_Indexed8
* uint8 (1-chan) images should always combine to efflut
this efflut will then be used for Indexed8 format color table.
previously, we would be taking a performance hit with doing a numpy
lookup with levels_lut.
* adapt benchmarks/makeARGB.py to renderImageItem.py
* restructure uint8 and uint16 codepaths
* normalize 1-chan images to ndim==2 earlier up
* refactor long code into functions
* bug: qimage may not be assigned
* fix: assign to self.qimage only if not None
* for uint16, do rescale rather than do levels_lut lookup
* cases 2,3 are already handled
i.e. no more using lut to do rescale of uint16 image data.
* rescale rgb images by computation, not by memory lookup
* setImage() does not take an output argument
* try to be cupy compatible
use "xp" instead of numpy module
* add numba to benchmarking
* fix: lut_big is dtype uint8 with more than 256 entries
* bug: applying colors_lut needs C-order
* support float with no nans
* fix: variable could be uninitialized
* add float32 format tests
* avoid explicitly forcing to C-contiguous
* cache effective lut only if combination took place
every one of the four branches now does its own return.
this makes it easier to follow.
* fix cupy benchmark : typo in renderQImage
* remove for loop of 1 iteration
* use float32 for floating point benchmark
* superceded by renderImageItem.py
* lint
* benchmark without lut conversion
* put the lut onto the substrate
* fix editor complaints
* handle lack of cupy
* leading underscores imply privacy
Co-authored-by: KIU Shueng Chuan <nixchuan@gmail.com>
140 lines
4.4 KiB
Python
140 lines
4.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
import numpy as np
|
|
|
|
import pyqtgraph as pg
|
|
|
|
try:
|
|
import cupy as cp
|
|
|
|
pg.setConfigOption("useCupy", True)
|
|
except ImportError:
|
|
cp = None
|
|
|
|
try:
|
|
import numba
|
|
except ImportError:
|
|
numba = None
|
|
|
|
|
|
def renderQImage(*args, **kwargs):
|
|
imgitem = pg.ImageItem(axisOrder='row-major')
|
|
if 'autoLevels' not in kwargs:
|
|
kwargs['autoLevels'] = False
|
|
imgitem.setImage(*args, **kwargs)
|
|
imgitem.render()
|
|
|
|
|
|
def prime_numba():
|
|
shape = (64, 64)
|
|
lut_small = np.random.randint(256, size=(256, 3), dtype=np.uint8)
|
|
lut_big = np.random.randint(256, size=(512, 3), dtype=np.uint8)
|
|
for lut in [lut_small, lut_big]:
|
|
renderQImage(np.zeros(shape, dtype=np.uint8), levels=(20, 220), lut=lut)
|
|
renderQImage(np.zeros(shape, dtype=np.uint16), levels=(250, 3000), lut=lut)
|
|
renderQImage(np.zeros(shape, dtype=np.float32), levels=(-4.0, 4.0), lut=lut)
|
|
|
|
|
|
class _TimeSuite(object):
|
|
def __init__(self):
|
|
super(_TimeSuite, self).__init__()
|
|
self.size = None
|
|
self.float_data = None
|
|
self.uint8_data = None
|
|
self.uint8_lut = None
|
|
self.uint16_data = None
|
|
self.uint16_lut = None
|
|
self.cupy_uint16_lut = None
|
|
self.cupy_uint8_lut = None
|
|
|
|
def setup(self):
|
|
size = (self.size, self.size)
|
|
self.float_data, self.uint16_data, self.uint8_data, self.uint16_lut, self.uint8_lut = self._create_data(
|
|
size, np
|
|
)
|
|
if numba is not None:
|
|
# ensure JIT compilation
|
|
pg.setConfigOption("useNumba", True)
|
|
prime_numba()
|
|
pg.setConfigOption("useNumba", False)
|
|
if cp:
|
|
_d1, _d2, _d3, self.cupy_uint16_lut, self.cupy_uint8_lut = self._create_data(size, cp)
|
|
renderQImage(cp.asarray(self.uint16_data["data"])) # prime the gpu
|
|
|
|
@property
|
|
def numba_uint16_lut(self):
|
|
return self.uint16_lut
|
|
|
|
@property
|
|
def numba_uint8_lut(self):
|
|
return self.uint8_lut
|
|
|
|
@property
|
|
def numpy_uint16_lut(self):
|
|
return self.uint16_lut
|
|
|
|
@property
|
|
def numpy_uint8_lut(self):
|
|
return self.uint8_lut
|
|
|
|
@staticmethod
|
|
def _create_data(size, xp):
|
|
float_data = {
|
|
"data": xp.random.normal(size=size).astype("float32"),
|
|
"levels": [-4.0, 4.0],
|
|
}
|
|
uint16_data = {
|
|
"data": xp.random.randint(100, 4500, size=size).astype("uint16"),
|
|
"levels": [250, 3000],
|
|
}
|
|
uint8_data = {
|
|
"data": xp.random.randint(0, 255, size=size).astype("ubyte"),
|
|
"levels": [20, 220],
|
|
}
|
|
c_map = xp.array([[-500.0, 255.0], [-255.0, 255.0], [0.0, 500.0]])
|
|
uint8_lut = xp.zeros((256, 4), dtype="ubyte")
|
|
for i in range(3):
|
|
uint8_lut[:, i] = xp.clip(xp.linspace(c_map[i][0], c_map[i][1], 256), 0, 255)
|
|
uint8_lut[:, 3] = 255
|
|
uint16_lut = xp.zeros((2 ** 16, 4), dtype="ubyte")
|
|
for i in range(3):
|
|
uint16_lut[:, i] = xp.clip(xp.linspace(c_map[i][0], c_map[i][1], 2 ** 16), 0, 255)
|
|
uint16_lut[:, 3] = 255
|
|
return float_data, uint16_data, uint8_data, uint16_lut, uint8_lut
|
|
|
|
|
|
def make_test(dtype, kind, use_levels, lut_name, func_name):
|
|
def time_test(self):
|
|
data = getattr(self, dtype + "_data")
|
|
levels = data["levels"] if use_levels else None
|
|
lut = getattr(self, f"{kind}_{lut_name}_lut", None) if lut_name is not None else None
|
|
pg.setConfigOption("useNumba", kind == "numba")
|
|
img_data = data["data"]
|
|
if kind == "cupy":
|
|
img_data = cp.asarray(img_data)
|
|
renderQImage(img_data, lut=lut, levels=levels)
|
|
|
|
time_test.__name__ = func_name
|
|
return time_test
|
|
|
|
|
|
for option in ["cupy", "numba", "numpy"]:
|
|
if option == "cupy" and cp is None:
|
|
continue
|
|
if option == "numba" and numba is None:
|
|
continue
|
|
for data_type in ["float", "uint16", "uint8"]:
|
|
for lvls in [True, False]:
|
|
if data_type == "float" and not lvls:
|
|
continue
|
|
for lutname in [None, "uint8", "uint16"]:
|
|
name = (
|
|
f'time_1x_renderImageItem_{option}_{data_type}_{"" if lvls else "no"}levels_{lutname or "no"}lut'
|
|
)
|
|
setattr(_TimeSuite, name, make_test(data_type, option, lvls, lutname, name))
|
|
|
|
|
|
class Time4096Suite(_TimeSuite):
|
|
def __init__(self):
|
|
super(Time4096Suite, self).__init__()
|
|
self.size = 4096
|