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な解析範囲をする方法だ(と思う)。 ここまでたどり着くの相当疲れた。。