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

Reply via email to