Sunday, March 20, 2011

ImageJ 4: Message

ในการเขียนโปรแกรม สิ่งที่หลีกเลี่ยงไม่ได้คือการตรวจสอบความผิดพลาด ซึ่งวิธีการหนึ่งคือการ watch ค่าตัวแปร หรือแสดงค่าตัวแปรที่กำลังสนใจอยู่

ใน ImageJ เรามีคำสั่งสำหรับการแสดงข้อความหรือตัวแปร เช่น
IJ.log(String) ตัวอย่างเช่น
 public void run(String arg) 
 {
  // log
  IJ.log("Use log method to debug variable");
  int a = 99;
  IJ.log("a = "+a);  
 }

ซึ่งจะได้ผลลัพธ์ คือ

หากต้องการแสดงข้อความที่ status bar ก็จะสามารถใช้คำสั่ง
IJ.showStatus(String) เช่น
 public void run(String arg) 
 { 
  // show text in status bar
  IJ.showStatus("Message at status bar");
 }

ซึ่งจะได้ผลลัพธ์

 ImageJ ยังสนับสนุนการแสดงข้อความเป็น dialog box ในหลายรูปแบบ เช่น
-แสดงข้อความผิดพลาด ใช้คำสั่ง IJ.error(String Title, String ErrorMessage); เช่น
IJ.error("Error","Need user input");
จะได้ผลลัพธ์เป็น

หากต้องการให้ผู้ใช้ป้อนค่าตัวอักษร สามารถใช้คำสั่ง String IJ.getString(String Title, String DefaultString) เช่น
String name = IJ.getString("Please enter your name: ","Your name here");
ก็จะเกิดกล่องข้อความดังนี้

จากนั้นเราก็สามารถใช้ตัวแปรที่รับมาได้ เช่น ใช้แสดงในกล่องข้อความใหม่ โดยใช้คำสั่ง IJ.showMessage(String Title, String message) เช่น
IJ.showMessage("Finished",name+" is running ImageJ");
จะได้ผลลัพธ์คือ

Sunday, March 13, 2011

ImageJ 3: Histogram

ตัวอย่างนี้จะเป็นการลองสร้าง Histogram ของรูปสีเทารูปนี้ครับ


ผลลัพธ์ที่อยากได้คือ Histogram ประมาณนี้

ImageJ มีฟังก์ชันในการคำนวณ Histogram ให้คือ getHistogram(); ซึ่งจะใช้หรือเขียนฟังก์ชันเองก็ได้ตามตัวอย่างต่อไปนี้ครับ
//Plugin to compute histogram
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;
import ij.process.ByteProcessor;

public class My_Histogram implements PlugInFilter 
{
 public int setup(String arg, ImagePlus im) 
 {
  return DOES_8G + NO_CHANGES;  //accept only 8-bit grayscale image
 }

 public void run(ImageProcessor ip) 
 {
  int[] H = new int[256];
  /*
  int MN = ip.getPixelCount();
  for(int p=0;p<MN;p++)
  {
   int v = ip.get(p);
   H[v] += 1;
  }
  */
  H = ip.getHistogram(); //built-in method   
 }
}

ส่วนการแสดงผล จำเป็นต้องสร้างหน้าต่างขึ้นมาใหม่ แล้ววาดจุดสีดำลงไปครับ ซึ่งโค้ดทั้งหมดต่อไปนี้ ยังอยู่ใน method run() ครับ
//find max of histogram bin
  int max=H[0];
  for(int b=1;b<256;b++)
  {
   if(max<H[b])
    max=H[b];
  }
  
  //create the image of histogram
  //prepare blank area of w=256 h=100
  ImageProcessor histip = new ByteProcessor(256,100); 
  histip.setValue(255) ; // white = 255 
  histip.fill(); // clear this image 
  //draw histogram bins    
  for(int b=0;b<256;b++)
  {
   int hh = (int)(100.0*H[b]/max); //normalized bin's height
   for(int r=0;r<hh;r++)
   {
    histip.set(b,99-r,0); //plot black points
   }
  }
  
  // display the histogram image: 
  ImagePlus histim = new ImagePlus("Histogram", histip); 
  histim.show();

ImageJ 2: Invert an image

เรายังอยู่ที่การ invert image ครับ แต่จะลองใช้เทคนิคในการเข้าถึงพิกเซลต่างๆกัน ลองดูตัวอย่างโคัดครับ ในที่นี้มีอย่างน้อย 4 วิธี ดูรายละเอียดตามที่หมายเหตุได้ครับ

//Plugin to invert a grayscale image
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;
import ij.process.ByteProcessor;

// _ in the class name = plugin
public class My_Invert implements PlugInFilter 
{
 public int setup(String arg, ImagePlus im) 
 {
  return DOES_8G;  //accept only 8-bit grayscale image
 }

 public void run(ImageProcessor ip) 
 {  
  //1. use getPixel, putPixel to access each pixel
  //they check image boundary+invoke function call, slowest
  int w = ip.getWidth();
  int h = ip.getHeight();
  //for each pixel
  for(int x=0;x<w;x++)
   for(int y=0;y<h;y++)
    ip.putPixel(x,y,255-ip.getPixel(x,y));
    
  /*
  //2. use get,set which do not check boundary
  //still invoke function, faster
  int w = ip.getWidth();
  int h = ip.getHeight();
  //for each pixel
  for(int x=0;x<w;x++)
   for(int y=0;y<h;y++)
    ip.set(x,y,255-ip.get(x,y));
  */
  
  /*  
  //3. use get,set with 1d indices
  //only 1 loop, invoke function, more faster
  int MN = ip.getPixelCount(); //total pixels
  for(int p=0;p<MN;p++)
   ip.set(p, 255-ip.get(p));
  */
  
  /*
  //4. use getPixels to directly process pixel array
  //direct access, no function invocation
  //fastest but more memory need
  //check if ip is ByteProcessor (0-255)
  if(!(ip instanceof ByteProcessor))
   return;
  if(!(ip.getPixels() instanceof byte[]))
   return;
  //dump to array but by reference!!!
  byte[] pixels = (byte[]) ip.getPixels(); 
  int MN = ip.getPixelCount();
  for(int p=0;p<MN;p++)
  {
   int v = 0xFF & pixels[p]; //convert to integer
   v = 255-v;
   pixels[p] = (byte) (0xFF & v); //to byte
  }
  */      
 }
}

ตามทฤษฎีแล้วแต่ละวิธีจะให้ความเร็วต่างกัน ซึ่งจะเห็นชัดขึ้นเมื่อรูปมีขนาดใหญ่ขึ้นครับ

ImageJ 1: Invert an image

ImageJ เป็นซอฟต์แวร์ทางด้านการประมวลผลภาพที่มีประสิทธิภาพ ไม่เสียค่าใช้จ่าย และใช้งานได้ไม่ยากครับ

ในที่นี้เราจะลองศึกษาการประยุกต์ใช้งาน โดยสร้าง plugin ซึ่งเนื้อหาโดยส่วนใหญ่ในหัวข้อนี้และถัดๆไป ผมจะอ้างอิงจากเอกสารในเว็บ http://rsbweb.nih.gov/ij/ และหนังสือ Wilhelm Burger and Mark James Burge, Digital Image Processing: An Algorithmic Introduction using Java, Springer, 2008.

ซึ่งไม่ได้มีเจตนาจะละเมิดลิขสิทธิ์ใดๆทั้งสิ้น

ถ้าพร้อมแล้ว ดาวน์โหลด ImageJ ได้ที่ http://rsbweb.nih.gov/ij/ ครับ

หลังจากคลายไฟล์ที่ดาวน์โหลดมาแ้ล้ว เมื่อเรียกใช้งานก็จะเห็นหน้าตาแบบนี้ครับ

เราจะลองสร้าง plugin เพื่อเพิ่มความสามารถในการประมวลผลภาพตามที่เราต้องการ ในครั้งนี้เราจะลอง invert รูปสีเทาครับ ที่เราเรียกว่าทำ image negative

ขั้นตอนในการสร้าง PlugIn
1. สร้างโฟลเดอร์ หรือ ไฟล์จาวา ในไดเรกทอรี ImageJ/plugins ครับ ในที่นี้เราจะสร้างโฟลเดอร์ชื่อ My และสร้างไฟล์ชื่อ My_Invert.java ไว้
โปรดสังเกตว่า เราใช้เครื่องหมาย _ เพื่อแสดงว่าไฟล์นี้เป็น plugin และจะถูกโหลดเข้าไปใน ImageJ โดยอัตโนมัติ
2. ใช้ IDE ตัวไหนก็ได้แก้ไขไฟล์จาวา ชื่อคลาสต้องตรงกับชื่อไฟล์ plugin
3. ใช้ ImageJ คอมไพล์ไฟล์นั้น
4. แ้ก้ไขไฟล์ถ้ามีข้อผิดพลาด
5. ถ้าไม่มีข้อผิดพลาดก็จะใช้ plugin นั้นได้เลยทันที

ลองมาดูโค้ดของ plugin invert กันครับ
//Plugin to invert a grayscale image
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;
import ij.process.ByteProcessor;

// _ in the class name = plugin
public class My_Invert implements PlugInFilter 
{
 public int setup(String arg, ImagePlus im) 
 {
  return DOES_8G;  //accept only 8-bit grayscale image
 }

 public void run(ImageProcessor ip) 
 {  
  //1. use getPixel, putPixel to access each pixel
  //they check image boundary+invoke function call, slowest
  //0.015 second
  int w = ip.getWidth();
  int h = ip.getHeight();
  //for each pixel
  for(int x=0;x<w;x++)
   for(int y=0;y<h;y++)
    ip.putPixel(x,y,255-ip.getPixel(x,y));
        
 }
}

ทดลองโหลดรูปใดๆ ขึ้นมาก่อน เช่น

จากนั้น คอมไพล์โดยใช้เมนู Plugin / Compile and Run...

ผลลัพธ์ที่ได้

จะเห็นได้ว่าเรามีการเข้าถึงและประมวลผลพิกเซลได้โดยสะดวกครับ

Saturday, July 3, 2010

Basic Image Loading and Display

เรามาลองทำโปรเจคเกี่ยวกับ Image processing เบื้องต้นด้วยจาวากันครับ

ก่อนอื่น เราจะออกแบบให้มีหน้าแสดงผลง่ายๆ โดยใช้เฟรม (JFrame) ครับ ซึ่งก็จะมีหน้าตาดังนี้

ซึ่งก็ใช้รหัสโปรแกรมง่ายๆ ดังนี้ครับ
package dip;

import javax.swing.JFrame;

public class Main
{
    public static void main(String[] args)
    {
        new ImageFrame();
    }
}

class ImageFrame extends JFrame
{
    public ImageFrame()
    {
        //set Frame's properties
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle("Image Processing");   //frame title
        setSize(250,250);   //initial size
        setLocationRelativeTo(null); //set window to center of the screen
        setVisible(true);
    }    
}

ในขั้นตอนต่อไปก็จะทำการทดลองอ่านรูปขึ้นมา แล้วแสดงผลในหน้าต่างดังกล่าว ซึ่งจะมีขั้นตอนคือ

  1. สร้าง component เพื่อรองรับรูป อาจจะเป็น JComponent หรือ JPanel ก็ำได้
  2. อ่านรูปที่ต้องการ
  3. วาดรูปนั้นลงบน component
  4. เพิ่ม component ที่มีรูปอยู่นี้บนเฟรม
ขั้นตอนที่ 1-3 สร้าง component เพื่อรองรับรูป, อ่านรูปที่ต้องการ และวาดรูปลงบน component โดยกำหนดให้รูปอยู่ที่ d:/lena.jpg
เราจะสร้างคลาสขึ้นมาใหม่ ดังนี้

package dip;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;

class Canvas extends JComponent
{
    private BufferedImage img;

    public Canvas()
    {
        try
        {
            //read an image lena.jpg
            img = ImageIO.read(new File("d:/lena.jpg"));
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
    }

    public void paintComponent(Graphics g)
    {
        if(img==null)
            return;
        g.drawImage(img,0,0,null);
    }
}

ขั้นตอนที่ 4 เพิ่ม component นี้ให้เฟรม ในที่นี้เราจะแก้ไข constructor ของเฟรมหลัก ดังนี้

    public ImageFrame()
    {
        //set Frame's properties
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle("Image Processing");   //frame title
        setSize(250,250);   //initial size
        setLocationRelativeTo(null); //set window to center of the screen
       //create and add image component to frame
        Canvas myCanvas = new Canvas();
        add(myCanvas);
        setVisible(true);
    } 

เมื่อรันโปรแกรม ผลลัพธ์ที่ได้ก็จะเป็นดังนี้ครับ