16-bit color images with pyinsane - python

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 .

Related

Why does the Sobel filter return a black square?

I'm trying to use sobel and prewitt filters from skimage for edge detection to compare the results, but for both I just get black squares!
That's my code:
import numpy as np
from skimage import filters
from PIL import Image
a=Image.open('F:/CT1.png').convert('L')
a.show()
a=np.asarray(a)
b=filters.sobel(a)
b=Image.fromarray(b)
b.show()
As most methods from scikit-image, the sobel function uses np.float64 for calculations, and thus converts your image appropriately to the range 0.0 ... 1.0. Following, your result b is also of type np.float64 with values in the same range. When now converting to some Pillow Image object, its mode is set to F, which is used for 32-bit floating point pixels.
Now, the documentation on Image.show tells us, for example:
On Windows, the image is opened with the standard PNG display utility.
It remains unclear, in which file format(?) the image is actually displayed. Seemingly, it's PNG, at least according to the temporary file name. But, for example, saving some Image object with mode F as PNG or JPG doesn't work! So, it seems, the image must be somehow converted to make it displayable. The first guess is, that some regular 8-bit image is chosen as default, since you get a nearly all black image, indicating that values 0 and maybe 1 are treated as "very dark". And, in fact, when using something like
b=Image.fromarray(b * 255)
the Windows image preview displays a proper image when using b.show().
So, that would be a workaround for the displaying.
Nevertheless, if you want to save the image instead, you don't necessarily need that conversion, but just need to use a proper file format to store those 32-bit information, TIFF for example:
b=Image.fromarray(b)
b.save('b.tiff')

Python Why does Image differ after tobytes() frombytes()?

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)

Working with truncated images with PIL

I am trying to get the Python 2.7 PIL Library to work with JPEG images that are only available as a stream coming from a HDD image and are not complete.
I have set the option:
ImageFile.LOAD_TRUNCATED_IMAGES = True
And load the stream as far as it is available (or better said: as far as I am 100% sure that this data is still a image, not some other file type). I have tested different things and as far as I can tell (for JPEGs) PIL only accepts it as a valid JPEG Image if it finds the 0xFFDA (Start of Scan Marker). This is a short example of how I load the data:
from PIL import Image
from StringIO import StringIO
ImageFile.LOAD_TRUNCATED_IMAGES = True
with open("/path/to/image.raw", 'rb') as fp:
fp.seek("""jump to position in image where JPEG starts""")
data = fp.read("""number of bytes I know that those belong to that jpeg""")
img = Image.open(StringIO(data)) # This would throw exception if the data does
# not contain the 0xffda marker
pixel = img.load() # Would throw exception if LOAD_TRUNCATED_IMAGES = false
height,width = img.size
for i in range(height):
for j in range(width):
print pixel[i,j]
On the very last line I expected (or hoped) to see at least the read pixel data to be displayed. But for every pixel it returns (0,0,0).
The Question: Is what I am trying here not possible with PIL?
Some weeks ago I tried the same with a image file I truncated myself, simply by cutting data from it with an editor. It worked for the pixel-data that was available. As soon as it reached a pixel that I cut off, the program threw an exception (I will try this again later today to make sure that I am not remembering wrong).
If somebody is wondering why I am doing this: I need to make sure that the image/picture inside that hdd image is in consecutive blocks/clusters and is not fragmented. To make sure of this I wanted to use pixel matching.
EDIT:
I have tried it again and this is what I have seen.
I opened a truncated image in GIMP and it showed me a few pixel lines in the upper part, but PIL was not able to at least give me the RGB values of those pixels. It always returns (0,0,0).
I made the image slightly bigger such that the lower 4/5 of the image was not visible, but that was enough for PIL to show me the RGB values that were available. Everything else was (0,0,0).
I am still not 100% sure whether PIL can show me the RGB values, even if only view pixel-data is available.
I would try it with an uncompressed format like TGA. JPG being a compressed format may not make any sense to extract pixels from an incomplete image. JPEG actually stores the parameters for equations that describe the image, not pixel values. When you query a JPEG for a pixel value it evaluates the equations at that point and returns the result.
I have the same problem with Pillow==9.2.0
Let's downgrade to Pillow==8.3.2 and it works.
I don't really know about streaming, but I think that you simply cannot access rgb value the way you do.
Try:
rgb_im = img.convert('RGB')
r, g, b = rgb_im.getpixel((i, j))

PIL jpeg, how to preserve the pixel color

I have some experiments with JPEG, the doc said "100 completely disables the JPEG quantization stage."
However, I still got some pixel modification during saving. Here is my code:
import Image
red = [20,30,40,50,60,70];
img = Image.new("RGB", [1, len(red)], (255,255,255))
pix = img.load()
for x in range(0,len(red)):
pix[0,x] = (red[x],255,255)
img.save('test.jpg',quality=100)
img = Image.open('test.jpg')
pix = img.load()
for x in range(0,len(red)):
print pix[0,x][0],
I got unexpected output: 22 25 42 45 62 65
What should I do to preserve the pixel value ? Please note that I also tried with PHP using imagejpeg and It gives me the correct value when quality=100.
I can use png to preserve, but I want to know the reason behind this and if there is any option to avoid
JPEG consists of many different steps, many of which introduce some loss. By using a sample image containing only red, you've probably run across the worst offender - downsampling or chroma subsampling. Half of the color information is thrown away because the eye is more sensitive to brightness changes than color changes.
Some JPEG encoders can be configured to turn off subsampling, including PIL and Pillow by setting subsampling=0. In any case it won't give you a completely lossless file since there are still other steps that introduce a loss.
JPEG will always carry risk of lossyness, see Is Jpeg lossless when quality is set to 100?.
Your best bet is to use another format, especially if your experiments are for science :) Even if you're forced to start with JPEG (which seems unlikely) you should immediately convert to a lossless format for any kind of analysis and modification.
If you really want to try lossless JPEG work with python you can try jpegtran, "the lossless jpeg image transformation software from the Independent Jpeg Group", but as #Mark notes, this won't get you very far.
By the way, quantization is used in lossy or lossless compression alike, so my guess is that
...100 completely disables the JPEG quantization stage.[1]
simply means that it's not compressed at all.
Believe I've figured out how to keep the current color subsampling and other quality details:
from PIL import Image, JpegImagePlugin as JIP
img = Image.open(filename)
img.save(
filename + '2.jpg', # copy
format='JPEG',
exif=img.info['exif'], # keep EXIF info
optimize=True,
qtables=img.quantization, # keep quality
subsampling=JIP.get_sampling(img), # keep color res
)
Per https://www.exiv2.org/tags.html I've found that the YCbCrSubSampling tag is not kept in EXIF in JPEG files:
In JPEG compressed data a JPEG marker is used
instead of this tag.
This must be why there is another function in a seemingly out of the way place to to grab it.
(Believe I found it here: https://newbedev.com/determining-jpg-quality-in-python-pil)

How can one perform color transforms with ICC profiles on a set of arbitrary pixel values (not on an image data structure)?

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.

Categories