Hi everyone,
After getting fed severely fed up with Matlab in recent months I
downloaded Python, Numpy and Matplotlib to try out as an alternative. So
far I'm pleasantly impressed, even if building from source on Mac OS X is
an experience ;) However, I have discovered a couple of problems with
Matplotlib's imread() function and, shall we say, 'esoteric' PNG files. My
research group uses a 12-bit CCD controlled through Labview to capture
high dynamic range image stacks. Often there are ~30 images in a single
data set. These get read into Matlab in one go for processing as a stack.
I tried converting my code over to Python but, after digging through the
_png.cpp source file found the following that are problems from my point
of view:
1) All .png files in imread() are converted to 4-plane RGBA files
regardless of original format. I would prefer greyscale images to return a
single plane.
2) 16-bit PNGs are stripped to 8 bit, losing any extra precision.
3) The significant bits option in the PNG header was not being checked.
Our camera software will automatically save the PNGs at the maximum
bit-depth required to cover the dynamic range in the image, and can sum
images before saving, so pixels can be anywhere from 6- to 16-bits (at
least those are the values I have observed whilst using the camera).
I have attached the results of an svn diff after I made an attempt at
correcting these issues. This is the first time I have contributed to an
open source project, so am not sure of the etiquette here. Also, I have
only had Python and Matplotlib for a fortnight so am still unfamiliar with
them and haven't programmed with libpng before so I apologise in advance
if there any stupid mistakes in my code. I am aware that imread() is a
pretty important function in Matplotlib and hence any changes I suggest
would need comprehensive testing. In brief, I made the following changes:
1) Removed the libpng 16- to 8-bit strip command
2) Added in the libpng calls to cope with variable bit-depth and
converting 16-bit pngs from big-endian to little-endian
3) Added a large if/else if stucture at the end to return different
PyArrays depending on the input data. RGBA images are 4 plane, RGB 3 plane
and greyscale 1 plane. Numbers within these are still floats scaled
between 0 and 1, except 16-bit images which are doubles (Are floats
preferable to doubles?). The scaling factor is worked out from the
significant bits struct.
There are still a couple of issues with this code, mainly that I have only
tested it with PNGs I have lying to hand, all of which display correctly
with imshow() and I have not made much attempt at supporting 1,2 and 4 bit
pngs. I'm personally not a big fan of large if/else ifs but in this case
thought it was the clearest way to return the different types.
I would finally like to point out that no software I have used so far has
been able to read the images produced by this camera completely correctly.
PIL interprets the variable bit-depth images as binary (?!), and we had to
write a wrapper round the Matlab imread() function using iminfo() as
Matlab ignores the significant bits setting as well.
Oh, almost forgot, I'm compiling on Mac OS X 10.5, Python 2.6.1
(r261:67515) and the latest Numpy and Matplotlib SVN checkouts.
Kind regards,
Tobias Wood
Index: src/_png.cpp
===================================================================
--- src/_png.cpp (revision 7022)
+++ src/_png.cpp (working copy)
@@ -208,38 +208,34 @@
png_init_io(png_ptr, fp);
png_set_sig_bytes(png_ptr, 8);
-
png_read_info(png_ptr, info_ptr);
png_uint_32 width = info_ptr->width;
png_uint_32 height = info_ptr->height;
- bool do_gray_conversion = (info_ptr->bit_depth < 8 &&
- info_ptr->color_type == PNG_COLOR_TYPE_GRAY);
int bit_depth = info_ptr->bit_depth;
- if (bit_depth == 16) {
- png_set_strip_16(png_ptr);
- } else if (bit_depth < 8) {
+
+ if (bit_depth < 8) {
png_set_packing(png_ptr);
}
- // convert misc color types to rgb for simplicity
- if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
- info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
- png_set_gray_to_rgb(png_ptr);
- } else if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
+ // If sig bits are set, shift data
+ png_color_8p sig_bit;
+ if (png_get_sBIT(png_ptr, info_ptr, &sig_bit))
+ png_set_shift(png_ptr, sig_bit);
+
+ // Convert big endian to little
+ if (bit_depth == 16)
+ png_set_swap(png_ptr);
+
+ // Convert palletes to full RGB
+ if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png_ptr);
}
png_set_interlace_handling(png_ptr);
png_read_update_info(png_ptr, info_ptr);
- bool rgba = info_ptr->color_type == PNG_COLOR_TYPE_RGBA;
- if ( (info_ptr->color_type != PNG_COLOR_TYPE_RGB) && !rgba) {
- std::cerr << "Found color type " << (int)info_ptr->color_type <<
std::endl;
- throw Py::RuntimeError("_image_module::readpng: cannot handle color_type");
- }
-
/* read file */
if (setjmp(png_jmpbuf(png_ptr)))
throw Py::RuntimeError("_image_module::readpng: error during read_image");
@@ -255,35 +251,61 @@
npy_intp dimensions[3];
dimensions[0] = height; //numrows
dimensions[1] = width; //numcols
- dimensions[2] = 4;
+ dimensions[2] = 1; //Dummy for now, set to 3/4 for RGB/RGBA
- PyArrayObject *A = (PyArrayObject *) PyArray_SimpleNew(3, dimensions,
PyArray_FLOAT);
+ PyArrayObject *A;
- if (do_gray_conversion) {
- float max_value = (float)((1L << bit_depth) - 1);
+ // Deal with each colour type and bit depth separately to support different
return types
+ if ((bit_depth == 16) && (info_ptr->color_type == PNG_COLOR_TYPE_GRAY)) {
+ A = (PyArrayObject *) PyArray_SimpleNew(2, dimensions, PyArray_DOUBLE);
+ double max_value = (1 << (sig_bit->gray));
for (png_uint_32 y = 0; y < height; y++) {
png_byte* row = row_pointers[y];
- for (png_uint_32 x = 0; x < width; x++) {
- float value = row[x] / max_value;
+ for (png_uint_32 x = 0; x < width; x++) {
size_t offset = y*A->strides[0] + x*A->strides[1];
- *(float*)(A->data + offset + 0*A->strides[2]) = value;
- *(float*)(A->data + offset + 1*A->strides[2]) = value;
- *(float*)(A->data + offset + 2*A->strides[2]) = value;
- *(float*)(A->data + offset + 3*A->strides[2]) = 1.0f;
+ *(double*)(A->data + offset) =
static_cast<double>(reinterpret_cast<png_uint_16*>(row)[x]) / max_value;
}
}
- } else {
+ } else if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY) {
+ A = (PyArrayObject *) PyArray_SimpleNew(2, dimensions, PyArray_FLOAT);
+ float max_value = ((1 << bit_depth) - 1);
+ for (png_uint_32 y = 0; y < height; y++) {
+ png_byte* row = row_pointers[y];
+ for (png_uint_32 x = 0; x < width; x++) {
+ size_t offset = y*A->strides[0] + x*A->strides[1];
+ *(float*)(A->data + offset) = static_cast<float>(row[x]) /
max_value;
+ }
+ }
+ } else if ((bit_depth == 8) && (info_ptr->color_type ==
PNG_COLOR_TYPE_RGBA)) {
+ dimensions[2] = 4;
+ A = (PyArrayObject *) PyArray_SimpleNew(3, 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++) {
- png_byte* ptr = (rgba) ? &(row[x*4]) : &(row[x*3]);
+ png_byte* ptr = &(row[x*4]);
size_t offset = y*A->strides[0] + x*A->strides[1];
- *(float*)(A->data + offset + 0*A->strides[2]) = (float)(ptr[0]/255.0);
- *(float*)(A->data + offset + 1*A->strides[2]) = (float)(ptr[1]/255.0);
- *(float*)(A->data + offset + 2*A->strides[2]) = (float)(ptr[2]/255.0);
- *(float*)(A->data + offset + 3*A->strides[2]) = rgba ?
(float)(ptr[3]/255.0) : 1.0f;
+ *(float*)(A->data + offset + 0*A->strides[2]) = (float)(ptr[0]) / 255.;
+ *(float*)(A->data + offset + 1*A->strides[2]) = (float)(ptr[1]) / 255.;
+ *(float*)(A->data + offset + 2*A->strides[2]) = (float)(ptr[2]) / 255.;
+ *(float*)(A->data + offset + 3*A->strides[2]) = (float)(ptr[3]) / 255.;
}
}
+ } else if ((bit_depth == 8) && (info_ptr->color_type == PNG_COLOR_TYPE_RGB))
{
+ dimensions[2] = 3;
+ A = (PyArrayObject *) PyArray_SimpleNew(3, 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++) {
+ png_byte* ptr = &(row[x*3]);
+ size_t offset = y*A->strides[0] + x*A->strides[1];
+ *(float*)(A->data + offset + 0*A->strides[2]) = (float)(ptr[0])
/ 255.;
+ *(float*)(A->data + offset + 1*A->strides[2]) = (float)(ptr[1])
/ 255.;
+ *(float*)(A->data + offset + 2*A->strides[2]) = (float)(ptr[2])
/ 255.;
+ }
+ }
+ } else {
+ std::cerr << "Found unsupported color type " <<
(int)info_ptr->color_type << std::endl;
+ throw Py::RuntimeError("_image_module::readpng: cannot handle color_type");
}
//free the png memory
------------------------------------------------------------------------------
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users