ImageJでPlug-inを作る時の参考になれば幸いです。
画像処理Plug-inは、Interface ij.plugin.filter.PlugInFilterの 実装として作ります。ここではまず、Test_というPlug-inを 作ってみます。この場合、少なくとも実装する必要があるのは、 void run(ImageProcessor ip)と int setup(java.lang.String arg, ImagePlus imp)です。 setup()は、Plug-in Filterが処理できる画像の種類を 返す初期関数、run()は、実際の処理関数です。 詳しくは、API referenceを参照して下さい。 Plug-inの名前には_が含まれている必要があるようです。
import ij.*; import ij.process.*; import ij.gui.*; import ij.plugin.filter.*; public class Test_ implements PlugInFilter { public int setup(String arg, ImagePlus img) { if (IJ.versionLessThan("1.28e")) return DONE; else // どの種類の画像も扱えるし、マルチページtiff // なども扱えることを言う。 return DOES_ALL+DOES_STACKS; } public void run(ImageProcessor ip) { // 幅を取得 int width = ip.getWidth(); // 高さを取得 int height = ip.getHeight(); int[] p1 = new int[3]; // load for (int y=1; y<height; y++) { for (int x=0; x<width; x++) { // (x,y)のPixelデータを取得。 // int[] は、RGBである。 // !!!! 注意 !!!! // 以降の例題ではほとんどこのgetPixelを使っていますが、 // これはプログラムを見やすくするためです。 // 画像の画素数だけ呼び出すので、 // 実用的ではありません。実際に使う場合は、 // getPixelsなどを使って高速化してください。 p1 = ip.getPixel(x, y, p1); } } } }
この内容をTest_.javaという名前で、 ImageJのディレクトリの中のpluginsというディレクトリに 保存してください。
コンパイルする時は、以下のようにして、ImageJのライブラリの 位置をjavacに教える必要があります。
コンパイルが終ってTest_.classができれば、java -jar ij.jarなりして ImageJを実行します。一番右のPluginsというメニューの中に Testという項目ができているでしょう。そうしたら、あとは画像を開いて その項目を選ぶだけです。 上の例では、画像の縦横を取得し、1つ1つのPixelを 読むという動作をします。画像は変更されず、見かけ上はなにも起きません。
次は、それぞれの画素に対してFilterをかけてみましょう。ここでは、 それぞれの画素に対し、そこが明るさで200より小さければ 黒く、それ以外はそのままというものを作ってみます。今度は、Test2_.java という名前にして下さい。
import ij.*; import ij.process.*; import ij.gui.*; import ij.plugin.filter.*; public class Test2_ implements PlugInFilter { // 明るさ Y = 0.299R+0.587G+0.114B // 色差 Cr = 0.500R-0.419G-0.081B // 色差 Cb = -0.169R-0.332G+0.500B // 画素から明るさを計算 private double getY(int[] a) { if (a.length != 3) return 0; return 0.299*((double)a[0])+0.587*((double)a[1])+0.114*((double)a[2]); } public int setup(String arg, ImagePlus img) { if (IJ.versionLessThan("1.28e")) return DONE; else // どの種類の画像も扱えるし、マルチページtiff // なども扱えることを言う。 return DOES_ALL+DOES_STACKS; } public void run(ImageProcessor ip) { // 幅を取得 int width = ip.getWidth(); // 高さを取得 int height = ip.getHeight(); int[] p1 = new int[3]; int[] black = {0, 0, 0}; // load for (int y=1; y<height; y++) { for (int x=0; x<width; x++) { // (x,y)のPixelデータを取得。 // int[] は、RGBである。 p1 = ip.getPixel(x, y, p1); // 明るさが100より小さければ黒にしてしまう。 if (getY(p1) < 100) ip.putPixel(x, y, black); } } } }
ここでは、putPixelという関数で出力しています。
今度は、画像に対し、計測を行なってその結果を表で出力 するものを考えてみましょう。
import ij.*; import ij.process.*; import ij.gui.*; import ij.plugin.filter.*; public class Test3_ implements PlugInFilter { // 明るさ Y = 0.299R+0.587G+0.114B // 色差 Cr = 0.500R-0.419G-0.081B // 色差 Cb = -0.169R-0.332G+0.500B // 画素から明るさを計算 private double getY(int[] a) { if (a.length != 3) return 0; return 0.299*((double)a[0])+0.587*((double)a[1])+0.114*((double)a[2]); } public int setup(String arg, ImagePlus img) { if (IJ.versionLessThan("1.28e")) return DONE; else // どの種類の画像も扱えるし、マルチページtiff // なども扱えることを言う。 return DOES_ALL+DOES_STACKS; } public void run(ImageProcessor ip) { // 幅を取得 int width = ip.getWidth(); // 高さを取得 int height = ip.getHeight(); int[] p1 = new int[3]; // 表のsetup // 例えば、一行に3つの事項を出力するなら、 // 1 \t 2 \t 3と、3つのタイトルを\tで区切って書く。 IJ.setColumnHeadings("x\t y\t bright"); // load for (int y=1; y<height; y++) { for (int x=0; x<width; x++) { // (x,y)のPixelデータを取得。 // int[] は、RGBである。 p1 = ip.getPixel(x, y, p1); // 明るさが500より小さければ、 // 場所と明るさを表に出力 // 出力方法はさっきと同じように\tで区切る if (getY(p1) < 500) IJ.write(x + "\t" + y + "\t" + getY(p1)); } } } }
先ほどの例でいえば、明るさがどの値より大きいのかを 実行毎に指定できるようにしてみましょう。これには GenericDialogを使用します。
import ij.*; import ij.process.*; import ij.gui.*; import ij.plugin.filter.*; public class Test4_ implements PlugInFilter { // 明るさ Y = 0.299R+0.587G+0.114B // 色差 Cr = 0.500R-0.419G-0.081B // 色差 Cb = -0.169R-0.332G+0.500B // 画素から明るさを計算 private double getY(int[] a) { if (a.length != 3) return 0; return 0.299*((double)a[0])+0.587*((double)a[1])+0.114*((double)a[2]); } public int setup(String arg, ImagePlus img) { if (IJ.versionLessThan("1.28e")) return DONE; else // どの種類の画像も扱えるし、マルチページtiff // なども扱えることを言う。 return DOES_ALL+DOES_STACKS; } public void run(ImageProcessor ip) { // dialogの表示。キャンセルでそのまま戻る。 if(getParam()) return ; // 幅を取得 int width = ip.getWidth(); // 高さを取得 int height = ip.getHeight(); int[] p1 = new int[3]; int[] black = new int[3]; black[0] = 0; black[1] = 0; black[2] = 0; // 表のsetup // 例えば、一行に3つの事項を出力するなら、 // 1 \t 2 \t 3と、3つのタイトルを\tで区切って書く。 IJ.setColumnHeadings("x\t y\t bright"); // load for (int y=1; y<height; y++) { for (int x=0; x<width; x++) { // (x,y)のPixelデータを取得。 // int[] は、RGBである。 p1 = ip.getPixel(x, y, p1); // 明るさが50より小さければ、 // 場所と明るさを表に出力 // 出力方法はさっきと同じように\tで区切る if (getY(p1) < brightLimit) IJ.write(x + "\t" + y + "\t" + getY(p1)); // もし画像変更が許可されていれば // 条件に合わないところを黒で塗りつぶす else if (ifChange) ip.putPixel(x, y, black); } } } // staticにしておくと、次にPlug-inを実行した時も // 以前の入力結果が保存されている。 static int brightLimit = 50; // static でなければ実行毎に初期化されます。 private boolean ifChange = false; // ここでユーザからParameterを取得。 // 実行前にdialogが出ます。 private boolean getParam() { GenericDialog gd = new GenericDialog("Enter Parameters.", IJ.getInstance()); gd.addNumericField("", brightLimit, 0); gd.addCheckbox("change image", ifChange); // 同じようにして他のParameterも取得できます。 // dialog表示 gd.showDialog(); // dialogでキャンセルボタンが押された時 // (ちょっと汚い処理の仕方です。Javaらしくするなら例外を使うのかな。) if (gd.wasCanceled()) { return true; } // ここで取得結果を代入。詳しくはAPI docを参照して下さい。 brightLimit = (int) gd.getNextNumber(); ifChange = (boolean) gd.getNextBoolean(); // 他のParameterもここで取得可能です。 return false; } }
おそらく実行すれば上のようなdialogが現れるでしょう。 Change imageにチェックをつけて実行すれば画像が変更され、 そうでなければ単に計測が行なわれます。
他にも色々な関数が存在するので、APIを見て試してみて下さい。
Knuth is not God! Typing "God" into Google and pressing "I'm Feeling Lucky" would not lead you to his homepage. Shlomi Fish in Hackers-IL message No. 2084 ("The Great WWW-Wisdom Shootout") -- Shlomi Fish -- Shlomi Fish's Aphorisms Collection ( http://www.shlomifish.org/humour.html ) Rule of Open-Source Programming #15: If you like it, let the author know. If you hate it, let the author know why. -- Muli Ben-Yehuda -- "Rules of Open Source Programming"