[Image-SIG] Re: Help wanted: sensible way to scale 16-bit grayscaleimage

Fredrik Lundh fredrik at pythonware.com
Wed Dec 15 20:42:34 CET 2004

Russell E. Owen wrote:

> I had hoped to use an LUT to directly map a 32-bit floating point image
> (or 16 bit integer) to 8 bit color or grayscale (for now grayscale is
> fine, though I hope to support psuedocolor at some point). I could then
> recompute the LUT as needed (e.g. if the user asked for a different
> scaling function or contrast or...).
> Unfortunately, I have not been able to figure out how to use an LUT.
> Despite the following hopeful statement in the Concepts section of the
> Handbook:
>  The mode of an image defines the type and depth of a pixel
>   in the image. The current release supports the following
>  standard modes:
>  ...
>  * P (8-bit pixels, mapped to any other mode using a colour palette)
> it appears that palettes can only be attached to L or P images. I hope
> I'm missing something obvious in the use of palettes.

lookup table (LUT) != palette.

the "point" method replaces every pixel in the image by the corresponding
value from the given lookup table:

    for each pixel in image:
        new pixel value = LUT[old pixel value]

"point" with a lookup table is currently only supported for 8-bit images.

a palette maps colour indexes to RGB values.  if you attach an palette to an
8-bit grayscale image (mode "L"), it becomes a palette image (mode "P"), but
it still only contains 8-bit values.

if you have 16-bit or 32-bit data, you can use the "getextrema" method to find
the lowest and highest value in the image, calculate a suitable linear transform to
bring the values into a 256-value range, and use "point" with a lambda expression
to convert the values to 0-255.  you can then convert the resulting image to "L",
and optionally attach a psuedo-color palette to it to get a "P" image.

or, in code:

        lo, hi = im.getextrema()
        scale  = 255.0 / (hi - lo)
        offset = -lo * scale
        im = im.point(lambda v: v * scale + offet)
        im = im.convert("L")

in older Python versions, replace the point call with:

        im = im.point(lambda v, s=scale, o=offset: v * s + o)

this snippet simply maps the darkest pixel value to 0, and the lightest to 255.
you may want to adjust the lo/hi values before calculating the coefficients.

also note that this only works well if the source data is relatively linear; there's no
way to use a non-linear transform on I or F data in the current version of PIL.


More information about the Image-SIG mailing list