I am developing an Image viewer.
My main goal is to make something able to easily load .EXR files along with all other main formats.
I based it around :
PySide2 (Qt) for the UI.
OpenImageIO for .exr files (and some others) loading. I built a custom python module based around OpenImageIO (made with C++ and PyBind11)
On the c++ side, the output is pretty straight forward, my custom module return a simple struct
( nicely 'converted' into python by pybind11) :
struct img_data
{
bool is_valid;
int width;
int height;
int nchannels;
std::vector<unsigned char> pixels;
};
It's less straightforward when in python:
# get image_data from custom module
image_data = custom_exr_loader.load_image(image_path)
# convert pixels to a numpy array
np_array = np.uint8(image_data.pixels)
np_array.shape = (image_data.height, image_data.width, image_data.nchannels)
# convert to a PIL Image
pil_image = Image.fromarray(np_array).convert("RGBA")
# convert to a QImage
image_qt : ImageQt = ImageQt(pil_image)
# and finally convert to a QPixmap for final consumption ...
pixmap = QPixmap.fromImage(image_qt, Qt.ImageConversionFlag.ColorOnly)
Performance is pretty bad. Especially when caching a whole directory of big EXRs
I bet there is a better way to do this ...
Of course the idea is to offload some of the work from python to C++ land. But I'm not sure how.
I tried using QImage.fromData() method, but I never got a working result.
It requires a QByteArray as a param.
How should I format pixels data in c++ to be able to use QImage.fromData() from UI code ?
The problem here are the copies of the data. You are actually using 5 representations:
mage_data from load_image
numpy array
pil image with conversion
QImage
QPixmap again with conversion
All your conversion as far as I can see make a complete copy of the image. Sometimes you might even convert the data. This is detrimental for performance.
What you need is are converters that don't make a copy but just memory map the data in some array. Ideally you would need to do this for your binding as well. I am not sure how PyBind11 works, but you need to somehow give access to the raw C++ memory used there. Also you might be able to use QImage from the C++ side as such a wrapper. For numpy you need to use np.frombuffer. To go to Qt you can do something like
im_np = np.array(img)
qimage = QImage(im_np.data, im_np.shape[1], im_np.shape[0],
QImage.Format_BGR888)
Also you need to find a representation (column, pixel format) that is supported by all those entities. It's not an easy task. You would need to research a copy free mapping for each of your conversion. Examples are qimage_from_numpy, library_qimage2ndarray, pil_from_numpy. And there are many more.
Related
I dont get why die image differ from each other after this 3 lines of code. In my opinion the images should be identical.
from PIL import Image
phone_img = Image.open("img2.png")
phone_img1 = Image.frombytes(phone_img.mode, phone_img.size, phone_img.tobytes())
phone_img1.save("img2_new.png","PNG")
img2.png: http://666kb.com/i/dk4ykapuzs4wc2e4g.png
img2_new.png: http://666kb.com/i/dk4ykz98cg97grxts.png
I'm not a big PIL/Pillow user, but:
You open your image with Image.open()
The returned object is of type Image
It holds more than the pure pixel-data (as you see by using .mode, .size)
You create a new image by interpreting the full object as pixel-data only!
The last part should probably something like frombytes(phone_img.mode, phone_img.size, phone_img.getdata())
Depending on the lib, one should take care of bit-mode too (8bit vs. 16bit for example)
pyinsane's scan sessions return a list of 8-bit PIL images by default. This is true, even when the scan has been done in 16-bit mode (for instance using the transparency unit). Is there any way to get 16-bit images (I suppose PIL does not support that) or the original raw data out of pyinsane?
Here is the sample code I am currently using and getting images with 8 bits colour depth:
import pyinsane.abstract as pyinsane
device = pyinsane.get_devices()[0]
device.options['resolution'].value = 1200
device.options['mode'].value = 'Color'
device.options['source'].value = 'Transparency Unit'
scan_session = device.scan(multiple=False)
try:
while True:
scan_session.scan.read()
except EOFError:
pass
image = scan_session.images[0]
You're right, this is a limitation from Pillow (PIL). You can actually see the conversion from raw to PIL Image here : https://github.com/openpaperwork/pyinsane/blob/master/pyinsane2/sane/abstract.py#L161
If you really need this extra data, I guess the only option is to use the Sane API directly and do your own conversions:
import pyinsane2.sane.rawapi
pyinsane.sane.rawapi.sane_init()
(...)
pyinsane.sane.rawapi.sane_exit()
Unfortunately, doing this will make you loose the Windows portability (WIA support), and this part of Pyinsane is not documented at all. However, pyinsane.sane.rawapi provides the Sane C API with only minor transformations to make it more Pythony-friendly. So I guess you can just refer to the Sane documentation for informations : http://www.sane-project.org/html/doc009.html .
I am attempting to load in images I can't figure out how to load the images using pkgutil.get_data() which I would prefer to use since I don't want to have fixed paths in my code.
Currently, I have something like this, which works only when running out of the same folder.
...
self.imagePixmap = QtGui.QPixmap("img/myImage.png")
...
The issue then is, if you run the script from other folders the path is messed up and you get this error:
QPixmap::scaled: Pixmap is a null pixmap
I would like to use something like: pkutil.get_data("img", "myImage.png") to load the images however this provides the data from the image file where QPixmap() wants other kinds of data.
The only "workaround" I can see is to use part of what they specify here: pkgutil.get_data
and do something like this:
self.myPath = os.path.dirname(sys.modules[__name__].__file__)
self.imagePixmap = QtGui.QPixmap(os.path.join(self.myPath,"img/myImage.png"))
This just seems to much of a kludge to me. Is there a better way?
Here is what I ended up finding out. I should have rtfm a little closer. Anyway, you can use the loadFromData() method to get data from a QByteArray and pass it the format of the data therein.
self.imagePixmap = QtGui.QPixmap()
self.imagePixmap.loadFromData(get_data(__name__, "img/myImage.png"), 'png')
Here is a link to the information from here:
bool QPixmap::loadFromData(const QByteArray &data, const char *format = Q_NULLPTR, Qt::ImageConversionFlags flags = Qt::AutoColor)
This is an overloaded function.
Loads a pixmap from the binary data using the specified format and conversion flags.
I'd like to convert a set of pixel values from one profiled colorspace to another, without these values residing in an image file, such as (say) a list of RGB/RGBA/CMYK/etc data structures.
I have Python and PIL at my disposal, but I'm interested in solutions in related environments if that's what it takes.
The latest PIL has very nice support for LittleCMS -- but no way to hand it anything other than a PIL image (or a legacy pyCMS object) for it to act upon.
As far as I can ascertain, the command-line tool icctrans that's included with LittleCMS does something of this sort, but I can't seem to find any non-skeletal documentation on it, and the documentation refers to it as a demonstration tool.
In order to use the current 2.3 version of Little CMS with Python, I translated lcms2.h to lcms2consts.py with the h2py.py script that comes in the Python distribution. The script does not translate struct declarations, but the constants are enough to do basic color transformations with ctypes and lcms2 as a dynamic library.
This example transforms a single colour from double precision Lab to 8-bit sRGB using built-in profiles. Use cmsOpenProfileFromFile(filename, 'r') instead for files.
import ctypes
from ctypes import byref
from lcms2consts import *
lcms = ctypes.windll.lcms2
inprof = lcms.cmsCreateLab4Profile(0)
outprof = lcms.cmsCreate_sRGBProfile()
xform = lcms.cmsCreateTransform(inprof, TYPE_Lab_DBL,
outprof, TYPE_RGB_8,
INTENT_PERCEPTUAL, 0)
lcms.cmsCloseProfile(inprof)
lcms.cmsCloseProfile(outprof)
DblTriplet = ctypes.c_double * 3
ByteTriplet = ctypes.c_ubyte * 3
inbuf = DblTriplet(60.1,20.2,0.5)
outbuf = ByteTriplet()
lcms.cmsDoTransform(xform, byref(inbuf), byref(outbuf), 1)
print list(outbuf)
lcms.cmsDeleteTransform(xform)
There are two ways.
The hack way: To reprofile N color structures (and/or transform them between colorspaces) you create a 1x(N+2) image with PIL.Image.new(), use yourimage.load() to get a pixel-setting object interface thing, and set values (0,0) through (0, N) to whatever you got. Set (0, N+1) to white and (0, N+2) to black, and transform (or proof-transform) that image using your favorite ICC files and PIL.ImageCms.ImageCmsTransform(). Blammo: that PIL object is now your LUT. Read the values off with the image.load() thingy and you're good.
The true-nerd way: You need to use Python-colormath -- which is great for colorspace transforms but not profiling. Colormath can't read ICC profiles, so either a) you get to parse their crazy binary format in a reliable way, or b) just do the math, literally. This guy Bruce Lindbloom has all the data available in excel format for, like, all of the matricies you need to reprofile your LUTs. He is totally awesome. I am still trying to 'just' feed this data into colormath, so yeah, that makes me less awesome as I am still trying to get this 'nerd way' into something resembling production-quality.
There you go. That is what I did so far to answer the question of freestanding, commando-style ICC LUT transforms. U guys, srsly.
I'm trying to write a video application in PyQt4 and I've used Python ctypes to hook into an old legacy video decoder library. The library gives me 32-bit ARGB data and I need to turn that into a QImage. I've got it working as follows:
# Copy the rgb image data from the pointer into the buffer
memmove(self.rgb_buffer, self.rgb_buffer_ptr, self.buffer_size)
# Copy the buffer to a python string
imgdata = ""
for a in self.rgb_buffer:
imgdata = imgdata + a
# Create a QImage from the string data
img = QImage(imgdata, 720, 288, QImage.Format_ARGB32)
The problem is that ctypes outputs the data as type "ctypes.c_char_Array_829440" and I need to turn it into a python string so that I can construct a QImage. My copying mechanism is currently taking almost 300ms per image so it's painfully slow. The decode and display part of the process is only taking about 50ms.
Can anyone think of any cunning shortcuts I can take to speed up this process and avoid the need to copy the buffer twice as I'm currently doing?
The ctypes.c_char_Array_829400 instance has the property .raw which returns a string possibly containing NUL bytes, and the property .value which returns the string up to the first NUL byte if it contains one or more.
However, you can also use ctypes the access the string at self.rgb_buffer_ptr, like this:
ctypes.string_at(self.rgb_buffer_ptr, self.buffer_size); this would avoid the need for the memmove call.