Translate

2019年9月11日水曜日

findMaxima (MaximumFinder)で解析範囲を指定する-2

findMaxima(...)の中身を見てみる。
まず、
Rectangle roi = ip.getRoi();
とあり、その後の処理(local maximaの探すところ)では、
for (int y=roi.y; y<roi.y+roi.height; y++) {         //find local minimum/maximum now

            for (int x=roi.x; x<roi.x+roi.width; x++) {      //ImageStatistics won't work if we have no ImagePlus

...

となっている。明らかに矩形のROIとしてしか取り出していない。
次に呼び出されるメソッドは、
getSortedMaxPoints(ip, typeP, excludeEdgesNow, isEDM, globalMin, globalMax, threshold)
であるが、このメソッドの中も
Rectangle roi = ip.getRoi();
となっていて、やはり矩形のROIとしてしか取り出していない。

さて、Maximaの探索には、さらに次のメソッド
analyzeAndMarkMaxima(ip, typeP, maxPoints, excludeEdgesNow, isEDM, globalMin, tolerance, strict, outputType, maxSortingError);
で行われている。このメソッドの中をみると、
        if (imp!=null)

            roi = imp.getRoi();

なんと、ipではなくimp (ImagePlus)からROIを取り出している。マジか!
さらにソースを斜め読みしていくと、
                            if (roi==null || roi.contains(x, y))

                                xyCoordinates.addPoint(x, y);
となっていた。これは、検出したMaximaがroiの中に含まれるかどうか判定している個所と考えられる。

findMaxima (MaximumFinder)で解析範囲を指定する-1

とても久しぶりに更新。
輝点検出に便利なfindMaxima。JavaではMaximumFinderクラスを使う。
コンストラクタは、シンプルにMaximumFinderのみ。
MaximumFinder mf = new MaximumFinder()
輝点検出には、主にgetMaximaか、findMaximaを使う。
java.awt.Polygon getMaxima(ImageProcessor ip, double tolerance, boolean excludeOnEdges)

ByteProcessor findMaxima(ImageProcessor ip, double tolerance, int outputType, boolean excludeOnEdges)
findMaximaの方は、outputTypeにMaximumFinder.Maskなどとしておけば、Mask画像を返してくれる。getMaximaの方は、輝点をPointRoiとして利用したい場合に便利。Polygonとしてもらっておけば、例えば、
poly = mf.getMaxima(ip, tolerance, false);

PointRoi pr = new PointRoi(poly.xpoints, poly.ypoints, poly.npoints);

imp.setRoi(pr);
とすれば、検出した輝点をPointRoiとして表示してくれる。

さて、画面全体に対して輝点検出する場合はこれでよいが、例えば画面の一部分ROIで区切った個所だけを検出することができると、MaximumFinderの説明文に書いてある。

Except for segmentation, this plugin works with area ROIs, including non-rectangular ROIs, which define the area where maxima are reported.

円形のROIを設定することにする。getMaximaの引数がImageProcessor ipなので、
ip.setRoi(ovalRoi);
poly = mf.getMaxima(ip, tolerance, false);
とでもすれば、 指定できるのかと思いきやうまくいかない。円なのに矩形領域になってしまう。
じゃあ、ImagePlusに指定すればいいのかと思い、
imp.setRoi(ovalRaoi);
としてもダメ。ImageJの掲示板に似たような議論があったが、ip.setRoiでうまくいくよ、と書いてあるけどうまくいかない。
discrepancy between APi and IJ.run in Find maxima

こういう時は、MaximumFinderのソースコードを見る必要がある。
MaximumFinder.java
例えば、上記のgetMaximaを見てみると、
public Polygon getMaxima(ImageProcessor ip, double tolerance, boolean excludeOnEdges) {
return getMaxima(ip, tolerance, excludeOnEdges, excludeOnEdges);}
public Polygon getMaxima(ImageProcessor ip, double tolerance, boolean strict, boolean excludeOnEdges) {
 findMaxima(ip, tolerance, strict, ImageProcessor.NO_THRESHOLD,
  MaximumFinder.POINT_SELECTION, excludeOnEdges, false);
 if (xyCoordinates==null)
  return new Polygon();
 else
return xyCoordinates;}
となっており、結局findMaximaを呼んでいることがわかる。
2へ続く

2018年12月27日木曜日

MacroとPluginについて

最近はもっぱらMacroで書くことが多い。自由度はPluginの方が高いのだが、開発のスピードは圧倒的にMacroの方が早い。JavaはCompileしなければいけないし、ちゃんとテスト環境を構築しないといけないからめんどい。Eclipseはほとんど立ち上げなくなってしまった。

MacroからPythonなどを呼び出せば、最悪複雑な処理はPythonに投げてしまえる。Jythonはあんまり使う気になれない。numpyとか使えないし。
また、MacroからもJavaのクラスメソッドなら呼び出せるし。
call("class.method", arg1, arg2, ...)

三浦耕太さんが作った、Vim用のHighlighterを使って、コマンドラインからMacroを呼び出すようにしてやれば、開発もかなりはかどる。Fijiの最近のScriptは登録されたコードの補完はできるようになったが、変数の補完はやってくれないので、そういう時はVimの補完機能が超便利。これでだいぶバグが減る。

2018年12月13日木曜日

誰も教えてくれないImageJの使い方(というかマクロの使い方)その2 マクロからマクロを呼ぶ

マクロから他のマクロを呼ぶ場合は二通りある。

  1. pluginsフォルダーにいれて、runコマンドで呼び出す
  2. macrosフォルダーにいれて、runMacroコマンドで呼び出す

簡単なのはマクロをmacrosフォルダーではなくpluginsフォルダーに置いて、
run(macroName, dialogParameter);

で呼ぶ。この時、呼ばれる側のマクロがDialogを開く設定にしておけば、dialogParameterに必要な引数を渡せば、呼び出される側にパラメータを渡すことができる。dialogParameterの記述方法を知るには、Recorederを使って記録しておけばよい。
例えば例として以下のようなマクロを考えてみる。
/*
 * add_Test.ijm
 */
Dialog.create("add test");
Dialog.addNumber("a", 1);
Dialog.addNumber("b", 1);
Dialog.show()
a = Dialog.getNumber();
b = Dialog.getNumber();
print("a + b = ", a + b);

このマクロをpluginsフォルダーに保存して、pluginメニューから実行すると、Recorderには以下のように記録される。
run("add Test", "a=1 b=1");

このようにDialogを開くようにしておけば、パラメータとして引数を渡すことができる。ちなみにPluginを作る際にもこのテクニックは利用できる。

しかし、この方法ではパラメータを与えることはできても受け取ることはできない (returnが使えない)。従って、例えば呼び出す側で
ret = run("add Test", "a=1 b=1");
と書いてもエラーがでる。

引数を受け取ることのできるようにするには、runではなくrunMacro(macroName, parameter)で呼び出す。ただし、上記のようにDialogのパラメータとして直接渡すことはできないし、呼び出すとDialogが開いてしまう。そこで、呼び出される側のマクロ (ここではadd_Test.ijm)を次のようにする。
/*
 * add_Test.ijm
 */

arg = getArgument();
if (arg == "") {
 Dialog.create("add test");
 Dialog.addNumber("a", 1);
 Dialog.addNumber("b", 1);
 Dialog.show()
 a = Dialog.getNumber();
 b = Dialog.getNumber();
} else {
 items = split(arg, ",");
 a = parseInt(items[0]);
 b = parseInt(items[1]);
}
return toString(a+b);
getArgument()は、このマクロが別のマクロから呼び出された場合、parameterで指定した文字列が入る。そこで、もしこの文字列が空の場合はDialogを開くように設定することで、このマクロ単体でも実行チェックを行うことができる。
argにparameterが入っていれば、そこから要素を取り出す。渡すことのできるのは一つの文字列なので、例えば","で各パラメータを区切ってやる。そこから各要素をとりだすために、
items = split(arg, ",");

を行うと、各要素をitems配列に返してくれる。あとは、各要素を取り出してやればよい(数値ならparseIntや parseFloatを使う)。
さらに、結果を
return toString(a+b);

で文字列として返してやることができる(マクロの場合のパラメータのやりとりは常に文字列のみ)。こうすれば、呼び出す側で例えば
ret = runMacro("add_Test.ijm", "1,1");

としてやれば、retに結果を文字列として返すことができる。

まとめると、お手軽なのはpluginsフォルダーに入れる方法だが、後々のことを考えると、runMacroで呼び出せるようにしておいた方がよいと思う。

2018年12月5日水曜日

誰も教えてくれないImageJの使い方 -ツールバーへの登録-

久しぶりの更新。あんまり知らないだろうな、という方法をこそっと紹介してみる。

ImageJのツールバーへの登録方法。Built-in-Macroにある
newMenu(macroName, stringArray)
を使う。macroNameにはメニュー処理するMacro、stringArrayにはメニューの項目を登録する。実際の例として、ToolBar Menuに記載がある。一部抜粋すると、

var dCmds = newMenu("Developer Menu Tool",
      newArray("ImageJ Website","News", "ImageJ Wiki", "Resources", "Macro Language", "Macros", "Macro Functions", "Startup Macros...", "Plugins", "Source Code", "List Archives", "-", "Record...", "Capture Screen ", "Monitor Memory...", "Find Commands... ", "Control Panel...", "Search...", "Debug Mode"));
      
macro "Developer Menu Tool - C037T0b11DT7b09eTcb09v" {
       cmd = getArgument();
       if (cmd=="ImageJ Website")
           run("URL...", "url=http://rsbweb.nih.gov/ij/");

ルールとして、Macro名(macroName)の後ろには"Menu Tool"を付ける。stringArray(直接newArrayで登録しているが)にメニューの項目を設定する。ここまではいいのだが、では例えば以下のようにしても、アイコンがでずうまく登録できない。

var dCmds = newMenu("TestMenu Tool", newArray("Test1macro", "Test2macro");

macro "Test Menu Tool" {
    cmd = getArgument();
    runMacro(cmd);
}

なぜなら、登録すべきアイコンを設定していないから!
ここでサンプルをみると、
macro "Developer Menu Tool - C037T0b11DT7b09eTcb09v" {
とあり、Menu Toolの後ろになにやらよくわからない文字列が並んでいる。しかしサンプルのどこをみてもこれの説明はないし、ググっても見つからない。最初、アイコンの画像を参照するIDか何かかと思って、ij.jarを解凍してみたけど、それっぽいリソースは見つからなかった。
うーん、困った。困ったときにはImageJのソースコードを見るのが良い。メニューを登録しているクラスは、ij.gui.Toolbarっぽい。さらに探すと、void drawIconなるメソッドが見つかる。ちょっと長いけど、メソッドを張り付けてみる。

void drawIcon(Graphics g, int tool, int x, int y) {
        if (null==g) return;
        icon = icons[tool];
        if (icon==null) return;
        this.icon = icon;
        int x1, y1, x2, y2;
        pc = 0;
        while (true) {
            char command = icon.charAt(pc++);
            if (pc>=icon.length()) break;
            switch (command) {
                case 'B': x+=v(); y+=v(); break;  // reset base
                case 'R': g.drawRect(x+v(), y+v(), v(), v()); break;  // rectangle
                case 'F': g.fillRect(x+v(), y+v(), v(), v()); break;  // filled rectangle
                case 'O': g.drawOval(x+v(), y+v(), v(), v()); break;  // oval
                case 'V': case 'o': g.fillOval(x+v(), y+v(), v(), v()); break;  // filled oval
                case 'C': // set color
                    int v1=v(), v2=v(), v3=v();
                    int red=v1*16, green=v2*16, blue=v3*16;
                    if (red>255) red=255; if (green>255) green=255; if (blue>255) blue=255;
                    Color color = v1==1&&v2==2&&v3==3?foregroundColor:new Color(red,green,blue);
                    g.setColor(color);
                    break; 
                case 'L': g.drawLine(x+v(), y+v(), x+v(), y+v()); break; // line
                case 'D': g.fillRect(x+v(), y+v(), 1, 1); break; // dot
                case 'P': // polyline
                    Polygon p = new Polygon();
                    p.addPoint(x+v(), y+v());
                    while (true) {
                        x2=v(); if (x2==0) break;
                        y2=v(); if (y2==0) break;
                        p.addPoint(x+x2, y+y2);
                    }
                    g.drawPolyline(p.xpoints, p.ypoints, p.npoints);
                    break;
                case 'G': case 'H':// polygon or filled polygon
                    p = new Polygon();
                    p.addPoint(x+v(), y+v());
                    while (true) {
                        x2=v(); y2=v();
                        if (x2==0 && y2==0 && p.npoints>2)
                            break;
                        p.addPoint(x+x2, y+y2);
                    }
                    if (command=='G')
                        g.drawPolygon(p.xpoints, p.ypoints, p.npoints);
                    else
                        g.fillPolygon(p.xpoints, p.ypoints, p.npoints);
                    break;
                case 'T': // text (one character)
                    x2 = x+v()-1;
                    y2 = y+v();
                    int size = v()*10+v()+1;
                    char[] c = new char[1];
                    c[0] = pc<icon.length()?icon.charAt(pc++):'e';
                    g.setFont(new Font("SansSerif", Font.PLAIN, size));
                    g.drawString(new String(c), x2, y2);
                    break;
                default: break;
            }
            if (pc>=icon.length()) break;
        }
        if (menus[tool]!=null && menus[tool].getItemCount()>0) { 
            xOffset = x; yOffset = y;
            drawTriangle(15, 15);
        }
    }
なにやらSwich文でBやらFやらOやらCやらを場合分けしている。。実は先ほどの意味のよくわからない文字列
C037T0b11DT7b09eTcb09v
これには、アイコンを描画するための命令が記述されていた!
例えば最初の
C037
の”C”は描画色を決めている。そのあとの3桁の数字はそれぞれRGBに対応しており、この値に16を乗じたものになる。したがってここでは(R,G,B) = (0, 3*16, 7*16)という色を指定している。
次の
T0b11D
の”T"は、文字を指定する。最初の0bはXY座標を示しており、X=0, Y=b (=11, 16進数)を表す。次の"11"はフォントサイズ。最後の"D"は実際に表示する文字を示す。
こんな感じでアイコンを直接描画することができる。
利用できるコマンドをソースコードから類推すると
  1. Bxy: ベース(開始XY座標)をxyでリセットする?
  2. Rxywh: Rectangleの描画 (x, y, w, h)
  3. Fxywh: FillRectangleの描画 (x, y, w, h)
  4. Oxywh: Ovalの描画 (x, y, w, h)
  5. Vxywh: FillOvalの描画 (x, y, w, h) ("o"でもよいみたい)
  6. Crgb: 描画色の決定 (r*16, g*16, b*16)が実際の色
  7. Lxyx'y': Lineの描画 (x, y, x', y')
  8. Dxy: Dotの描画 (x, y)
  9. Pxyxyxyxyxy... : PolyLineの描画
  10. Gxyxyxyxyxy... : Polygonの描画 ("H"でもよいみたい)
  11. Txysc : Text cの描画 (x, y), fontsize = s
これを使えば好きなアイコンを作ることができる。座標の指定は0~fの16なので、実質16x16ピクセルと思えばよさそう。


2017年5月10日水曜日

writeとprint

writeとprint

ImageJでマクロを書くときに、Logウィンドウへの書き出しはいつもwrite関数で行っていた。しかし、一般的にはprint関数が使われている。
ImageJ のBuilt-in Macro Functionsには, 実はwrite関数は明示されてない.現在では,logウィンドウへの書き出しはwrite関数ではなく,print関数が一般的である.では,write関数とprint関数では動作は異なるのだろうか?ImageJのソースコードを見ると、ij.macro.Functionsパッケージに,void doFunction(int type)という関数があり、この中に、
switch (type) {
…
    case PRINT: case WRITE: print(); break;
…
と書かれた場所がある.switch関数はtypeの中身に応じて処理を振り分けるJava言語の命令であり,処理の数だけcase が用意されてある.ここで示されたcase行の意味は,「typeにPRINTやWRITEが格納されていれば,print()関数を実行せよ」である.従って,マクロでprintと書こうが,writeと書こうが同じ処理となる.このような仕組みはおそらく互換性を維持するためのものと思われる

2017年2月11日土曜日

輝点の検出を楽に行う

FindMaximaを使う

[Process -> FindMaxima]を使うと、画面上の輝点などを一瞬で検出してくれるため、輝点追跡をする際の初期パラメータを与えるためなどに重宝する。Pluginから当然利用することが可能。

MaximumFinderクラス

FindMaximaを使うには、MaximumFinderクラスを利用する。
import ij.plugin.filter.MaximumFinder;
import java.awt.Polygon;

...

ImageProcessor ip = imp.getProcessor();
double tol = 10;

MaximumFinder mf = new MaximumFinder();
Polygon mp = mf.getMaxima(ip, tol, true);

...
  1. まず、MaximumFidnerオブジェクトを作成する。
  2. 輝点の検出には、MaximumFinderのメソッドgetMaxima(ImageProcessor ip, double tolerance, boolean excludeOnEdges)を使う。ipで現在開いている画像のImageProcessorを渡す。toleranceは輝点を検出する際の閾値を決める。excludeOnEdgesは、画面のエッジ上の輝点を検出するかどうか決める。
  3. 検出された輝点の位置情報を、java.awt.Polygon型として返す。
    • 格納される順番は、輝度値が高い順番となる
  4. 輝点の座標情報には以下のようにアクセスすればよい
int ind = 0;
double x = mp.xpoints[ind];
doulbe y = mp.ypoints[ind];
Polgonのメンバ変数であるxpoints, ypointsを使えば、座標データに直接アクセスすることが可能である。これを利用すれば、輝点の自動検出や輝点のトラッキングなどに応用することが可能となる。
以上