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

Ray Pasco pascor at hotpop.com
Fri Dec 17 18:39:23 CET 2004

Date: Thu, 16 Dec 2004 14:27:19 -0800
From: "Russell E. Owen" <rowen at cesmail.net>
Subject: [Image-SIG] Re: Help wanted: sensible way to scale 16-bit
	grayscale	image
To: image-sig at python.org
Message-ID: <rowen-A6690C.14271916122004 at sea.gmane.org>

In article <41C1D38A.90902 at hotpop.com>, Ray Pasco <pascor at hotpop.com> 

>> I believe the response to Russell's question missed the point.  I 
>> believe he wants to change the
>> dynamic range of his images, not produce an image file of a smaller 
>> size, which is the primary
>> goal of producing a paletted (indexed or gray "L"evel) image....

Exactly. My image viewer offers log, sqrt and a few other scaling 
functions which the user may switch between in order to bring out 
different features of interest in the original image.

When a scaling function is selected, an appropriate 8-bit image is 
created from the original data (which is always retained unchanged) and 

Right now I am using brute force to create the 8-bit image -- using 
numarray to apply the scaling function to each pixel to create a scaled 
version to display. I had hoped, instead, to specify the desired scaling 
as an array of 256 values that told PIM how to map values from the 
original image down to 8 bits (thus only computing my scaling function 
at 256 points instead of for every pixel in the original image).

It sounds as if PIM does not have LUT support in that sense, which is a 

# "PIM" ?!  "PIL", right ?

pity as it seems a natural thing to do.

# As far as I know, you're right.  It would be great to have a true LUT transform
# function.  I can easily imagine one for 16-bit unsigned integers, say,
# that consists of a 256 entry table in which each entry consists of two
# values each: a lower and an upper limit in which a transform function would
# use to map that range of original values to a single 8-bit gray level value.
# To be complete, this fuction would have to be able to convert any kind 
# of image (16-bit signed/unsigned, 32-bit signed/unsigned, RGB, floating point,
# etc., etc., to any other kind of image.  However, perhaps only one or two
# of the conversions could be implemented at first, with more as users request.
# This would necessarily be done as a C extension for speed, an area that is
# black magic to me at this time. However, the point() function is not -that-
# slow as to cause a unacceptable time lag for "small" to "medium" sized images.
# But, I'm guessing that your images fall in the "large" to "very large"
# category, making a C-based transform function required rather than a pure Python one.

>> So, Russel, I think this is what you want to do:
>> 1) Create a palette of 256 8-bit integer indices.  This is not a LUT 
>> used as a transform tool.
>> 2) Lets say your image consists of unsigned 16-bit values.  Decide the 
>> ranges of values that will be mapped.
>> E.g., let the range 0-255 map to the new image pallette value of 0, the 
>> original range of 256 .. 511 ==> 1, etc.
>> In other words, the original value range of [0..255, 256..511, ...]  
>> will transform to the gray level values
>> of [0, 1, ...]...
>> 3) Create a brand new L type image.  Install your new pallette into it. 
>>  Examine at each pixel of the original image
>> and map its 16-bit value to a (unique) pallette value.  Write that 
>> pallete value to the pixel value of the new image.
>> You are now done converting the old image to a new L image.  Store,  
>> display or even convert to a new image
>> type as desired.

I'm not sure I get this yet.

I think you are suggesting I start by creating a table that specifies 
how values from the original image are mapped to 8-bit grayscale image, 
e.g. (using your linear example): [0, 256, 512, ...] means display 
values 0-255 as 0, 256-511 as 1, etc.

# No, the table does not transform the original image. It helps to specify
# a new image that was created by your own Python transformation algorithm that you
# apply to your original image to create a new paletted image. 

But then I don't see how to apply it. You suggest:

>>Examine at each pixel of the original image and map its 16-bit value to 
a (unique) pallette value. Write that pallete value to the pixel value 
of the new image.
but is there some efficient way to do this? I suspect a normal python 
loop would be dreadfully slow.

Also, I confess I'm puzzled why you also suggest creating a palette and 
attaching it to the "L" image (maybe this is the heart of what I'm 
missing). I would think that once I had used the table to compute the 
pixel values for the 8-bit "L" image, I'd be done -- and that the proper 
palette for "L" would then be the default palette.

I hope I'm not being too dense.

# I think an explaination about paletted images is in order.  
# By the way, I use the term "paletted image". This is not the "industry standard" 
# terminology, but does characterize the image representation in a clear and exact manner. 
# Imagine an N by M image consisting of 8-bit values. These pixel values are indices
# into a 256-entry array, a.k.a the palette table.  Each palette table entry value 
# (in my example) # represents a gray level image value (intensity value = luminance).  
# So, for example, image location (173, 1407) could contain (have the value of) 
# the palette entry 148, which, in turn, indicates that that all pixels that have 
# the palette index of 148 should have a gray level of 17. So, the image consists
# of an array of palette entries instead of an array of the actual pixel values. 
# Your original images are arrays of pixel values.
# This palette-based image representation is easily extended to RGB and all other image types. 
# In an RGB image, each palette entry consists of an RGB 3-tuple which specifies the 8-bit 
# R, G and B values for all pixels whose values have that palette index. So, color images
# with a "dynamic range" of 16,777,216 possible colors (8-bit R x 8-bit G x 8-bit B) could be 
# represented in a paletted image format consisting of N x M x 8-bit indices and a 768 entry 
# palette. The catch is, and it's a big one, is that the paletted image must be such that 
# it can use only 256 total colors or less (the palette size) out of a possible of 16 million.
# This is why I call the table a "palette" and not a LUT. It is just like a painter's palette.
# Imagine an artist's hand-held palette with 256 cups in it for holding paint mixtures.  
# He starts out with 3 tubes of primary colors R, G and B. He can put any mix of primary colors
# in each cup. The digital world limits this to 16 million possibilities, instead of
# an infinite analog range. But, he has only 256 mixtures of primary colors to choose from which
# to create his final painting.
# In your case, your new palette would consist of 256 shades of gray, from black to white.
# So, a grayscale palette is just a simplified form of a full-color palette representation.
# I just can fathom why an explaination like this can't be found by Googling, or otherwise.
# Everyone is just expected to "know" how paletted images work by osmosis or some other means.
# Hope this clears up any confusion.
# Ray Pasco


-- Russell

# Oh, I almost forgot to say that PIL version 1.1.5b1 has new functions for setting
# and retrieving palettes. 

More information about the Image-SIG mailing list