[Numpy-discussion] saving and loading as PNG

Martin Teichmann lkb.teichmann at gmail.com
Thu Nov 8 10:25:57 EST 2007


Hi all,

i am working with numpy and mostly 16 bit matrices, and so I was looking
for a standard way of saving them. I found that PNG actually supports
that, but I could not find a way to use this feature from within
python. I thought that it actually is a cool way of storing matrices
and thus I wrote a little pyrex program that does the job. I figured
this might be interesting to a broader audience, so I posted it here,
maybe for inclusion in numpy?

Some more thoughts:
* Other implementations: There is other people who have done such a thing.
the PIL knows how to read and write PNG, but only 8 bit. The same holds
for matplotlib.

* Problems: until now, numpy does not link agains libpng. This should not
be a problem on linux-like systems, as those normally all have libpng 
installed. Windows however, I don't know. Same thing with pyrex. 
Numpy doesn't use pyrex until now.

* other places to put: numpy could be considered to basic to have such
a functionality, one could put it in scipy. Well, but png is not really
a _scientific_ application (even though I personally use it to do science).
Matplotlib would be another nice place to put this feature, big advantage
is that matplotlib already links against libpng.

So, now I shared my thoughts with you, comments?

Greetings,

Martin

And here the code (188 lines are hopefully ok for a mailinglist?)

cimport c_numpy
import numpy

cdef extern from "stdlib.h":
    cdef void *malloc(int)
    cdef void free(void *)

cdef extern from "stdio.h":
    cdef struct file:
        pass
    ctypedef file FILE
    cdef FILE *fopen(char *, char *)
    cdef int fclose(FILE *)

cdef extern from "errno.h":
    cdef int errno

cdef extern from "png.h":
    cdef struct jmp_buf:
        pass
    cdef int setjmp(jmp_buf)
    cdef struct png_struct:
        jmp_buf jmpbuf
    ctypedef png_struct *png_structp

    cdef struct png_info:
        int width
        int height
        int bit_depth
        int color_type
        int channels
    ctypedef png_info *png_infop

    cdef struct png_text:
        int compression
        char *key
        char *text
        int text_length
    ctypedef png_text *png_textp

    cdef png_structp png_create_read_struct(char *, void *, void *, void*)
    cdef void png_init_io(png_structp, FILE *)
    cdef void png_set_sig_bytes(png_structp, int)
    cdef png_infop png_create_info_struct(png_structp)
    cdef void png_read_info(png_structp, png_infop)
    cdef int png_set_interlace_handling(png_structp)
    cdef void png_read_update_info(png_structp, png_infop)
    cdef void png_read_image(png_structp, unsigned char **)
    cdef void png_read_png(png_structp, png_infop, int, void *)
    cdef void png_read_end(png_structp, png_infop)
    cdef void png_destroy_read_struct(png_structp *, png_infop *, void *)
    cdef png_structp png_create_write_struct(char *, void *, void *, void*)
    cdef void png_set_IHDR(png_structp, png_infop, unsigned int,
        unsigned int, int, int, int, int, int)
    cdef void png_write_info(png_structp, png_infop)
    cdef void png_write_image(png_structp, unsigned char **)
    cdef void png_write_end(png_structp, png_infop)
    cdef void png_destroy_write_struct(png_structp *,png_infop *)
    cdef void png_set_swap(png_structp)
    cdef void png_set_rows(png_structp, png_infop, unsigned char **)
    cdef void png_write_png(png_structp, png_infop, int, void *)
    cdef void png_set_text(png_structp, png_infop, png_textp, int)
    cdef int PNG_COLOR_TYPE_GRAY
    cdef int PNG_COLOR_TYPE_PALETTE
    cdef int PNG_COLOR_TYPE_RGB
    cdef int PNG_COLOR_TYPE_RGB_ALPHA
    cdef int PNG_COLOR_TYPE_GRAY_ALPHA
    cdef int PNG_INTERLACE_NONE
    cdef int PNG_COMPRESSION_TYPE_BASE
    cdef int PNG_FILTER_TYPE_BASE
    cdef int PNG_TRANSFORM_IDENTITY
    cdef int PNG_TRANSFORM_SWAP_ENDIAN
    cdef char *PNG_LIBPNG_VER_STRING

def load(fn):
    """ load a PNG file into a NumPy array.

    This function can read all kinds of PNG images in a senseful way:
    Depending on the number of bits per pixel, we chose numpy.bool,
    numpy.unit8 or numpy.uint16 as base type. For gray scale images or
    pallettes, this returns a 2-D array, for everything else a 3-D array,
    where the third dimension has a size of 2 for gray+alpha images, 3 for
    RGB and 4 for RGBA images.  """

    cdef FILE *f
    f = fopen(fn, "rb")
    if f == NULL:
        raise IOError(errno)
    cdef png_structp p
    p = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)
    if setjmp(p.jmpbuf):
        fclose(f)
        raise RuntimeError("libpng reported error")
    cdef png_infop i
    i = png_create_info_struct(p)
    png_init_io(p, f)
    png_read_info(p, i)

    if i.color_type == PNG_COLOR_TYPE_GRAY:
        shape = (i.height, i.width)
    elif i.color_type == PNG_COLOR_TYPE_GRAY_ALPHA:
        shape = (i.height, i.width, 2)
    elif i.color_type == PNG_COLOR_TYPE_PALETTE:
        shape = (i.height, i.width)
    elif i.color_type ==  PNG_COLOR_TYPE_RGB:
        shape = (i.height, i.width, 3)
    elif i.color_type == PNG_COLOR_TYPE_RGB_ALPHA:
        shape = (i.height, i.width, 4)

    if i.bit_depth == 1: dtype = numpy.bool
    elif i.bit_depth == 8: dtype = numpy.uint8
    elif i.bit_depth == 16: dtype = numpy.uint16

    png_set_interlace_handling(p)
    png_read_update_info(p, i)

    r = numpy.zeros(shape, dtype)
    cdef c_numpy.ndarray s
    s = r
    cdef unsigned char **rp
    rp = <unsigned char **> malloc(sizeof(char *) * i.height)
    cdef int j
    for j from 0 <= j < i.height:
        rp[j] = <unsigned char *> &s.data[(j *
            i.channels * i.width * i.bit_depth) / 8]
    png_set_swap(p)
    png_read_image(p, rp)
    free(rp)
    png_destroy_read_struct(&p, &i, NULL)
    fclose(f)
    return r

def save(fn, data):
    """ Save a NumPy array to PNG

    This function saves the NumPy array in data to the file fn. It saves
    the data the best way it can: 2-D arrays are saved as gray scale
    images, 3-D arrays are saved as gray+alpha, rgb or rgba depending on
    the size of the 3rd dimension.  """

    cdef c_numpy.ndarray d
    d = numpy.ascontiguousarray(data)
    cdef int j
    cdef int width
    if data.itemsize > 2:
        raise ValueError("PNG cannot handle BPP > 16")
    if data.ndim == 2:
        width = data.shape[1] * data.itemsize
    elif data.ndim == 3:
        width = data.shape[2] * data.shape[1] * data.itemsize
    else:
        raise ValueError("Can only save 2-D or 3-D arrays to PNG")
    cdef FILE *f
    f = fopen(fn, "wb")
    cdef png_structp p
    p = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)

    cdef unsigned char **rp
    rp = NULL
    if setjmp(p.jmpbuf):
        free(rp)
        fclose(f)
        raise RuntimeError("libpng reported error")
    cdef png_infop i
    i = png_create_info_struct(p)
    png_init_io(p, f)
    if data.ndim == 2:
        color = PNG_COLOR_TYPE_GRAY
    elif data.shape[2] == 2:
        color = PNG_COLOR_TYPE_GRAY_ALPHA
    elif data.shape[2] == 3:
        color = PNG_COLOR_TYPE_RGB
    elif data.shape[2] == 4:
        color = PNG_COLOR_TYPE_RGB_ALPHA
    else:
        raise ValueError("Third dimension must be <= 4 for saving to PNG")
    png_set_IHDR(p, i, data.shape[1], data.shape[0], data.itemsize*8,
        color, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
        PNG_FILTER_TYPE_BASE)

    rp = <unsigned char **> malloc(sizeof(char *) * data.shape[0])
    for j from 0 <= j < data.shape[0]:
        rp[j] = <unsigned char *> &d.data[j * width]
    png_set_rows(p, i, rp)
    png_write_png(p, i, PNG_TRANSFORM_SWAP_ENDIAN, NULL)
    free(rp)
    png_destroy_write_struct(&p, &i)
    fclose(f)





More information about the NumPy-Discussion mailing list