Using -clut to convert 16-bit image to 8 bits (solved)

Questions and postings pertaining to the usage of ImageMagick regardless of the interface. This includes the command-line utilities, as well as the C and C++ APIs. Usage questions are like "How do I use ImageMagick to create drop shadows?".
Post Reply
headcount

Using -clut to convert 16-bit image to 8 bits (solved)

Post by headcount »

I'm relatively new to ImageMagick, but I couldn't find a straightforward answer to this question after some searching.

I have a greyscale 16-bit image with some max value n, and I'd like to convert it to an 8-bit image. I can create an intermediate PGM file to use for the lookup values (thanks to this post), but I'm not sure what exactly the intermediate lut image should look like. Should it be a 1x256 array of the pixel values spanning from 0 to n with whatever redistribution profile I've computed?

Thanks in advance.
Last edited by headcount on 2010-04-01T12:43:39-07:00, edited 1 time in total.
User avatar
fmw42
Posts: 25562
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Using -clut to convert 16-bit image to 8 bits

Post by fmw42 »

If you want to keep the range in proportion, then just

convert image16bit -depth 8 image8bit

if you want to scale to full dynamic range at the same time

convert image16bit -auto-level -depth 8 image8bit

see

-depth
-auto-level

http://www.imagemagick.org/script/comma ... ptions.php
headcount

Re: Using -clut to convert 16-bit image to 8 bits

Post by headcount »

Thank you for the suggestion, but I was not clear enough in my original post. I'm performing mathematical operations on the 16-bit image histogram in order to create a custom 16 bit -> 8 bit conversion.

To do this, I follow the following steps:
  1. Calculate the histogram and pipe it to awk
  2. Apply my adjustments to that array (the specific operations don't matter in this case)
  3. Compute the cumulative sum normalized by the maximum pixel value
  4. Multiply the sum by 255 to get an array from 0 to 255 (the number of bins in this array correspond to the number of values in the 16-bit histogram)
  5. Write it to an intermediate PGM file
  6. Apply clut to image
The above steps don't produce my desired 8-bit image, so I feel the clut file must be incorrect. Running the examples on this page show the clut image to be a 256-bin array with values that range from 0 to 65535. However, the Zelda example is an 8-bit RGB image, and I'm operating on a 16-bit greyscale image.

I'm able to generate the same remapping profile and produce valid 8-bit images in Matlab, but am unsure how to apply the same using the ImageMagick.
User avatar
fmw42
Posts: 25562
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Using -clut to convert 16-bit image to 8 bits

Post by fmw42 »

I suspect that you may need to post your code for others to examine. But if you want to convert 16-bit to 8-bit, then it would see that you would need a 65536 long PGM image for the clut image with values ranging from 0 to 255.

Those examples on the page you refer to come from my script, redist. See below. But I simply converted all images to 8-bit histograms to generate the clut (length 256 with values ranging from 0 to quantumrange) as I recall. Thus on Q16 it would work whether the images themselves were 8-bit or 16-bit. The accuracy of a clut of only 256 bins seemed adequate.
User avatar
anthony
Posts: 8883
Joined: 2004-05-31T19:27:03-07:00
Authentication code: 8675308
Location: Brisbane, Australia

Re: Using -clut to convert 16-bit image to 8 bits

Post by anthony »

Note however that while the clut is only 256 bits, the -clut operator does interpolation of the image
it is applied to.

What I means is that if a 16 bit color value falls between the 256 range of pixels (very common) then the actual value assigned will be 'interpolated' from the neighbouring pixels. By default that interpolation is bilinear and can be set or changed according to the -interpolate setting.

Because of this while the clut may be limited to 256, it interpolates for all the other posible color values being assigned. This is how using an image CLUT differs from a more typical application assigning a histogram 'bin' generated CLUT.

If you want actual assignment (just simple round down and pick the 'floor' pixel) set interpolation to 'integer'. For examples of this see IM Examples, Interpolate.
http://www.imagemagick.org/Usage/color/#color_lut
Anthony Thyssen -- Webmaster for ImageMagick Example Pages
https://imagemagick.org/Usage/
headcount

Re: Using -clut to convert 16-bit image to 8 bits

Post by headcount »

Thank you both for your help! I've finally figured it out. The short answer is that the lut file needs to be 256 bins with values ranging from 0 to your maximum 16-bit pixel value. How you shape that curve is up to you. There are many papers with different algorithms for dynamic range reduction.

I was getting stuck because I was producing an a 16-bit image that only used values 0 to 255. This post got me over the hump.

Here is my implementation in bash (heavily cribbed from Fred's 'redist' script). The input file should be a 16-bit grayscale image with at least 12 bits of dynamic range.

Code: Select all

#!/bin/bash

inputImg="input.png"
outImg="output.png"

# perform some arbitrary math on the histogram -- can be whatever algorithm you desire
# in this example, just add pi to each histogram value
adjustedHist=(`convert $inputImg -format "%c" histogram:info:- |\
	tr -cs '0-9\012' ' ' |\
	awk '{ bin[$2] = $1; arrLen=length(bin) }
		END { for (i=0;i<65535;i++) 
				{newVal = bin[i] + 3.14159265; print newVal; } 
			} '`)
				
# find normalized cumulative sum of new histogram
cumSum=(`echo ${adjustedHist[*]} |
	awk ' # first find the sum of histogram counts
	func sum(s){split(s,a);r=0;for(i in a)r+=a[i];return r}{val=sum($0)}	
	END {split($0,inputHist);arrLen=length(inputHist)} 
	END {for (i=1;i<=arrLen;i++) {cumsum += inputHist[i] / val; print cumsum; } } '`) 

# find max pixel value in original image
maxPixel=(`convert $inputImg -format "%[max]" info:-`)

# create remapping function betweeen 0 and max pixel value
remapFun=(`echo ${cumSum[*]} |
	awk -v maxPixel="$maxPixel" '{split($0,inputHist);arrLen=length(inputHist)}
	END {for (i=0;i<arrLen;i++) {remap = int(inputHist[i]*maxPixel); print remap; } } '`) 
	
# rescale remapping function to 256 bins - rough, no interpolation
remapBins=${#remapFun[*]}
spacedRemap=(`echo ${remapFun[*]} | 
		awk -v remapBins="$remapBins" '{split($0,inputHist); binSpace=int(remapBins/256)}
		END {for (i=0;i<=256;i++) {scaleRemap = inputHist[i*binSpace]; print scaleRemap; }}'`)
newBins=${#spacedRemap[*]}

# now convert remapFun into look up table (lut) image
# Use NetPBM (PGM format implied intermediate image)
echo "P2 $newBins 1 65535 ${spacedRemap[*]}" | convert - remap.png

# now apply the lut and save image
# need to multiply the values by 256 to give the image a 16-bit range with only 256 bins
var256=256
var8=8
convert $inputImg remap.png -clut -evaluate multiply $var256 -depth $var8 $outImg

exit 0 

User avatar
anthony
Posts: 8883
Joined: 2004-05-31T19:27:03-07:00
Authentication code: 8675308
Location: Brisbane, Australia

Re: Using -clut to convert 16-bit image to 8 bits (solved)

Post by anthony »

In that script is the lines

Code: Select all

# now convert remapFun into look up table (lut) image
# Use NetPBM (PGM format implied intermediate image)
echo "P2 $newBins 1 65535 ${spacedRemap[*]}" | convert - remap.png
the value 65535 is the Quantum Range of the values in the PGM image. And in this case is
16 bit. If you want to generate a 8bit PGM image with 8 bit values then that value should
be 255.

I have used very unusual Quantum Range value sin PGM images for specific purposes.
for example here I generate (and display) a 'step gradient' with black, white and only two gray values. The 'range' value was chosen to make generating the gradient easy without needing to calculate 1/3 of a not 8-bit (255) or 16-bit (65535) range. I let IM do the calculation appropriately.

Code: Select all

   echo "P2 4 1   3    0 1 2 3" | convert - -scale x200 show:
NOTE: IM can only output PGM at 8 and 16 bit depths. I would love to see some way of specify an odd range (like 3) for such images. See the future proposal at
http://www.imagemagick.org/Usage/bugs/future/#quality
Anthony Thyssen -- Webmaster for ImageMagick Example Pages
https://imagemagick.org/Usage/
Post Reply