Dear list,
Back in April I submitted a patch that allowed imread() to correctly read
PNGs that have odd bit-depths, ie not 8 or 16 (I actually submitted that
to the Users list as I was unsure of protocol). There were a couple of
things I left unfinished that I've finally got round to looking at again.
The main remaining issue for me is that PNG specifies that all bit depths
should be scaled to have the same maximum brightness, so that a value of
8191 in an 13-bit image is displayed the same as 65535 in a 16-bit image.
Unfortunately, the LabView drivers for the 12-bit CCD in our lab do not
follow this convention. A higher bit-depth from this setup means the image
was brighter in an absolute sense and no scaling takes place. So this is
not an error with Matplotlib as such, but more about having a decent way
to handle iffy PNGs. It is worth noting that Matlab does not handle these
PNGs well either (We have to query the image file using iminfo and then
correct it) and PIL ignores anything above 8-bits as far as I can tell.
A simple method, in my mind, and originally suggested by Andrew Straw is
to add a keyword argument to imread() that indicates whether a user wants
floats scaled between 0 and 1, or the raw byte values which they can then
scale as required. This then gets passed to read_png(), which does the
scaling if necessary and if not returns an array of UINT16s. I wrote a
patch that does this, changing both image.py and _png.cpp. I'm very much
open to other suggestions, as I didn't particularly want to fiddle with a
core function like imread() and I'm fairly new to Python. In particular I
have not changed anything to do with PIL - although it would not be much
work to update pil_to_array() to follow the same behaviour as read_png().
I have tested this with the pngsuite.py*, and if desired I can submit an
extended version of this that tests the extended bit-depth images from the
PNG suite.
Thanks in advance,
Toby Wood
* My patch also includes a minor change to pngsuite.py which was throwing
a deprecation warning about using get_frame() istead of patchIndex: src/_png.cpp
===================================================================
--- src/_png.cpp (revision 7230)
+++ src/_png.cpp (working copy)
@@ -178,8 +178,11 @@
Py::Object
_png_module::read_png(const Py::Tuple& args) {
- args.verify_length(1);
+ args.verify_length(1,2);
std::string fname = Py::String(args[0]);
+ bool raw = false;
+ if (args.length() == 2)
+ raw = Py::Boolean(args[1]);
png_byte header[8]; // 8 is the maximum size that can be checked
@@ -213,16 +216,21 @@
png_uint_32 width = info_ptr->width;
png_uint_32 height = info_ptr->height;
+ // Bit depth can be 1, 2, 4, 8, or 16
int bit_depth = info_ptr->bit_depth;
-
+ double max_value = (1 << bit_depth) - 1; // For scaling later
// Unpack 1, 2, and 4-bit images
if (bit_depth < 8)
png_set_packing(png_ptr);
- // If sig bits are set, shift data
+ // If a pngs max value does not use the full bit depth, then values are
shifted up during writing.
+ // This shifts them back and then recalculates max_value
png_color_8p sig_bit;
if ((info_ptr->color_type != PNG_COLOR_TYPE_PALETTE) &&
png_get_sBIT(png_ptr, info_ptr, &sig_bit))
+ {
png_set_shift(png_ptr, sig_bit);
+ max_value = (1 << sig_bit->red) - 1; // RGB values appear to always be
equal
+ }
// Convert big endian to little
if (bit_depth == 16)
@@ -260,12 +268,13 @@
dimensions[2] = 3; //RGB images
else
dimensions[2] = 1; //Greyscale images
+
//For gray, return an x by y array, not an x by y by 1
int num_dims = (info_ptr->color_type & PNG_COLOR_MASK_COLOR) ? 3 : 2;
+
+ // If read mode is raw give back ints, otherwise float between 0 and 1
+ PyArrayObject *A = (PyArrayObject *) PyArray_SimpleNew(num_dims, dimensions,
(raw) ? PyArray_UINT16 : PyArray_FLOAT);
- double max_value = (1 << ((bit_depth < 8) ? 8 : bit_depth)) - 1;
- PyArrayObject *A = (PyArrayObject *) PyArray_SimpleNew(num_dims, dimensions,
PyArray_FLOAT);
-
for (png_uint_32 y = 0; y < height; y++) {
png_byte* row = row_pointers[y];
for (png_uint_32 x = 0; x < width; x++) {
@@ -273,13 +282,13 @@
if (bit_depth == 16) {
png_uint_16* ptr = &reinterpret_cast<png_uint_16*> (row)[x *
dimensions[2]];
for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++)
- *(float*)(A->data + offset + p*A->strides[2]) = (float)(ptr[p]) /
max_value;
+ raw ? *reinterpret_cast<unsigned short*>(A->data +
offset + p*A->strides[2]) = ptr[p] :
+ *reinterpret_cast<float*>(A->data + offset +
p*A->strides[2]) = static_cast<float>(ptr[p]) / max_value;
} else {
png_byte* ptr = &(row[x * dimensions[2]]);
for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++)
- {
- *(float*)(A->data + offset + p*A->strides[2]) = (float)(ptr[p]) /
max_value;
- }
+ raw ? *reinterpret_cast<unsigned short*>(A->data + offset +
p*A->strides[2]) = ptr[p] :
+ *reinterpret_cast<float*>(A->data + offset +
p*A->strides[2]) = static_cast<float>(ptr[p]) / max_value;
}
}
}
Index: lib/matplotlib/image.py
===================================================================
--- lib/matplotlib/image.py (revision 7230)
+++ lib/matplotlib/image.py (working copy)
@@ -739,7 +739,7 @@
rows, cols, buffer = im.as_rgba_str()
_png.write_png(buffer, cols, rows, fname)
-def imread(fname):
+def imread(fname, raw=False):
"""
Return image file in *fname* as :class:`numpy.array`.
@@ -773,7 +773,7 @@
return im
handler = handlers[ext]
- return handler(fname)
+ return handler(fname, raw)
def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
origin=None):
Index: examples/tests/pngsuite/pngsuite.py
===================================================================
--- examples/tests/pngsuite/pngsuite.py (revision 7230)
+++ examples/tests/pngsuite/pngsuite.py (working copy)
@@ -24,6 +24,6 @@
cmap = cm.gray
plt.imshow(data, extent=[i,i+1,0,1], cmap=cmap)
-plt.gca().get_frame().set_facecolor("#ddffff")
+plt.gca().patch.set_facecolor("#ddffff")
plt.gca().set_xlim(0, len(files))
plt.show()
------------------------------------------------------------------------------
Are you an open source citizen? Join us for the Open Source Bridge conference!
Portland, OR, June 17-19. Two days of sessions, one day of unconference: $250.
Need another reason to go? 24-hour hacker lounge. Register today!
http://ad.doubleclick.net/clk;215844324;13503038;v?http://opensourcebridge.org
_______________________________________________
Matplotlib-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel