Monday, November 28, 2011

Exploring colors and grays with ImageJ



 How are stored the grays and colors in a digital image? This simple question is explored in this post thanks to the IJ programming language.

Updated for  ECMAScript2015+, it requires at least version ImageJ 1.51r and Java 9. See this post for update [Link].

1- Reading pixels in an image

Fig.1: Exploring grays and color pixel values with ImageJ
1-1- Function getPixel(...)
In ImageJ, the function for reading pixels is getPixel(x,y) where x,y are the coordinates of the pixel and it returns the decimal pixel value (base 10).

For example, this small script creates a 8-bit 50x50 image colored in medium gray, gets the gray value of the pixel of coordinates (25,25) and finally prints the result in the Log window.

+++ IJ snippet +++ +++ End of IJ snippet +++

In the Log window, the result is displayed:
Pixel value (10): 100

2- Gray-level images

2-1- 8-bit image
If you try with different color values (black (0,0,0)RGB, white (255,255,255)RGB, etc.), the gray level corresponds to a value between 0 and  255 (28-1) and is defined by three identical red, green, and blue values in the function setColor(...).
Note: If you try to define a color like setColor(100,120,130), ImageJ converts this color in a gray level by averaging the three values and in this case, the script prints:
Pixel value (10): 117 equal to (100+120+130)/3
2-2- 16-bit image
If you try with a 16-bit image, modify the first line of the previous script as follows:

IJ.createImage("test", "16-bit Black", 50, 50, 1);

Pixel value (10): 25700

Surprisingly, the result isn't 100. In a 16-bit image, the pixel values are in the range of 0-65535 (216-1), thus, the gray level 100 is rescaled according to this simple formula:

v=100 * 65535/255=25700

2-3- 32-bit floating-point image
Modify the first line of the script to take into account a 32-bit image.

IJ.createImage("test", "32-bit Black", 50, 50, 1);

Pixel value (10): 0.3922

By default, a 32-bit image (in ImageJ) is comprised between 0.0 and 1.0 and the gray value (in range of 0-255 in setColor(..) function) is rescaled in function of these boundaries.
v= 255.0 / 100.0 = 0.3922

3- Now, what about RGB color image?

3-1- Understanding the packed RGB format
Modify the script
  • (i) to create an image with an orange background (255,127,20)RGB and
  • (ii) to display the pixel value in decimal, binary, and hexadecimal notations (see [Link] if you don't know what it is)
+++ IJ snippet +++ +++ End of IJ snippet +++

Note: Since the new JavaScript engine developed by Oracle: Nashorn, the numbers are signed and because the integer stored in images are unsigned, we need to do the conversion before displaying them. This is done with the pixel >>>0 line of code.

Run the script and look at the result (in decimal, bin, and hex notations)
Pixel value (10): 4294934292
Pixel value (2): 11111111111111110111111100010100
Pixel value (16): #ffff7f14

Why do we get this strange number?
Here, the explanation isn't so straightforward. We know that each pixel is defined by three primary color values. However, these values aren't stored in three separate 8-bit channels as we could assume when using the operation Image > Color > Split Channels ..., In ImageJ, the RGB image is stored in a packed format that means that the triplets (R,G,B) are successive:

(R,G,B),(R,G,B),(R,G,B),(R,G,B),(R,G,B)

The decimal number 4294934292 expressed in binary notation is composed of 32 bits corresponding to the concatenation of four numbers of 8-bits (4x8) in the range of 0-255.
From right to left, the fourth (the leftmost) byte (=8 bits) isn't used by ImageJ and is always 11111111. The three other bytes correspond respectively to the red, green, and blue values as schemed in Fig. 1.

11111111 11111111 01111111 00010100
Unused       Red          Green      Blue

Now, if we convert each byte, that yields:

11111111(2) = 1x27 +1x26 +1x25 +1x24 +1x23 +1x22 +1x21 +1x20 = 255(10)
01111111(2) = 0x27 +1x26 +1x25 +1x24 +1x23 +1x22 +1x21 +1x20 = 127(10)
00010100(2) = 0x27 +0x26 +0x25 +1x24 +0x23 +1x22 +0x21 +0x20 = 20(10)

In conclusion, the number 4294934292 in decimal notation has no direct meaning in terms of color and/or gray value. This is just the result of the way, numbers are stored in computer memory. Thus, in this case, the value must be read in hex notation (or binary) to see the three numbers corresponding to the red, green, and blue components.
Note: The unused byte doesn't exist in the digital image, only three bytes are used to define a pixel but the getPixel(...) function can't return a number of 24-bit length. In other image processing softwares the fourth byte is used for transparency (also called alpha channel) which isn't supported in ImageJ.

4- Links


Numbers: decimal, binary, hexadecimal [Link]

No comments:

Post a Comment