Translate

2019年12月5日木曜日

Apache commons math の Levenberg-Marquardt法で、パラメータに制約条件を付ける

1か月ぶりの更新。
かなりマニアックなネタ。

Apache commons mathは、数値計算用のJavaのライブラリで、Levenberg-Marqurdt法によるフィッティングや、機械学習など、いろんなメソッドが用意されている。Fijiにも外部ライブラリとして利用されている。
2次元のガウス分布でのフィッティング(例えば蛍光輝点のフィッティングなど)には、
optimizationライブラリの、LevenbergarquardtOptimizerを使う。
詳しいやり方は省くが、

  1. モデル関数とそのモデル関数のjacobianを、それぞれMultivariateVectorFuncion, MultivariateMatrixFunctionとして作る
  2. LevenbergMarquardtOptimizerのオブジェクトを作る
    LevenbergMarquardtOptimizer lmo = new LevenbergMarquardtOptimizer();
  3. LeastSquaresProblemを作り、lmo.optimizeのパラメータとして渡す。返り値として、最適化されたパラメータを受け取る
    LeastSquaresOptimizer.Optimun lsoo = lmo.optimize(lsp); // lsp -> LeastSquaresProblemのオブジェクト
ここで、lspはLeastSquaresBuilderで作ることができる。
LeastSquaresBuilder lsb = new LeastSquaresBuilder();
lsb.model(modelfunction(), jacobianfunction()); // モデル関数とそのjacobian
lsb.target(data); // 入力データ
lsb.start(newStart); // パラメータの初期値
lsb.maxIterations(1000); // 最大itterationの回数

例えば有名なグラフ・統計解析ソフトのOriginでは、非線形フィッティングの際に、パラメータに制約条件を付けることができる。Apache commons mathでもできるが、詳しいやり方はちゃんと書いてない。一応、LeastSquaresのParameters Validationにそれっぽい記述がある。

In some cases, the model function requires parameters to lie within a specific domain. For example a parameter may be used in a square root and needs to be positive, or another parameter represents the sine of an angle and should be within -1 and +1, or several parameters may need to remain in the unit circle and the sum of their squares must be smaller than 1. The least square solvers available in Apache Commons Math currently don't allow to set up constraints on the parameters. This is a known missing feature. There are two ways to circumvent this.

Both ways are achieved by setting up a ParameterValidator instance. The input of the value and jacobian model functions will always be the output of the parameter validator if one exists.
どうやらこの、ParameterValidatorを使えばできそうな雰囲気だが、Javadocを見てもほとんど情報がない。ParameterValidatorはinterfaceで、validate(RealVector params)というメソッドを実装するインターフェースである。なんとなくこのparamsというのが怪しそうだが、RealVector型もよくわからん。

LeastSquaresBuilderで作ったlsbのメソッドで、parameterValidatorがあるのでなんとなく使ってみる。
lsb.parameterValidator(new ParameterValidator() { ... }
ここで、parametervValidatorの引数はParameterValidatorを実装するクラスだが、いちいちクラスを作るのは面倒臭いので内部クラスとして作る。
lsb.parameterValidator(new ParameterValidator() {
    @Override
    public RealVector validate(RealVector params) { ... }
});
Eclipseを使えば、インターフェースで実装するべきメソッドを自動で作ってくれる。ここで、先ほどの記述に戻ると、
The input of the value and jacobian model functions will always be the output of the parameter validator if one exists.
なんとなく訳すと、「値とjacobian モデル関数の入力は、Parameter Validatorが存在するときには常にその出力となります」というようなことが書いてあり、多分iterationのたんびにparameterValidatorが呼び出されることになりそう。あとは、パラメータにアクセスできればよい。 RealVector型であるparamsからパラメータにアクセスするには、
getEntry(int index)
setEntry(int index, double value)
メソッドを使えばよい。indexはパラメータを示すインデックスである。 例えば、
public RealVector validate(RealVector params) {
 System.out.println("parameter[0]: " + params.getEntry(0));
 return params;
}
としてフィッティングを行うと(ここではとあるモデル関数によるフィッティングを行っている)、
v0: 255.0
v0: 76.64496657297977
v0: 215.79832218070536
...
v0: 258.1698480422253
というように、iterationごとでv0がどのような値になったかを出力してくれるようになる。
従って、例えばv0は必ず0より大きな値をとる必要があるのであれば、
if (params.getEntry(0) < 0) params.setEntry(0, 0.00001);
とすれば、もしiterationの途中で0以下となったとしても、0.00001に強制的に引き戻すことができる(ここで0にすると、パラメータが0に張り付いてしまうことがあるので、微妙に0より大きな値にしている)。
これで、フィッティングのパラメータに制約を与えることができるようになった。

ちなみにこの方法をネット上で検索したが、英語のサイトでもまず見つからない。かなりレアな情報だと思う。

2019年10月28日月曜日

Logウィンドウ以外にprintの出力を出す

printは普通に使うとLogウィンドウに結果を出力する。
Logウィンドウとは違うウィンドウに結果を出力するには、
print(textTitle, string);
を使う。
textTitleは
run("Text Window...", "name=textTitle width=80 height=20 menu");
のようにして作成したテキストウィンドウを指す。このとき、textTitleは"[]"で囲まれている必要がある。
textTitle = "[test text]";
run("Text Window...", "name=" + textTitle + " width=80 height=20 menu");
print(textTitle, "Hello mac\n");
print(textTitle, "DomDom burger");

とかいう感じ。printはそのままでは改行してくれないので、複数行にするときは改行コードを入れておく。


2019年10月21日月曜日

ImageJのデバッグ機能

ImageJのMacro editorは普通のテキストエディタで、予約語や変数をハイライトしてくれないし、インデントも自動で設定はしてくれない。
マクロを書くならScript EditorがあるFijiを使うのが断然楽だ。だが、Fiji使いでも普通のImageJは別途入れておいた方が良い。
ImageJのMacroEditorには、Debug機能がついている。

プログラムは常にバグを含んでいる。構文ミスなどのエラーは修正が容易だが、エラーは出ずにでもなんか動作がおかしい、というバグは厄介である。
バグを修正することをデバッグというが、ImageJのMacroEditorにはその機能がある。EditorのDebugメニューから使うことができる。

  • Debug Macro ... デバッグモードに入る
  • Step ... 1行ずつ実行
  • Trace ... ゆっくり実行
  • Fast Trace ...速度をあげて実行
  • Run ... 普通に実行
  • Run to Insertion Point ... カーソル位置まで実行(カーソル位置の文は実行しない)
  • Abort ... やめる
となっている。デバッグモードになれば、変数の中身も別ウィンドウで確認することができる。

作成はFijiでDebugはImageJで、という感じで使いたい(Fijiでできれば話は早いのだが、デバッグ機能の実装が難しい、というような話を聞いた)。

2019年10月16日水曜日

輝点の検出を楽に行う -macro-

以前、輝点の検出を楽に行うとしてpluginからMaximumFinderを使う方法を記述したが、Macroからは、FindMaximaを使う。
run("Find Maxima...", "prominence=10 output=[Point Selection]");
(最新のImageJバージョンは、prominenceだが、ちょっと前のバージョンまではtoleranceというパラメータだった)。
これだけでPointRoiを作ってくれる。
このPointRoi位置の輝度の情報は、Measure...をすればResults Tableとして得ることもできるが、Macro内でアクセスする場合は、
getStatistics(area, mean, min, max, std, histogram);
で簡単に取得できる。(このコマンドを実行すれば、各要素が変数area, mean, min, max, histogramに格納される。histogramは配列)

さらに、PointRoiの個々の座標は、
Roi.getCoordinates(xpoints, ypoints);
とすれば、各座標はxpoints、ypointsに配列として格納できるので、

for (i = 0; i < lengthOf(xpoints); i++) {

 print(xpoints[i], ypoints[i]);

}
とでもすれば、各座標を取り出すことができる。

2019年10月11日金曜日

public static void mainとresourceへのアクセス

Public static void main

Eclipseでmavenを使ってjarファイルのpluginを作る場合、動作確認はjarファイルをImageJのpluginsフォルダに移して実行、ということが多い。作ってはImageJを立ち上げなおして、というのは結構面倒臭い。
そこで、public static void main(String args[])を作っておけば、EclipseのRunからJava applicationとして簡単にテストすることができる。
ijをインスタンスしなくても、ImageJのクラスは普通に使えるので、特にplugin filterを作る場合などに便利。
import java.net.URI;
import java.net.URISyntaxException;

import ij.IJ; import ij.ImagePlus; import ij.plugin.filter.PlugInFilter; import ij.process.ImageProcessor;
public class Hoge implements PlugInFilter {
@Override public int setup(String arg, ImagePlus imp) { // TODO Auto-generated method stub return DOES_ALL; }
@Override public void run(ImageProcessor ip) { // TODO Auto-generated method stub
}
public static void main(String[] args) { // TODO Auto-generated method stub URI img; try { img = ClassLoader.getSystemResource("test.tif").toURI(); ImagePlus imp = IJ.openImage(img.getPath()); Hoge hogehoge = new Hoge(); hogehoge.setup("", imp); hogehoge.run(imp.getProcessor()); } catch (URISyntaxException e) { // TODO Auto-generated catch block e.printStackTrace(); }
}

}

Hogeのオブジェクトを作り、setupでImagePlusを、runでImageProcessorを送ることができる。

Resourceフォルダへのアクセス

テスト用の画像データは、resourceフォルダに入れておく。resourceフォルダに入れたファイルは、以下のようにしてアドレスを取り出す。
URI img = ClassLoader.getSystemResource("test.tif").toURI();
ImagePlus imp = IJ.openImage(img.getPath());

これで開発はかなり楽になる。

2019年10月2日水曜日

(小ネタ)pluginからmacroに値を渡す

PluginからMacroに値を渡す方法がImageJのメーリングリストにあった。
How to return a value from a plugin to a macro?
ここのMicheal schmidさんの投稿にあるように、
plugin側では、
public class My_Plugin extends ... {

     static double myResult;

     void run(ImageProcessor ip) {

        ...

         myResult = ...;

     }

     static String getResult() {

         return myResult.toString();

     }
}
としておき、macro側では
myResult = parseFloat(call("My_Plugin.getResult"));
とすれば渡せると思いきや、これを実行すると、getResultなんてありませんよ、とエラーがでると思う。
static String getResult() {...}
を、
public static String getResult() {...}

にしないと、外部からアクセスすることができないから。ちなみにテキスト以外は無理ぽいので、複数の値を渡すときは、カンマ区切りなどにしておき、macro側で、
items = split(myResult, ",");

として要素を取り出せばよい。 Pluginにしかできないことも多々あるが、macroの方が手軽なので、できるだけmacroで作りつつ、外部ライブラリを使いたい場合などはpluginを最小限利用するのが良い。

2019年9月12日木曜日

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

繰り返しになるが、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)
であり、impはない。しかし、findMaximaの中ではimpを参照していることから、mfにimpを入れないといけない。 そこで、改めてメソッドを眺めてみると、
int setup(java.lang.String arg, ImagePlus imp)
あった!ありました!
たまに見るsetupメソッドだが、MaximumFinderで唯一impをパラメータとしている。ソースを見てみると、
public int setup(String arg, ImagePlus imp) {
        this.imp = imp;
        return flags;
    }
だけ。impをメンバ変数のimpに代入するだけのメソッドである。これを使えばよい。 まとめると、non-rectangularなRoiを解析範囲として指定するためには、
imp.setRoi(ovalRaoi);
MaximumFinder mf = new MaximumFinder()
mf.setup("", imp);
poly = mf.getMaxima(ip, tolerance, false);
とすればよい。imp.setRoi(ovalRoi)の時点で、ipにもovalRoiがセットされている。 以上がMaximumFinderでnon-rectangularな解析範囲をする方法だ(と思う)。 ここまでたどり着くの相当疲れた。。

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を使えば、座標データに直接アクセスすることが可能である。これを利用すれば、輝点の自動検出や輝点のトラッキングなどに応用することが可能となる。
以上

2017年2月6日月曜日

ToDo List

ToDo List

ブログで書く予定の内容。随時更新。

Basic

  • [ ] ImagePlusとImageProcessor
  • [ ] ImageStack
  • [ ] Java
  • [ ] ImageStatistics

Listener系

  • [x] MouseListener
  • [x] ImageListener

ImageProcessor系

  • [x] Threshold
  • [x] MaximumFinder

ImageCanvas系

  • [ ] Overlay

ImagePlus系

  • [ ] Calibration

GUI系

  • [ ] Table
  • [ ] GenericDialog

外部ライブラリ

  • [ ] JFreeChart
  • [ ] JNI
  • ApacheCommonsMath
    • [ ] Curve Fitting
    • [ ] Matrix

Call from Macro

Environment

  • IDE
    • [ ] Eclipse
    • [ ] IntelliJ IDEA
  • [ ] Git
  • [ ] Maven
  • Mindmap
    • [ ] Xmind
    • [ ] mindmup
  • TextEditor
    • [ ] Vim
    • [ ] SublimeText3

2017年2月4日土曜日

画像に閾値が設定されているかどうか知る

Thresholdについて

ThresholdはImageProcessorのメソッドを使う。よく使うのは以下の二つ。
public void setThreshold(double minThreshold,
                         double maxThreshold,
                         int lutUpdate)

public double getMinThreshold()
lutUpdateにはRED_LUTやBLACK_AND_WHITE_LUT, OVER_UNDER_LUT, NO_LUT_UPDATEがあり、ImageProcessorの静的なメンバとして値が設定されている。なので、
ImageProcessor ip = imp.getProcessor();
ip.setThreshold(min, max, ImageProcessor.RED_LUT);
などのように使う。ここで、minの値を-808080.0DにセットするとThresholdを解除することができる。あるいは、ip.resetThreshold()というメソッドも使える。
また、getMinThoreshold()で返ってきた値が-808080.0Dあるいは、ImageProcessor.NO_THRESHOLDと同値であれば、その画像にはThresholdがかかっていないことを知ることができる。
これらメソッドは、例えばオブジェクトの自動検出のために閾値を利用する場合、閾値が設定されているかどうか事前に調べるために使うことができる。
以上

2017年2月3日金曜日

マウスのイベントを取得する

MouseListnerの実装

マウスのイベントを取得するためにはMouseListenerを利用する。MouseListenerはImageCanvasで実装する
public class hoge extends PlugInFrame {
    public static icMouseAdapter ima;
    public static ImagePlus imp;

    ...

    public hoge() { 
        imp = WindowManager.getCurrentImage();
        ImageCanvas ic = imp.getCanvas();
        ic.addMouseListener(ima = new icMouseAdapter(imp));

        ...

    }
}
ij.WindowManager.getCurrentImage()を使うと、現在アクティブな画像のImagePlusオブジェクトを取得することができる。

MouseAdapterクラス

ImageCanvasオブジェクトを取得したのち、addMouseListenerメソッドで実装する。ここでインスタンスしているicMouseAdapterは以下のようなクラスになる。
public class icMouseAdapter extends MouseAdapter implements Measurements {

    ... 
    public ImagePlus imp;

    public icMouseAdapter(ImagePlus imp) {
        this.imp = imp;
    }

    public void mouseClicked(MouseEvent e) {
        // マウスがクリックされたときの処理
    }
}
MouseAdapterクラスは、マウスイベントを受け取るための抽象アダプタクラスである。マウスのイベントには当然ほかにもいろいろあるが(ドラッグとかホイールの回転とか)、この場合継承しているのでinterfaceと違い、すべてのメソッドを実装する必要はない。
メソッドのパラメータMouseEvent eを使えば、マウスの座標情報を得ることができる。
int x = e.getX();
int y = e.getY();
ただし、もし画像が拡大・縮小されていた場合、マウスをクリックした場所の座標データを取得すると、本当に欲しい画像上の座標ではなく、拡大・縮小された画像での座標を取得することになってしまう。そこで、ImageCanvas.offScreenX(int x)を使う。
int x = ic.offScreenX(e.getX());
int y = ic.offScreenY(e.getY());
これで、マウスがクリックされた場所の座標データを取得することができる。

MouseListenerインターフェース

一方、Interfaceを使ってマウスイベントの取得方法を使うこともできる。

public class icMouseAdapter implements MouseListener, Measurements {

    ... 
    public ImagePlus imp;

    public icMouseAdapter(ImagePlus imp) {
        this.imp = imp;
    }

    public void mouseClicked(MouseEvent e) {
        // マウスがクリックされたときの処理
    }
    public void mouseEntered(MouseEvent e) {
        // マウスがコンポーネントに入ったときの処理
    }
    public void mouseExited(MouseEvent e) {
        // マウスがコンポーネントから出たときの処理
    }
    public void mousePressed(MouseEvent e) {
        // マウスのボタンが押されたとき
    }
    public void mouseReleased(MouseEvent e) {
        // マウスのボタンが離れたとき
    }
}
MouseListenerはInterfaceなので、使わないメソッドもすべて実装する必要があるが、もしほかのクラスを継承したいためにMouseAdapterが使えない(多重継承できないため)場合には、このInterfaceが使える。
プラグインが終わるなどしてMouseListenerの取り除く場合は
ic.removeMouseListener(ima);
としてやればよい(ImageListenerの場合と同じようにする)。
以上

2017年2月2日木曜日

画像が更新されたことを知る

ImageListenerの実装

画像が更新されたときにOverlayを書き直したり、画像を新しく開いたり閉じたりした際に応答するためには ImageListenerを実装する。
Interface ImageListener
ImageListenerには3つのabstract methodsがある。それぞれ、
void    imageClosed(ImagePlus imp) 
void    imageOpened(ImagePlus imp) 
void    imageUpdated(ImagePlus imp) 
で、文字通り、imageClosedは画像が閉じたとき、imageOpenedは画像が開いたとき、imageUpdatedは画像が更新されたとき(例えばimp.updateAndDraw()が呼び出されたとき)に実行される。
このinterfaceはPluginのサブクラスやPluginFilterを実装するクラス(要はPluginのクラス)に実装するのではなく、
static void addImageListener(ImageListener listener) 
と、ImagePlusのクラスメソッドであるaddImageListenerで実装する。手っ取り早いのは、内部クラスとしてPluginの中で実装してしまう方法だ。
... Pluginのなんらかの処理

ImagePlus.addImageListener(new ImageListener() {

    @override
    public void imageClosed(ImagePlus imp) {
    //画像を閉じたときの処理
    }

    @override
    public void imageOpened(ImagePlus imp) {
    //画像を開いたときの処理
    }

    @override
    public void imageUpdated(ImagePlus imp) {
    //画像が更新されたときの処理
    }
}
ただ、こうすると、例えばPlugInFrameを閉じてもlistenerが残ったままになるので具合が悪い。
ImagePlusからlistenerを取り除くには、
static void ImagePlus.removeImageListener(ImageListener listener)
を実行すればよい。なので、ImageListnerを実装するときはこうしている。
public class hoge extends PlugInFrame {
    ...
    public static ImageListener listener;
    ...

    public hoge() {
        ...
        ImagePlus.addImageListener(new listener = ImageListener() {
            ... 
        }

        addWindowListener(new WindowAdapter() {
            @override
            public void windowClosing(WindowEvent e) {
                //フレームが閉じる時の処理
            }

            @override
            public void windowClosed(WindowEvent e) {
                //フレームが閉じたときの処理
                ImagePlus.removeImageListener(listener);
            }
        }
    }
}
まず、staticなメンバ変数としてlistenerを宣言している。そして
ImagePlus.addImageListener(new listner = ImageListener() {
とすることで、listenerをnewしている。別にちゃんとクラスを用意してもいいが、面倒くさいのでいつもこうしている。
WindowAdapterは、フレームが閉じたりするときの動作を記述することができるサブクラスで、addWindowListenerにそのサブクラスを継承するクラスを渡せばよい。これもいつも内部クラスで済ます。interfaceと違い、すべてのメソッドを実装する必要はない。
以上

2017年1月31日火曜日

kotlinでplugin5

KotlinでPlugin開発-5

前回まででコードはできたので、ビルドしてPluginの形式にする。

plugins.config

ImageJのPluginには.classと.jarの2種類がある。.classファイルをPluginsフォルダーに入れれば、そのままファイル名がPlugin名となる。一方、複数のclassを含むような場合は、.jarファイルにまとめてしまう。その際、メインとなるクラスがどれかを指定する必要がある。plugins.configファイルを作れば、どのクラスがメインか、さらにはどのメニューに表示されるかを設定することができる。
plugins.configは、src\main\resourcesに作る。resourcesを右クリックして、[New -> File]を選ぶ。ファイル名はplugins.configとする。



中身はこんな感じ。
Plugins, "test1", jp.yo4.main.test
“,”でパラメータが区切られている。
  • 最初のパラメータはメニューを示す。ここを変えれば(例えばAnalyzeやProcessなど)、そこにPluginが表示されるようになる。
  • 次のパラメータは、メニューに表示される項目名を指定する。”“で囲む。
  • 最後のパラメータは、Pluginとして呼び出されるクラスを指定する。この際、パッケージ名をフルで指摘しないと実行されない。

jarファイルの作成

jarファイルはmavenのlifecycleで指定する。[View -> Tool Windows -> Maven Projects]を選ぶと、右側にMaven Projectsというウィンドウが現れる。ここで、プロジェクト名を展開し、Lifecycleも展開する。複数項目が現れるが、packageが目的のlifecycleになる。


packageを選ぶと、あらかじめpom.xmlに設定したdependencyなどの項目に従ってビルドが始まり、必要なファイルをすべて包含したjarファイルを作成してくれる。作成されたjarファイルは、Projectのtargetフォルダに作られる。あとは、このファイルをImageJのPluginsフォルダに放り込めば完成する。


おわり

KotlinでのPlugin開発は思ったより簡単にできる。今回はnull安全など、Kotlinらしい機能はあまり目立たなかったが、今後は、もっと活用したより大きなPluginを作ってみたい。

(2017/02/01: 少し修正)

2017年1月30日月曜日

kotlinでplugin4

KolinでPlugin開発-4

メソッドの中身を書く-2

run

IntelliJ IDEAが自動生成したrunメソッドは次の通り。
    override fun run(p0: ImageProcessor?) {
        throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
例によってImageProcessorの引数p0はipに変えておく。
ここでは、単純にinvert処理を行う。Javaではシンプルに、
    ip.invert();
とすればよいが、もしipがnullの場合(画像が開いていない場合)はNullPointerExceptionが発生しうる。
実際にはsetupメソッドで画像の有無を確認していることから、もし画像が開いていない場合はrunメソッドまで処理が行くことはないが
したがって、kotlinで同じようにip.invert()とすると
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type ImageProcessor?
とIntelliJ IDEAに怒られるため、それを回避するようにコードを記述する必要がある。

if文による確認

一番わかりやすいのは、ipがnullかどうか確認することだ。これは単純に
        if(ip != null) {
            ip.invert()
        }
とすればよい。

安全呼出し

null許容型のオブジェクトのプロパティにアクセスする方法として、安全呼出し (safe calls)がある。これは、プロパティの呼出しに.ではなく、?.を使う
    ip?.invert()
もしipの中身がnullの場合、nullを返すがビルドはエラーとならない。kotlinのreferenceによると、この呼び出し方法は連鎖(chain)呼出しの際に有効であると述べられている。
null-safetyのSave Callsを参照
例えば、もしip1がnullだった場合、
ip1: ImageProcessor? = null
IJ.log("ip1 is " + ip1?.toString())
このコードは、ip1 is nullと返す。

!!演算子

もう一つの方法は、NullPointerExceptionが好きな人(NPE Lovers)用らしいです。null共用型のプロパティを呼び出す際に!!.を使います。
    ip!!.invert()
これはJavaの時と同じような動作をする。もしipがnulの場合、nullPointerExceptionを返す。

どれを使うべきか

状況に応じて、とするべきだろう。!!演算子は、javaと同じよう挙動を示すので、kotlinを使うメリットがあまり感じられない気がする。固いのは条件文でnullかどうかを検証する方法だが、コードが長くなる。でも確実。安全呼出しはchainで使うと有用だよと書かれているが、単発の呼出しは単にnullが来てもスルーする方法にしか思えない。このあたり理解不足か。

runメソッドの中身

こんな感じにしてみた。
    override fun run(ip: ImageProcessor?) {
        if(ip != null) {
            IJ.log("if-check")
            ip.invert()
            imp?.updateAndDraw()
        }
        IJ.wait(1000)

        IJ.log("safe call")
        ip?.invert()
        imp?.updateAndDraw()

        IJ.wait(1000)
        IJ.log("!!")
        ip!!.invert()
        imp?.updateAndDraw()
    }