Translate

ラベル ImageJ の投稿を表示しています。 すべての投稿を表示
ラベル ImageJ の投稿を表示しています。 すべての投稿を表示

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月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で呼び出せるようにしておいた方がよいと思う。

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月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-safetySave 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()
    }

2017年1月26日木曜日

kotlinでplugin3

KotlinでPlugin開発-3

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

PlugInFilterを実装したPluginはsetup(String str, ImagePlus imp)run(ImageProcessor ip)を持つのだった。setupメソッドで解析すべき画像を評価し、runメソッドで解析を行う。まずはsetupメソッドから実装する。

setup

Intelli J IDEAが自動的に作ってくれたコードは以下の通り。
 override fun setup(str: String?, imp: ImagePlus?): Int {
         throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
 }
Javaの場合は、
@Override
public int setup(String str, ImagePlus imp) {
// TODO Auto-generated method stub
return 0;
となる(Eclipseにつくってもらいました)。a0, a1などは、おなじみにのstr, impに変更した。二つのコードを比較するとなんとなく「ははーん」とみえてくる(気がしてくる)。
  1. overrideはJavaにもあった、@Overrideアノテーションみたいなものだが、Kotlinの場合はもっと実際的。kotlinの場合、スーパークラスやインターフェースのメソッドを書き換える場合は、必ずoverrideをつけないといけない。さらに言うと、overrideできるメソッドは、スーパークラスではopenという修飾子をつけて、明示的にoverride可能であることを示さないといけない。
  2. メソッドにはfunキーワードが必要。
  3. 引数の書き方は最近の言語っぽい気がする。str: String?でString型のオブジェクトとしてstrを宣言している。”?”については後で説明する
  4. 宣言の終わりに: Intとしていることから、このメソッドは整数型を返すことがわかる。

Null許容型

先ほどの引数のところで、imp: ImagePlus?との後ろに”?”がついていた。これがkotlinの特徴の一つ、Null安全を実現するための仕組みである。kotlinにはnull許容型非null許容型の二つの型宣言が存在する。null許容型にはnullを格納できるのに対し、非null許容型にはnullを格納することはできない。こうやってわざわざ分けておくことで、非null型にはnullが入らないことを保証し、null許容型はnullが入る可能性があるよ、ということをプログラマに教えてくれる。
そして、null許容型にアクセスするためには、null許容型で宣言されたオブジェクトにnullが本当に入っていないか確かめないといけない。その確認を怠るような書き方をすると、ビルドの段階ではじかれる。そのため、kotlinではnullpointerexceptionが起きる可能性をビルドの段階で除外することができる。
impには現在開いている画像のオブジェクトが渡されるが、もしかしたら画像は開いていないかもしれない。そうすると、impにはnullが渡されることになる。もし画像が開かれていない状態でメソッド内でimpにアクセスするようなコードを書くと、NullPointerExceptionが発生してしまう。そのため、このようにnullが入る可能性がある場合は”?”を型の後ろにつけることでnull許容型として宣言している。
あとでsetupメソッドの中でimpを使いたいので、ここでは次のように書く。
this.imp = imp 
return DOES_ALL
こうかくと、this.impのimpの箇所と、DOES_ALLの部分が赤色で表示される。それは当然impが宣言されていないからである(javaと違い、kotlinではプロパティと呼ぶらしい)。impのところを触ると、”Unresolve reference:imp”と怒られ、左に赤い電球が現れるのでそれをクリックする。すると、”Create member propety”と聞かれるので選択すると、プロパティが自動的に作成される。
private var  imp: ImagePlus?
すると、この行全体に波線が引かれ、ImagePlus?の部分は赤枠で囲われている。カーソルを持っていくと”Property must be initialized or be abstract”と出る。kotlinでは宣言されたオブジェクトは必ず初期化されなければいけない。ここでは、
private var  imp: ImagePlus? = null
と、nullで初期化?しとく。
また、DOES_ALLでも怒られるはずだ。これはクリックすると、”? ij.plugin.filter.PlugInFilter.DOES_ALL? Alt+Enter”と言われるので、言われたままにalt+enterを押すと、
import ij.plugin.filter.PlugInFilter.DOES_ALL
が自動的に書かれる。Javaと違い、プロパティ(Javaでいうメンバ変数)も明示するひつようがある。ワイルドカード”*”を使うことは可能。
次回はrunメソッドの実装を行う。

2017年1月25日水曜日

kotlinでplugin2

KotlinでImageJ Plugin作成-2

Kotlinクラスを作成する

前回までで環境設定が終わったので、Kotlinで早速Pluginの作っていく。画面としては、左にProjectが表示されているとする
  1. 作成したProjectを展開して、src\main\javaまで選ぶ
  2. javaを右クリックして[New -> Package]を選ぶ
  3. Package名を入力する
    • jp.yo4のような感じ
    • Packageは他の同名クラスと区別するためにも必ず設定する
  4. Package名で右クリックし、[New -> Kotlin Class/File]を選ぶ
  5. 名前はtest1などとする。KindはClassを選び、OK
    ここまででこんな感じになっているのではないだろうか?

PlugInFilterを実装する

Javaの場合は
public class Filter_Plugin implements PlugInFilter
としてインターフェースを実装していたが、Kotlinの場合、
class test: PlugInFilter {
とする。

  1. class testの後に: PlugInFilterと入力する。pom.xmlにimagejのdependencyを設定してあれば、途中まで入力すれば候補が現れる
    • 自動的にimport ij.plugin.filter.PlugInFilterも入力される。このあたりがIDEの便利なところ。
  2. class testの部分に赤で波線が表示されている。これは、この箇所に問題があることを示している。
  3. 波線をクリックすると、
Class ‘test’ must be declared abstract or implement abstract member
などと言われる。PlugInFilterは必ずrunメソッドとsetupメソッドを実装する必要があるためである。

  • さらに表示される赤い電球+ビックリマークをクリックする
  • Implement Membersを選ぶ。すると、ウィンドウが現れ、未実装のメソッド(ここではsetupとrun)が出てくるのでシフトキーを押しながら複数選択し、OKを押す
  • その結果、自動的にメソッドが挿入される。
  • class test: PlugInFilter {
        override fun setup(p0: String?, p1: ImagePlus?): Int {
                throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
        }
    
        override fun run(p0: ImageProcessor?) {
                throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
        }
    次はコードを記述していく。

    (2017/01/25 タイトルを少し変更)

    2017年1月24日火曜日

    kotlinでplugin

    KotlinでImageJ Plugin作成-1

    はじめに

    Kotlinという言語がある。Javaと互換性があり、Androidのアプリ開発で使われている。AppleのSwiftになんとなく似てる。Null安全というおもしろい仕組みがある。たとえばJavaで
    ImagePlus imp;
    Roi roi = imp.getRoi();
    とすると、ビルドは通るが実行時にNullPointerException(ヌルポ)が出て怒られる。kotlinで同じ個所を書くとこんな感じになる(と思う)
    var imp: ImagePlus? = null
    var roi: Roi? = null
    
    if (imp != null)
        roi = imp.Roi;
    
    //あるいは
    roi = imp?.Roi;
    なんだかJavaのコードに比べてコード数が倍以上に増えてしまっている。
    • Kotlinでは、nullが代入される可能性がある変数には、?をつけないといけない(1行目、2行目)。
    • nullを参照する際に、if文でnullじゃないことを確認しないとけない(4行目、5行目)
    • あるいは、安全呼び出しとして、.のかわりに?.を使う必要がある(7行目)
    こうすることで、ビルドの段階でnullをはじくことができるため、デバッグが容易になる。 kotlinでImageJのPluginを開発するのはなかなか良いのではないかと思い、作ってみる。

    準備

    (いつものように)形から入る。kotlinを扱う場合、IDEはEclipseではなくIntelliJ IDEAがいいらしい。そもそもKotlinはJet Brains社で開発された言語で、IntelliJ IDEAもJet Brains社が提供しているので相性が良い。EclipseにもPluginはあるそうだが、どうもそれほど使い勝手が良くないらしい(2017.1.23現在。自分では試していません。。。)。ということで、IntelliJ IDEAをインストールする。当然Community Edtion (Free)。

    Intelli Jの設定

    1. Intelli J IDEA
      • 真ん中にあるDownloadを選んで、CommunityをDownload
      • インストールはデフォルト設定で良いと思います。
    2. 初期設定(かなり適当)
      • 起動後、設定を引き継ぐかどうか聞かれるので適当に選択
      • プロジェクトの保存先(Eclipseでいうworkspace)を聞かれるので適当に作成する
      • Create New Projectを選ぶ
        • Check out from Version Controlを選べば、例えばGithubからリポジトリをCloneしてくることも可能(minimal-ij1-pluginとか)。今回は使わない。

    Kotlin + ImageJのプロジェクト作成

    1. Create New Projectを選ぶと、New Projectというウィンドウが出てきて、JavaとかKotolinとかのProjectを作成することができる。ここでは、あえてMavenを選ぶ(後でkotolinにします)
      • Kotlinを選んでからMaven可もできるはずだけどよくわからなかった。。
    2. いろんなarchetypeが出てくるが、ImageJのPlugin開発には関係ない(たぶん)なので、右下の[Next]を選ぶ
    3. MavenでおなじみのGroupid, Artifactid, Versionを入力する
      • Groupidは開発者が属する組織や、個人を識別するドメインを入れる。私の場合はjp.yo4など。ドメインの順は逆なのが通例
      • Artifactidは最終的なプロダクト(.jarなど)の名前になる。ImageJのPluginの場合、必ず名前のどこかにアンダーバー”_”を入れないといけない
      • Versionはお好みで。開発中のバージョンにはSNAPSHOTを入れておく。
    4. Project名と保存場所を指定して[Finish]
    5. そうすると、何もない殺風景な画面になり心配になるのでとりあえずProject Viewを開く。[View -> Tool Window -> Project]
    6. 画面左にProjectが表示される。

    ProjectのKotolin化

    このままではただのMavenのProjectなのでこのProjectをKotlin化する
    1. メニューから[Tools -> Kotlin -> Configure Kotlin in Project]
    2. Single moduleで今作ったProjectを選んで[OK]

    ImageJのdependencyを加える

    いつもはminimal-ij1-pluginをgithubからクローンして設定を書き換えて使っているが、ここでは新規にMavenのProjectを作ったので、手動でdependencyを加えてみる。
    1. pom.xmlを開く
      • すでに開いている場合はそのままでよい。開いていない場合は、Project Viewの自分のProjectを展開してpom.xmlを選ぶ
    2. どこでもいいので[alt + insert]を押す
    3. GenerateというContext menuが現れるので、Dependencyを選ぶ
    4. Maven Artifact Searchウィンドウが開く。ここからMaven Repositoryを選ぶことができる。
    5. 上のフォームに”imagej”と入力する
    6. たぶん3つの候補が得られる。最低限必要なのは真ん中のij:1.xx (xxはバージョン名)
      • imagej-maven-pluginをいれると、ImageJのPluginを開発するときに便利な機能(copy-jars)が使えるようになる。
      • pom-imagejはImageJの様々なライブラリであるBill of Materialsが使えるようになる
    7. とりあえず、真ん中のij:1.51gを入れとく
    以上でKotlin+ImageJ開発環境が整ったので、プラグインを開発していく。

    (2017/01/25 タイトルを少し変更)
    (2017/03/10 Maven Artifact Searchのウィンドウを追加)