I would like to process a 12-bit grayscale image saved in png format. A monochromatic 12-bit camera was used as the signal source. The image file was created with NI Vision IMAQ Write File2.vi
. The maximum pixel value is 419 counts. When I load the image with Python3 imageio-2.32.0
imageio.imread("testimagefile")
, it seems that the image is scaled automatically, the maximum value is now 55019 counts. How can I keep the original grayscale value?
imageio.imread(): Retains the original grayscale values when reading a png file
192 views Asked by AlexK AtThere are 2 answers
Updated Code
#!/usr/bin/env python3
from pathlib import Path
# Load PNG as bytes
png = Path('image.png').read_bytes()
# Find "sBIT" chunk
sBIToffset = png.find(b'sBIT')
if sBIToffset:
# 4 bytes before "sBIT" tell us how many values there are - could be 1-4 values
nValues = int.from_bytes(png[sBIToffset-4:sBIToffset], byteorder="big")
values = list(png[sBIToffset+4:sBIToffset+4+nValues])
print(f'Found sBIT chunk at offset: {sBIToffset} with value(s): {values}')
# Find "scAl" chunk
scAloffset = png.find(b'scAl')
if scAloffset:
scAl = int.from_bytes(png[scAloffset+4:scAloffset+6], byteorder="big")
value = 65536 - scAl
print(f'Found scAl chunk at offset: {scAloffset} with value: {value}')
Sample Output
Found sBIT chunk at offset: 37 with value(s): [11]
Found scAl chunk at offset: 64 with value: 1000
Original Answer
This is an alternative to the (IMHO really rather poor) implementation of finding the sBIT
chunk and extracting the scale factors.
PNG chunks look like this:
Source: Wikipedia
The sBIT
chunk looks like this in hex:
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
00000010: 0000 056c 0000 040e 1000 0000 003a b9f2 ...l.........:..
00000020: 5200 0000 0173 4249 5409 910d 6b0f 0000 R....sBIT...k...
In hex, the 73 42 49 54
(in the middle of the third line) represents sBIT
, so the length is the 4 bytes before that, i.e. 00 00 00 01
and that means there is 1 value in the chunk - which is to be expected in a greyscale image. There might be up to 4 values depending on the image type (grey, grey+alpha, RGB, RGBA). That means only 1 byte is used in this image. That byte is 09
meaning the value is 9.
As such, you can more simply extract the sBIT(s) like this:
#!/usr/bin/env python3
from pathlib import Path
# Load PNG as bytes and find "sBIT" chunk
png = Path('120130.png').read_bytes()
sBIToffset = png.find(b'sBIT')
if sBIToffset:
# 4 bytes before "sBIT" tell us how many values there are - could be 1-4 values
nValues = int.from_bytes(png[sBIToffset-4:sBIToffset], byteorder="big")
values = list(png[sBIToffset+4:sBIToffset+4+nValues])
print(f'Found sBIT chunk at offset: {sBIToffset} with value(s): {values}')
Note that you can detect the presence of rare chunks in a PNG file using:
pngcheck -vv SOMEIMAGE.PNG
You can also grep
them:
grep sBIT SOMEIMAGE.PNG
I describe the other implementation as "rather poor" because it spends its time shuffling the entire content of the file around in memory and doesn't handle multiple significant bits for multi-channel images - it also reads the file twice and....
I am aware of that my code is also imperfect insofar as it doesn't check the CRC, but I didn't want to muddy the code. If checking the CRC is important, my answer here shows how to do that.
I finally got an answer on the NI-Forum. Many thanks to Andrew Dmitriev.
Here is the code using openCV by dgagnon05:
---------- UPDATE ----------
After answer from @Mark Setchell the better solution may be: