Mukarram Mukhtar

Image Enhancement

Okay so after posting a couple of novice or intermediate level articles, I think now is the time to increase the complexity level a little bit. This article will include some light weight mathematics and some intermediate to expert level Java concepts e.g. multiple threading and handling events etc. We’ll implement an already established image processing concept and subsequently invent and implement a new concept/algorithm. We’ll present these two concepts in a decent, standard looking GUI; because we strongly believe that it’s not what you present, it’s how you present it!

What is Image Enhancement?

In layman terms, and in the current context, image enhancement is to make certain parts of image more visible, brighter and in some cases, illuminated. Suppose we have an image with a big bright object in foreground and then there are some other objects in the background that are not so visible and bright; and we want to make those objects more visible, in other words, we want to enhance background objects; then this is a typical Image Enhancement problem. To cope with such problems, gurus of image processing have derived a mathematical formula, well, not only one formula, there are a few of them, but we’ll be discussing this one formula and then in Contrast Enhancement section we’ll improve this particular formula, which is given as follows:

OK, let me explain this before some of you math-haters (like myself), freak out and close the window. It’s not that bad as it first looks like. f ( i , j ) is the input image that we want to enhance. An image is composed of pixel intensity values in x-axis and y-axis. So for example, the following can be an image:

The above image has 7 columns and 7 rows, means 7 x 7 (total 49) pixels in x and y-axis. Each pixel can have a value from 0 – 255, where 0 means black pixel, 255 means white and anything in between them is just a shade of gray. Now in Java programming world we can easily load this image in a 2-D array of integer or short type. Once the image is loaded in a 2-D array then we can access each element (or each pixel) using i and j index values. So for example if the name of my 2-D array was f, then I can access an element in it by f ( i , j ), right? Then I can parse the whole array and calculate average or mean of its values, right? Yes. So  in the formula above is just that, a global mean, which means the average of all the values in image. Once we calculate the mean, then it’s easy to calculate standard deviation as well.  in the formula above is standard deviation of the whole image. We’ll see in short how to calculate these values programmatically. For now try to understand the concept. Okay so just as we understood that we can calculate global mean and standard deviation of the whole image, similarly, we can also calculate these two values for 3 x 3 or 5 x 5 neighborhood within the image as well. Okay, so what the heck is neighborhood in an image? Let me explain 3 x 3 neighborhood, pick any single pixel in the image, draw a circle around that pixel, then draw a circle bigger than the first one so that it includes all pixels that are closest to the first circle, in other words include all the next door neighboring pixels of the first circle in the second circle. See the image below:

Now the blue circlish-square is 3 x 3 neighborhood of red circle. So we can calculate average and standard deviation of this neighborhood (including the red circled pixel) and denote them as  andrespectively. Okay so now I hope the above given formula would have started making some sense to you. There are some k values in the formula; those are just weight values that need to be adjusted by user of the application. Same is the case with E. So that’s all Image Enhancement is about for now.

What is Contrast Enhancement?

In layman terms, and in the current context, again, contrast enhancement is one step above image enhancement, in which we not only enhance dull, dark and background objects, but also already bright and visible objects in the image. There was no established mathematical formula for this purpose (at least not to my knowledge), so I invented my own formula and an algorithm to achieve this. It goes like this, in first step we run the same formula as we ran in Image Enhancement, because we still want to make background, dark n’ dull objects brighter and more visible. In the second step we run the following formula:

This formula makes value of E, adaptive. There is a condition which in words would say, if current intensity value of image is less than the product of weight Ko and global mean then it means it’s already a dark part of image, then let’s further darken it by multiplying it with (1 – E). Else, if image’s intensity value is more than product of weight Ko and global mean then it means it’s a bright part of image then let’s further brighten it by multiplying it with (1 + E). E’s value is constant again, and can be set by user. Okay, enough of talking now, let’s see the theory working.

Coding Image & Contrast Enhancement in Java:

So as I promised we’ll be writing a Java application with standard GUI; here is one with a file input object to select an input image, a couple of slider objects to adjust weight values, a progress bar which will tell us how much of the work has been done, and a button to run start the process and last but certainly not least, some multi threading which keeps the UI of the form pretty while it’s running a heavy mathematical formula on the image. That’s how the main window of the application is going to look like:

Now you can select a *.pgm image and adjust weight values and then select Run button. If you want adaptive mode then you can select Adaptive Enhancement checkbox as well. The window will look like this after you select a file and set weights and you run image enhancement and it’s running the process:

Let’s compare input and out images:

In the above given images, as we can see the main object in the center of the image is same in both images, which means, it was left touched. On the other hand the objects on the right hand side have been enhanced and made brighter. And that’s exactly what our objective was. Now let’s see Adaptive mode:

Now as you can clearly see in the above images, not only background image got enhanced but the main object has also got brighter and clearer. Let me put these three images together so that you can compare:

You can compare yourself and choose whichever approach you like. Now before winding up let me show you one more example:

Okay, so I think I’ve made my point (to some extent, please?). Now let’s see the source code that does this magic. The source code is divided into two physical files; 1). frmImageEnhancer.java 2). BinaryFile.java.

Let me give you the source code of BinaryFile.java first, because I didn’t write it myself, I just got its code from Jeff Heaton (http://www.jeffheaton.com). So here you go:

import java.io.*;
import javax.swing.JOptionPane;

/**
 * @author Jeff Heaton(http://www.jeffheaton.com)
 * @version 1.0
 */
public class BinaryFile
{

 /**
 * Use this constant to specify big-endian integers.
 */
 public static final short BIG_ENDIAN = 1;

 /**
 * Use this constant to specify litte-endian constants.
 */
 public static final short LITTLE_ENDIAN = 2;

 /**
 * The underlying file.
 */
 protected RandomAccessFile _file;

 /**
 * Are we in LITTLE_ENDIAN or BIG_ENDIAN mode.
 */
 protected short _endian;

 /**
 * Are we reading signed or unsigned numbers.
 */
 protected boolean _signed;

 /**
 * The constructor.  Use to specify the underlying file.
 *
 * @param f The file to read/write from/to.
 */
 public BinaryFile(RandomAccessFile f)
 {
 _file = f;
 _endian = LITTLE_ENDIAN;
 _signed = false;
 }

 /**
 * Set the endian mode for reading integers.
 *
 * @param i Specify either LITTLE_ENDIAN or BIG_ENDIAN.
 * @exception java.lang.Exception Will be thrown if this method is not passed either BinaryFile.LITTLE_ENDIAN or BinaryFile.BIG_ENDIAN.
 */
 public void setEndian(short i) throws Exception
 {
 if ((i == BIG_ENDIAN) || (i == LITTLE_ENDIAN))
 _endian = i;
 else
 throw (new Exception(
 "Must be BinaryFile.LITTLE_ENDIAN or BinaryFile.BIG_ENDIAN"));
 }

 /**
 * Returns the endian mode.  Will be either BIG_ENDIAN or LITTLE_ENDIAN.
 *
 * @return BIG_ENDIAN or LITTLE_ENDIAN to specify the current endian mode.
 */
 public int getEndian()
 {
 return _endian;
 }

 /**
 * Sets the signed or unsigned mode for integers.  true for signed, false for unsigned.
 *
 * @param b True if numbers are to be read/written as signed, false if unsigned.
 */
 public void setSigned(boolean b)
 {
 _signed = b;
 }

 /**
 * Returns the signed mode.
 *
 * @return Returns true for signed, false for unsigned.
 */
 public boolean getSigned()
 {
 return _signed;
 }

 /**
 * Reads a fixed length ASCII string.
 *
 * @param length How long of a string to read.
 * @return The number of bytes read.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public String readFixedString(int length) throws java.io.IOException
 {
 String rtn = "";

 for (int i = 0; i < length; i++)
 rtn += (char) _file.readByte();
 return rtn;
 }

 /**
 * Writes a fixed length ASCII string.  Will truncate the string if it does not fit in the specified buffer.
 *
 * @param str The string to be written.
 * @param length The length of the area to write to.  Should be larger than the length of the string being written.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public void writeFixedString(String str, int length)
 throws java.io.IOException
 {
 int i;

 // trim the string back some if needed

 if (str.length() > length)
 str = str.substring(0, length);

 // write the string

 for (i = 0; i < str.length(); i++)
 _file.write(str.charAt(i));

 // buffer extra space if needed

 i = length - str.length();
 while ((i--) > 0)
 _file.write(0);
 }

 /**
 * Reads a string that stores one length byte before the string.  This string can be up to 255 characters long.  Pascal stores strings this way.
 *
 * @return The string that was read.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public String readLengthPrefixString() throws java.io.IOException
 {
 short len = readUnsignedByte();
 return readFixedString(len);
 }

 /**
 * Writes a string that is prefixed by a single byte that specifies the length of the string.  This is how Pascal usually stores strings.
 *
 * @param str The string to be written.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public void writeLengthPrefixString(String str) throws java.io.IOException
 {
 writeByte((byte) str.length());
 for (int i = 0; i < str.length(); i++)
 _file.write(str.charAt(i));
 }

 /**
 * Reads a fixed length string that is zero(NULL) terminated.  This is a type of string used by C/C++.  For example char str[80].
 *
 * @param length The length of the string.
 * @return The string that was read.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public String readFixedZeroString(int length) throws java.io.IOException
 {
 String rtn = readFixedString(length);
 int i = rtn.indexOf(0);
 if (i != -1)
 rtn = rtn.substring(0, i);
 return rtn;
 }

 /**
 * Writes a fixed length string that is zero terminated.  This is the format generally used by C/C++ for string storage.
 *
 * @param str The string to be written.
 * @param length The length of the buffer to receive the string.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public void writeFixedZeroString(String str, int length)
 throws java.io.IOException
 {
 writeFixedString(str, length);
 }

 /**
 * Reads an unlimited length zero(null) terminated string.
 *
 * @return The string that was read.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public String readZeroString() throws java.io.IOException
 {
 String rtn = "";
 char ch;

 do
 {
 ch = (char) _file.read();
 if (ch != 0)
 rtn += ch;
 } while (ch != 0);
 return rtn;
 }

 /**
 * Writes an unlimited zero(NULL) terminated string to the file.
 *
 * @param str The string to be written.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public void writeZeroString(String str) throws java.io.IOException
 {
 for (int i = 0; i < str.length(); i++)
 _file.write(str.charAt(i));
 writeByte((byte) 0);
 }

 /**
 * Internal function used to read an unsigned byte.  External classes should use the readByte function.
 *
 * @return The byte, unsigned, as a short.
 * @exception java.io.IOException If an IO exception occurs.
 */
 protected short readUnsignedByte() throws java.io.IOException
 {
 return (short) (_file.readByte() & 0xff);
 }

 /**
 * Reads an 8-bit byte.  Can be signed or unsigned depending on the signed property.
 *
 * @return A byte stored in a short.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public short readByte() throws java.io.IOException
 {
 if (_signed)
 return (short) _file.readByte();
 else
 return (short) _file.readUnsignedByte();
 }

 /**
 * Writes a single byte to the file.
 *
 * @param b The byte to be written.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public void writeByte(short b) throws java.io.IOException
 {
 _file.write(b & 0xff);
 }

 /**
 * Reads a 16-bit word.  Can be signed or unsigned depending on the signed property.  Can be little or big endian depending on the endian property.
 *
 * @return A word stored in an int.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public int readWord() throws java.io.IOException
 {
 short a, b;
 int result;

 a = readUnsignedByte();
 b = readUnsignedByte();

 if (_endian == BIG_ENDIAN)
 result = ((a << 8 ) | b);
 else
 result = (a | (b << 8 ));

 if (_signed)
 if ((result & 0x8000) == 0x8000)
 result = -(0x10000 - result);

 return result;
 }

 /**
 * Write a word to the file.
 *
 * @param w The word to be written to the file.
 * @exception java.io.IOException If an IO exception occurs.
 */

 public void writeWord(int w) throws java.io.IOException
 {
 if (_endian == BIG_ENDIAN)
 {
 _file.write((w & 0xff00) >> 8);
 _file.write(w & 0xff);
 } else
 {
 _file.write(w & 0xff);
 _file.write((w & 0xff00) >> 8);
 }
 }

 /**
 * Reads a 32-bit double word.  Can be signed or unsigned depending on the signed property.  Can be little or big endian depending on the endian property.
 *
 * @return A double world stored in a long.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public long readDWord() throws java.io.IOException
 {
 short a, b, c, d;
 long result;

 a = readUnsignedByte();
 b = readUnsignedByte();
 c = readUnsignedByte();
 d = readUnsignedByte();

 if (_endian == BIG_ENDIAN)
 result = ((a << 24) | (b << 16) | (c << 8 ) | d);
 else
 result = (a | (b << 8 ) | (c << 16) | (d << 24));

 if (_signed)
 if ((result & 0x80000000L) == 0x80000000L)
 result = -(0x100000000L - result);

 return result;
 }

 /**
 * Writes a double word to the file.
 *
 * @param d The double word to be written to the file.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public void writeDWord(long d) throws java.io.IOException
 {
 if (_endian == BIG_ENDIAN)
 {
 _file.write((int) (d & 0xff000000) >> 24);
 _file.write((int) (d & 0xff0000) >> 16);
 _file.write((int) (d & 0xff00) >> 8);
 _file.write((int) (d & 0xff));
 } else
 {
 _file.write((int) (d & 0xff));
 _file.write((int) (d & 0xff00) >> 8);
 _file.write((int) (d & 0xff0000) >> 16);
 _file.write((int) (d & 0xff000000) >> 24);
 }
 }

 /**
 * Allows the file to be aligned to a specified byte boundary.  For example, if a 4(double word) is specified, the file pointer will be moved to the next double word boundary.
 *
 * @param a The byte-boundary to align to.
 * @exception java.io.IOException If an IO exception occurs.
 */
 public void align(int a) throws java.io.IOException
 {
 if ((_file.getFilePointer() % a) > 0)
 {
 long pos = _file.getFilePointer() / a;
 _file.seek((pos + 1) * a);
 }
 }

}

As I said earlier that source code given in this article will be for intermediate and expert level Java programmers, therefore I’m not explaining the above code snippet. Besides, the code is pretty straight forward, easy to understand and has many inline comments written by programmer which will certainly help other programmers in understanding it. Anyways, so next is the main class or form (put the following code in frmImageEnhancer.java file).

Enjoy coding, happy programming and feel free to let me know if you need the whole project with its sample images all zipped in one file. Have a great one! 😉

import java.io.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.util.*;
import java.text.*;
import javax.swing.event.*;

// Main class that handles most UI related issues
public class frmImageEnhancer extends JFrame implements ActionListener, ChangeListener, ItemListener
{
 // Image Enhancer class, which handles all of the image related methods
 public class clsImageEnhancer implements Runnable
 {
 public float Mean_g, SD_g;       
 public short[][] originalImage;

 public clsImageEnhancer(short[][] originalImage)
 {
 this.originalImage = originalImage;
 }

 // This method does the adaptive enhancement
 public short[][] autoEnhance(float E, float Ko)
 {
 short[][] finalImage = new short[this.originalImage.length][this.originalImage.length];

 for(int y = 0; y < this.originalImage.length; y++)
 for(int x = 0; x < this.originalImage[0].length; x++)
 {                           
 if(this.originalImage[y][x] <= (this.Mean_g * Ko))
 finalImage[y][x] = this.clamp((short)((float)this.originalImage[y][x] * (1 - E)));               
 else
 finalImage[y][x] = this.clamp((short)((float)this.originalImage[y][x] * (1 + E)));               
 }

 return finalImage;
 }

 // This method does the regular enhancement given in the textbook
 public short[][] manualEnhance(float E, float Ko, float K1, float K2)
 {
 short[][] finalImage = new short[this.originalImage.length][this.originalImage.length];

 int neighborhood = 3, innerNeighborhood = 3;                   
 neighborhood = (neighborhood % 2 > 0 ? neighborhood - 1 : neighborhood - 2);
 neighborhood /= 2;
 innerNeighborhood = (innerNeighborhood % 2 > 0 ? innerNeighborhood - 1 : innerNeighborhood - 2);
 innerNeighborhood /= 2;                    

 for(int y = 0; y < this.originalImage.length; y++)
 for(int x = 0; x < this.originalImage[0].length; x++)
 {                           
 float Mean_l = this.calculateMean(this.Convert2DTo1D(this.originalImage, x, y, neighborhood));
 float SD_l = this.calculateStandardDeviation(this.Convert2DTo1D(this.originalImage, x, y, neighborhood), Mean_l);

 if(Mean_l <= (this.Mean_g * Ko))
 if((SD_g * K1) <= SD_l && SD_l <= (SD_g * K2))
 finalImage[y][x] = (short)this.clamp(this.originalImage[y][x] * E);
 else
 finalImage[y][x] = this.originalImage[y][x];   
 else
 finalImage[y][x] = this.originalImage[y][x];
 }        

 return finalImage;
 }

 public short clamp(short c) { return (short)this.clamp((float)c); }
 public int clamp(int c) { return (int)this.clamp((float)c); }
 public float clamp(float c)
 {
 if (c < 0) c = 0;
 else if (c > 255) c = 255;       
 return c;
 }

 // Converts a 2-D array into 1-D array of the size of neighborhood from x and y coordinates
 public short[] Convert2DTo1D(short[][] array2D, int x, int y, int neighborhood)
 {
 short[] array1D = new short[(neighborhood * 2 + 1) * (neighborhood * 2 + 1)];
 int index = 0;

 for(int j = y - neighborhood; j <= y + neighborhood; j++)
 for(int i = x - neighborhood; i <= x + neighborhood; i++)
 if((j < 0 || j >= array2D.length || i < 0 || i >= array2D[0].length))
 array1D[index++] = 0;
 else
 array1D[index++] = array2D[j][i];

 return array1D;
 }          

 // Converts a 2-D array into 1-D array
 public short[] Convert2DTo1D(short[][] array2D)
 {
 int index = 0;
 short[] array1D = new short[(array2D.length * array2D[0].length)];

 for(int y = 0; y < array2D.length; y++)
 for(int x = 0; x < array2D[0].length; x++)
 array1D[index++] = array2D[y][x];

 return array1D;
 }

 // This method calculates mean of intensity values of a 2-D array
 public float calculateMean(short[][] array2D)
 {
 return this.calculateMean(this.Convert2DTo1D(array2D));       
 }

 // This method calculates mean of intensity values of a 1-D array
 public float calculateMean(short[] array1D)
 {
 float tmpSum = 0;

 for(int x = 0; x < array1D.length; x++)
 tmpSum += array1D[x];

 return (tmpSum / array1D.length);
 }

 // This method calculates standard deviation of intensity values of a 2-D array
 public float calculateStandardDeviation(short[][] array2D, float mean)
 {
 return this.calculateStandardDeviation(this.Convert2DTo1D(array2D), mean);
 }    

 // This method calculates standard deviation of intensity values of a 1-D array
 public float calculateStandardDeviation(short[] array1D, float mean)       
 {
 float tmpSum = 0;

 for(int x = 0; x < array1D.length; x++)     
 {
 tmpSum += (int)Math.pow((array1D[x] - mean), 2);
 }

 return (float)Math.sqrt((tmpSum / array1D.length));
 }

 // This method runs a new thread and does all of the enhancement process
 public void run()
 {   
 c.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));    

 String imageName = txtSelectImage.getText();
 setProgressBar(30);

 try
 {   
 int neighborhood = 3, innerNeighborhood = 3;                   
 neighborhood = (neighborhood % 2 > 0 ? neighborhood - 1 : neighborhood - 2);
 neighborhood /= 2;
 innerNeighborhood = (innerNeighborhood % 2 > 0 ? innerNeighborhood - 1 : innerNeighborhood - 2);
 innerNeighborhood /= 2;                    

 if(!chkAutoEnhance.isSelected())
 finalImage = manualEnhance(((float)(sldrE.getValue())/100), ((float)(sldrK0.getValue())/100), ((float)(sldrK1.getValue())/100), ((float)(sldrK2.getValue())/100));
 else
 {
 finalImage = imageEnhancer.manualEnhance(((float)(sldrE.getValue())/100), ((float)(sldrK0.getValue())/100), ((float)(sldrK1.getValue())/100), ((float)(sldrK2.getValue())/100));
 imageEnhancer.originalImage = finalImage;
 finalImage = imageEnhancer.autoEnhance(((float)(sldrAutoE.getValue())/100), ((float)(sldrAutoKo.getValue())/100));
 }                

 setProgressBar(60);

 Calendar cal = Calendar.getInstance();
 SimpleDateFormat sdf = new SimpleDateFormat("MMddyyyy_hhmmss");

 String DoValue = sdf.format(cal.getTime());
 File tFile;    

 tFile = new File(DoValue);
 if(tFile.exists())
 deleteDirectory(tFile);
 if(!tFile.exists())
 tFile.mkdir();

 writeToTextFile(originalImage, "Original", M, N, DoValue);   
 imageName = writeToTextFile(finalImage, (chkAutoEnhance.isSelected() ? "AdaptiveImage" : "FinalImage"), M, N, DoValue);
 setProgressBar(70);

 float Mean_result = imageEnhancer.calculateMean(finalImage);
 float SD_result = imageEnhancer.calculateStandardDeviation(finalImage, Mean_result);

 lblGlobalStatistics.setText("<html><Font Name='Ariel' Bold='True' Size='2'>Original Image: Mean = " + dfSlider.format(this.Mean_g) + ", Standard Deviation = " + dfSlider.format(this.SD_g) + 
"<BR>Resultant Image: Mean = " + dfSlider.format(Mean_result) + ", Standard Deviation = " + dfSlider.format(SD_result) + "</Font></html>");
 setProgressBar(90);
 }
 catch (Exception exc)
 {
 //JOptionPane.showMessageDialog(this, exc.toString(), "Error", JOptionPane.ERROR_MESSAGE);                           
 }
 finally
 {
 c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

 }

 viewImage(imageName);
 // waiting for viewImage method to complete open the image in background
 for(int i = 92; i <= 102; i += 2)
 setProgressBar(i);
 }
 }

 public static void main(String[] args)
 {       
 final frmImageEnhancer app = new frmImageEnhancer();
 }

 // The whole constructor is for setting up the UI of the form
 public frmImageEnhancer()
 {
 // setting UI of the window
 c = getContentPane();
 setBounds(50, 50, 555, 595);
 setBackground(new Color(204, 204, 204));
 setDefaultCloseOperation(EXIT_ON_CLOSE);
 setTitle("Image & Contrast Enhancement");
 setResizable(false);
 c.setLayout(null);

 // add buttons to their event listener
 btnBrowse.addActionListener(this);       
 btnRun.addActionListener(this);   
 sldrK0.addChangeListener(this);
 sldrK1.addChangeListener(this);
 sldrK2.addChangeListener(this);
 sldrE.addChangeListener(this);
 sldrAutoKo.addChangeListener(this);
 sldrAutoE.addChangeListener(this);
 chkAutoEnhance.addChangeListener(this);

 txtAreaMessages.setEditable(true);    // message box is read-only
 scrollPaneMessages = new JScrollPane(txtAreaMessages);       
 pnlMessages.setBounds(10, 240, 530, 315);
 pnlMessages.setPreferredSize(new Dimension(0, 235));
 pnlMessages.add(scrollPaneMessages, BorderLayout.CENTER);

 lblK0.setBounds(50, 60, 100, 25);
 sldrK0.setBounds(10, 80, 125, 20);
 lblK1.setBounds(178, 60, 100, 25);
 sldrK1.setBounds(143, 80, 125, 20);
 lblK2.setBounds(315, 60, 100, 25);
 sldrK2.setBounds(280, 80, 125, 20);
 lblE.setBounds(455, 60, 100, 25);
 sldrE.setBounds(415, 80, 125, 20);       
 lblAutoKo.setBounds(25, 110, 100, 25);
 sldrAutoKo.setBounds(10, 130, 125, 20);
 lblAutoE.setBounds(165, 110, 100, 25);
 sldrAutoE.setBounds(143, 130, 125, 20);
 pbEnhancer.setBounds(10, 205, 530, 25);

 // setting UI of buttons
 lblSelectImage.setBounds(10, 20, 100, 25);               
 txtSelectImage.setBounds(110, 20, 335, 25);       
 btnBrowse.setBounds(450, 20, 90, 25);
 chkAutoEnhance.setBounds(280, 122, 200, 25);
 btnRun.setBounds(10, 165, 185, 25);
 lblGlobalStatistics.setBounds(210, 162, 500, 30);        

 txtSelectImage.setEditable(false);
 txtSelectImage.setBackground(Color.WHITE);
 pbEnhancer.setStringPainted(true);

 // adding buttons to the main window
 c.add(btnBrowse);       
 c.add(btnRun);           
 c.add(lblGlobalStatistics);           
 c.add(lblSelectImage);
 c.add(lblK0);
 c.add(sldrK0);
 c.add(lblK1);
 c.add(sldrK1);
 c.add(lblK2);
 c.add(sldrK2);
 c.add(lblE);
 c.add(sldrE);
 c.add(lblAutoKo);
 c.add(sldrAutoKo);
 c.add(lblAutoE);
 c.add(sldrAutoE);
 c.add(txtSelectImage);
 c.add(pnlMessages);
 c.add(chkAutoEnhance);
 c.add(pbEnhancer);        

 show();       
 }

 public void itemStateChanged(ItemEvent e) { }

 // This method decides which method to call for any particular slider motion
 public void stateChanged(ChangeEvent e)
 {       
 Object source = e.getSource();

 if (source == this.sldrK0)
 this.lblK0.setText("Ko = " + dfSlider.format(((float)this.sldrK0.getValue())/100));
 else if (source == this.sldrK1)
 this.lblK1.setText("K1 = " + dfSlider.format(((float)this.sldrK1.getValue())/100));
 else if (source == this.sldrK2)
 this.lblK2.setText("K2 = " + dfSlider.format(((float)this.sldrK2.getValue())/100));
 else if (source == this.sldrE)
 this.lblE.setText("E = " + dfSlider.format(((float)this.sldrE.getValue())/100));
 else if (source == this.sldrAutoE)
 this.lblAutoE.setText("Adapt E = " + dfSlider.format(((float)this.sldrAutoE.getValue())/100));
 else if (source == this.sldrAutoKo)
 this.lblAutoKo.setText("Adapt Ko = " + dfSlider.format(((float)this.sldrAutoKo.getValue())/100));
 }        

 // This method decides which method to call for any particular button click
 public void actionPerformed(ActionEvent e)
 {
 Object source = e.getSource();

 if (source == btnBrowse)
 btnBrowse_Click();
 else if (source == btnRun)
 btnRun_Click();
 }        

 // This method opens up a file dialogue box and lets user select a source pgm
 // image file for enhancement. It also sets hard coded weight values and open
 // an input source image.
 public void btnBrowse_Click()
 {   
 JFileChooser chooser = new JFileChooser();
 chooser.setCurrentDirectory(new File("images/"));

 chooser.setFileFilter(new javax.swing.filechooser.FileFilter() {
 public boolean accept(File f) {
 return f.getName().toLowerCase().endsWith(".pgm")
 || f.isDirectory();
 }

 public String getDescription() {
 return "PGM Images";
 }
 });

 int r = chooser.showOpenDialog(this);
 if (r == JFileChooser.APPROVE_OPTION)
 {   
 if(chooser.getSelectedFile().getAbsolutePath().endsWith(".pgm"))
 {
 this.setProgressBar(0);
 String imageName = chooser.getSelectedFile().getAbsolutePath();
 this.txtSelectImage.setText(imageName);
 this.loadImageStatistics(imageName);
 this.setHardCodedWeights();
 this.viewImage(imageName);   
 }
 else
 JOptionPane.showMessageDialog(this, "Only PGM image files are supported in this version. Please select a valid PGM image file.", "Warning: Image File Not Supported", JOptionPane.WARNING_MESSAGE);                           
 }
 }

 // This method sets the slider values against some of the most commonly used images
 private void setHardCodedWeights()
 {
 String imageName = this.txtSelectImage.getText().trim();

 if(imageName.endsWith("florida_satellite.pgm"))
 {
 this.sldrK0.setValue(100);
 this.sldrK1.setValue(5);
 this.sldrK2.setValue(250);
 this.sldrE.setValue(180);
 this.sldrAutoKo.setValue(70);
 this.sldrAutoE.setValue(25);
 }
 else if(imageName.endsWith("SEM-filament.pgm") || imageName.endsWith("Tungsten.pgm"))
 {
 this.sldrK0.setValue(65);
 this.sldrK1.setValue(0);
 this.sldrK2.setValue(250);
 this.sldrE.setValue(150);
 this.sldrAutoKo.setValue(0);
 this.sldrAutoE.setValue(15);
 }
 else if(imageName.endsWith("MRI-spine.pgm"))
 {
 this.sldrK0.setValue(180);
 this.sldrK1.setValue(1);
 this.sldrK2.setValue(225);
 this.sldrE.setValue(140);
 this.sldrAutoKo.setValue(75);
 this.sldrAutoE.setValue(25);
 }
 else if(imageName.endsWith("lena.pgm"))
 {
 this.sldrK0.setValue(25);
 this.sldrK1.setValue(5);
 this.sldrK2.setValue(250);
 this.sldrE.setValue(75);
 this.sldrAutoKo.setValue(35);
 this.sldrAutoE.setValue(35);
 } 

 this.lblK0.setText("Ko = " + dfSlider.format(((float)this.sldrK0.getValue())/100));
 this.lblK1.setText("K1 = " + dfSlider.format(((float)this.sldrK1.getValue())/100));
 this.lblK2.setText("K2 = " + dfSlider.format(((float)this.sldrK2.getValue())/100));
 this.lblE.setText("E = " + dfSlider.format(((float)this.sldrE.getValue())/100));
 this.lblAutoE.setText("Adapt E = " + dfSlider.format(((float)this.sldrAutoE.getValue())/100));
 this.lblAutoKo.setText("Adapt Ko = " + dfSlider.format(((float)this.sldrAutoKo.getValue())/100));
 }

 // This method sets progress bar's values
 private void setProgressBar(int value)
 {         
 pbEnhancer.setValue(value);
 pbEnhancer.repaint();
 try{Thread.sleep(250);}
 catch (InterruptedException err){}
 }

 // This method loads input image's statistics and starts the thread which actually
 // does the enhancement part
 public void btnRun_Click()
 {
 if(this.txtSelectImage.getText().trim().length() > 0)
 {
 loadImageStatistics(txtSelectImage.getText());
 this.trdImageEnhancer.start();
 }
 else
 JOptionPane.showMessageDialog(this, "No image was selected for enhancement. Please select the 'Browse' button to locate an image file.",
 "Warning: No Image to Process", JOptionPane.WARNING_MESSAGE);                           
 }    

 // This method calls some other methods to calculate mean, standard deviation and
 // other statistics of the input image passed to it
 private void loadImageStatistics(String imageName)
 {
 this.originalImage = this.loadImage(imageName);       
 this.imageEnhancer = new clsImageEnhancer(this.originalImage);   
 this.trdImageEnhancer = new Thread(this.imageEnhancer);
 this.trdImageEnhancer.setPriority(Thread.MAX_PRIORITY);
 this.finalImage = new short[this.originalImage.length][this.originalImage[0].length];
 this.imageEnhancer.Mean_g = this.Mean_g = this.imageEnhancer.calculateMean(this.originalImage);
 this.imageEnhancer.SD_g = this.SD_g = this.imageEnhancer.calculateStandardDeviation(this.originalImage, Mean_g);
 lblGlobalStatistics.setText("<html><Font Name='Ariel' Bold='True' Size='2'>Original Image: Mean = " + dfSlider.format(this.Mean_g) +
", Standard Deviation = " + dfSlider.format(this.SD_g) + "</Font></html>");
 }

 // This method loads the input image into a 2-D array
 private short[][] loadImage(String fileName)
 {
 short[][] fileImage = new short[1][1];
 String header = "";
 try
 {
 file = new RandomAccessFile(fileName, "r");
 bin = new BinaryFile(file);
 bin.setEndian(endian);
 bin.setSigned(signed);
 while(true)
 {
 header += bin.readFixedZeroString(1);
 if(header.endsWith("255"))
 {
 header += bin.readFixedZeroString(1);
 break;
 }
 }

 // get dimensions from its header
 String dimensions, strM, strN;
 dimensions = header.substring(header.indexOf('\n')+1);
 dimensions = dimensions.substring(dimensions.indexOf('\n')+1);
 dimensions = dimensions.substring(0, dimensions.indexOf('\n'));
 strM = dimensions.substring(0, dimensions.indexOf(' ')).trim();
 strN = dimensions.substring(dimensions.indexOf(' ')+1).trim();

 int actualM, actualN;

 // set M, N, P and Q

 actualM = Integer.parseInt(strM);
 actualN = Integer.parseInt(strN);

 M = actualM;
 N = actualN;            

 // create arrays
 fileImage = new short[N][M];

 // populate array from original image
 try
 {
 for(int y = 0; y < N; y++)               
 for(int x = 0; x < M; x++)
 fileImage[y][x] = bin.readUnsignedByte();
 }
 catch(Exception exc)
 {
 JOptionPane.showMessageDialog(this, "Error while reading the image.\n" + exc.toString(),  "Error", JOptionPane.ERROR_MESSAGE);             
 }
 finally         
 {
 file.close();             
 }   
 }
 catch(Exception exc)
 {
 JOptionPane.showMessageDialog(this, exc.toString(),  "Error", JOptionPane.ERROR_MESSAGE);             
 }    

 return fileImage;
 }    

 // This method deletes a directory and all sub-directories and files in it
 public boolean deleteDirectory(File path)
 {
 if( path.exists() )
 {
 File[] files = path.listFiles();
 for(int i=0; i<files.length; i++)
 if(files[i].isDirectory())
 deleteDirectory(files[i]);
 else
 files[i].delete();
 }
 return( path.delete() );
 }

 // This method opens up an image in ImageJ Viewer
 private void viewImage(String imageName)
 {       
 Runtime load = Runtime.getRuntime();           
 String programC = "C:\\Program Files\\ImageJ\\ImageJ.exe";           
 String programD = "D:\\Program Files\\ImageJ\\ImageJ.exe";     

 try
 {
 if(new File(programC).exists())
 load.exec(programC + " " + imageName);
 else
 load.exec(programD + " " + imageName);
 }
 catch (Exception exc)
 {
 JOptionPane.showMessageDialog(this, "ImageJ could not be found on this computer. This will not stop Image Enhancement process.\nHowever, application will not be able to display original and enhanced images." +
 " Please select\n'Run Image Enhancement' button in order to continue.\n\n", "Warning: ImageJ Not Found", JOptionPane.WARNING_MESSAGE);                           
 }           
 }        

 // This method writes a 2-D array into the text box
 private void writeImageInTextbox(String title, short[][] imageArray)
 {
 txtAreaMessages.append(title + "\n");
 for(int y = 0; y < imageArray.length; y++)
 {
 for(int x = 0; x < imageArray[0].length; x++)
 {
 txtAreaMessages.append(imageArray[y][x] + " ");
 }
 txtAreaMessages.append("\n");
 }
 txtAreaMessages.append("\n");
 txtAreaMessages.setCaretPosition(txtAreaMessages.getText().length());
 }

 // This method writes a 2-D image into a file in Text format
 private String writeToTextFile(short[][] imageArray, String imgName, int X, int Y, String DoValue)
 {   
 String strImageFile = "";       
 FileOutputStream fout;
 PrintStream ps;
 File file;    

 try
 {           
 // if the image already exists, delete it and re-create it
 strImageFile = DoValue + "/Image_" + imgName + X + Y + ".pgm";
 file = new File(strImageFile);
 if(file.exists())
 file.delete();

 fout = new FileOutputStream(strImageFile, true);   
 ps = new PrintStream(fout);            

 // adding predefined header of image
 ps.println("P2\n# Written by Image/Contrast Enhacement Tool (coded by Mukarram Mukhtar)\n" + X + " " + Y + "\n255");

 for(int y = 0; y < Y; y++)
 for(int x = 0; x < X; x++)
 ps.print(dfImage.format(imageArray[y][x]) + " ");

 ps.close();
 fout.close();           
 }           
 catch (IOException e)
 {
 JOptionPane.showMessageDialog(this, e.toString(), "Error", JOptionPane.ERROR_MESSAGE);
 }
 catch (Exception e)
 {
 JOptionPane.showMessageDialog(this, e.toString(), "Error", JOptionPane.ERROR_MESSAGE);
 }        

 return strImageFile;       
 }    

 // This method writes a 2-D image into a file in Binary format
 private String writeToBinaryFile(short[][] imageArray, String imgName, int X, int Y, String DoValue)
 {   
 String strImageFile = "";

 try
 {
 // if the image already exists, delete it and re-create it
 strImageFile = DoValue + "/Image_" + imgName + X + Y + ".pgm";
 File tempFile = new File(strImageFile);
 if(tempFile.exists())
 tempFile.delete();

 file = new RandomAccessFile(strImageFile, "rw");
 bin = new BinaryFile(file);

 bin.setEndian(endian);
 bin.setSigned(signed);
 bin.writeZeroString("P5\n# Written by Image/Contrast Enhacement Tool (coded by Mukarram Mukhtar)\n" + X + " " + Y + "\n255");

 for(int y = 0; y < Y; y++)
 for(int x = 0; x < X; x++)
 bin.writeByte((short)imageArray[y][x]);

 file.close();
 }           
 catch (IOException e)
 {
 JOptionPane.showMessageDialog(this, e.toString(), "Error", JOptionPane.ERROR_MESSAGE);
 }
 catch (Exception e)
 {
 JOptionPane.showMessageDialog(this, e.toString(), "Error", JOptionPane.ERROR_MESSAGE);
 }

 return strImageFile;
 }

 private JButton btnBrowse = new JButton("Browse...");       
 private JButton btnRun = new JButton("Run Image Enhancement");

 private JTextArea txtAreaMessages = new JTextArea();
 private JScrollPane scrollPaneMessages = null;    //    Instantiated in constructor frmChess()
 private JPanel pnlMessages = new JPanel(new BorderLayout());

 private JLabel lblSelectImage = new JLabel("Select an image:");
 private JLabel lblGlobalStatistics = new JLabel();
 private JTextField txtSelectImage = new JTextField();
 private JCheckBox chkAutoEnhance = new JCheckBox("Adaptive Enhancement", false);

 private JLabel lblK0 = new JLabel("Ko = 0.00");
 private JSlider sldrK0 = new JSlider(JSlider.HORIZONTAL, 0, 500, 0);
 private JLabel lblK1 = new JLabel("K1 = 0.00");
 private JSlider sldrK1 = new JSlider(JSlider.HORIZONTAL, 0, 500, 0);
 private JLabel lblK2 = new JLabel("K2 = 0.00");
 private JSlider sldrK2 = new JSlider(JSlider.HORIZONTAL, 0, 500, 0);
 private JLabel lblE = new JLabel("E = 0.00");
 private JSlider sldrE = new JSlider(JSlider.HORIZONTAL, 0, 500, 0);
 private JLabel lblAutoKo = new JLabel("Adapt Ko = 0.00");
 private JSlider sldrAutoKo = new JSlider(JSlider.HORIZONTAL, 0, 500, 0);
 private JLabel lblAutoE = new JLabel("Adapt E = 0.00");
 private JSlider sldrAutoE = new JSlider(JSlider.HORIZONTAL, 0, 500, 0);
 private JProgressBar pbEnhancer = new JProgressBar(SwingConstants.HORIZONTAL, 0, 100);

 private clsImageEnhancer imageEnhancer;   
 private Thread trdImageEnhancer;

 private DecimalFormat dfImage = new DecimalFormat("000");   
 private DecimalFormat dfSlider = new DecimalFormat("0.00");    

 private int M, N;
 private float Mean_g, SD_g;        

 short[][] originalImage;
 short[][] finalImage; 

 private RandomAccessFile file;
 private BinaryFile bin;
 // set the endian mode to run the test in
 final short endian = BinaryFile.BIG_ENDIAN;
 // set the signed mode to run the test in
 private final boolean signed = true;
 private final double TWO = 2;

 private Random rnd = new Random();   
 private Container c;
}

2 Comments »

  1. After running java file, appplication gives an error as there is no ImageJ on your computer.
    So what is this ImageJ ?
    plz reply asap…

    Comment by kapil kothari — March 2, 2012 @ 4:49 pm

    • ImageJ is a viewer, you have to install it on your computer. It’s available for free download from the web. I used it to open/view images of PGM type for this project.

      Comment by Mukarram Mukhtar — March 3, 2012 @ 1:46 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: