生物屋さんのためのゼロからのプログラミング

―忘れないための覚書 (たま~に更新)―

EclipseでImageJのPlugin作成 -拡散シミュレーション(ランダムウォーク)-

今回は粒子の拡散シミュレーションを行うImageJのPluginを書いてみた。

コードは下記。

import java.awt.BorderLayout;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import ij.IJ;
import ij.ImagePlus;
import ij.plugin.PlugIn;
import ij.plugin.ZProjector;
import ij.process.ImageProcessor;

public class Simulator_ implements PlugIn, ActionListener{

	ImagePlus imp, stackImp;
	ImageProcessor ip;
	int h, w;
	int sliceN = 500; //シミュレーションする時間 (フレーム数)
	int number = 1000; //粒子の数

	//シミュレーションするためのキャンバスの作成
	public void makeCanvas () {
		imp = IJ.openImage("何かしらの画像ファイルのディレクトリと名前");
		imp.setTitle("Simulation");
		w = imp.getWidth();
		h = imp.getHeight();
		imp.setRoi(0, 0, w, h);
		IJ.setBackgroundColor(0, 0, 0);
		IJ.run(imp, "Clear", "slice");
		imp.killRoi();

		//シミュレーションするためにstack画像にする
		for (int i = 0; i < sliceN; i++) {
			IJ.run(imp, "Add Slice", "");
		}
		ip = imp.getProcessor();
	}

	//拡散のシミュレーション
	public void simulate () {

		//粒子を作る
		int x[] = new int [number];
		int y [] = new int [number];

		//全ての粒子を中心に置く
		for (int i = 0; i < number; i++) {
			x[i] = w/2;
			y[i] = h/2;
		}
		imp.setSlice(1);
		ip.putPixelValue(w/2, h/2, 65535);

		//粒子の動きを描く
		for (int i = 2; i < sliceN; i++) {
			imp.setSlice(i);
			for (int j = 1; j < number; j++) {
				double th = 2.0*Math.PI*Math.random();
				x[j] = x[j] + (int)(Math.cos(th)*10);
				y[j] = y[j] + (int)(Math.sin(th)*10);
				ip.putPixelValue(x[j], y[j], 65535 - 10*j);
			}
		}

		//シミュレーション結果を見やすくする
		IJ.run(imp, "Fire", "");
		IJ.run("Brightness/Contrast...");
		IJ.setMinAndMax(imp, 55000, 65535);
		imp.show();
	}

	//シミュレーションのオーバーレイ画像を作る
	public void zStack () {
		stackImp = ZProjector.run(imp, "max");
		IJ.setMinAndMax(stackImp, 55000, 65535);
		stackImp.show();
	}

	//////シミュレーション結果を保存する////////
	//保存する名前を付ける
	String addName () {
		FileDialog fd = new FileDialog(new Frame(), "名前を付ける. 拡張子は付けないで下さい.", FileDialog.SAVE);
		fd.setVisible(true);
		String path = fd.getDirectory() + fd.getFile();
		fd.dispose();
		return path;
	}

	//保存する
	public void saveImage () {
		String name = addName();
		IJ.saveAs(imp, "TIFF", name);
		IJ.saveAs(stackImp, "TIFF", name + " stack");
	}

	//コントローラーの作成
	public void run (String arg) {
		JFrame frame = new JFrame ("Simulator");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setBounds(10, 10, 180, 120);
		JButton doneButton = new JButton ("Done");
		JButton saveButton = new JButton ("Save");
		doneButton.addActionListener(this);
		saveButton.addActionListener(this);
		doneButton.setActionCommand("Done");
		saveButton.setActionCommand("Save");
		doneButton.setBounds(40, 10, 90, 30);
		saveButton.setBounds(40, 50, 90, 30);

		JPanel pane = new JPanel ();
		pane.setLayout(null);
		pane.add(doneButton);
		pane.add(saveButton);
		frame.getContentPane().add(pane, BorderLayout.CENTER);
		frame.setVisible(true);
	}

	//ボタンを押した時の反応
	public void actionPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();
		if (cmd.equals("Done")) {
			makeCanvas ();
			simulate();
			zStack();
		} else if (cmd.equals("Save")) {
			saveImage();
		}
	}
}

このPluginを使うと、まず下のコントローラーが立ち上がる。
f:id:Aki-Miya:20180212160959p:plain
そして、Doneボタンを押すと、粒子の拡散シミュレーションを行い、
f:id:Aki-Miya:20180212161133p:plain
拡散シミュレーションの結果のstack画像と、それをZ-Projectionした下記の画像が作成される。
f:id:Aki-Miya:20180212161145p:plain
そしてSaveボタンを押せば、これらのシミュレーション結果を保存できる。

このシミュレーション方法はかなり簡便で、しかも実験データと同じ方法で解析できるので、かなり汎用性が高そうだ。



いくつかコードを見ていく。
今回は、拡散シミュレーションの結果を動画として取り扱うために、拡散シミュレーション結果をMuliti-tiffにすることにした。
そのため、まず下記のコードで、

	//シミュレーションするためのキャンバスの作成
	public void makeCanvas () {
		imp = IJ.openImage("何かしらの画像ファイルのディレクトリと名前");
		imp.setTitle("Simulation");
		w = imp.getWidth();
		h = imp.getHeight();
		imp.setRoi(0, 0, w, h);
		IJ.setBackgroundColor(0, 0, 0);
		IJ.run(imp, "Clear", "slice");
		imp.killRoi();

		//シミュレーションするためにstack画像にする
		for (int i = 0; i < sliceN; i++) {
			IJ.run(imp, "Add Slice", "");
		}
		ip = imp.getProcessor();
	}

適当な画像(ここでは512x512 pixelの16bit画像を使用)を開き、その画像の数値をクリアし、値を持たないstack画像を作成。このstack画像がシミュレーション結果を書き出すためのキャンバスになる。(stackの枚数がシミュレーション時間に相当)

そして、下記の部分で、

	//全ての粒子を中心に置く
	for (int i = 0; i < number; i++) {
		x[i] = w/2;
		y[i] = h/2;
	}
	imp.setSlice(1);
	ip.putPixelValue(w/2, h/2, 65535);

全ての粒子を中心に配置 (初期状態)。

そして、下記の部分で個々の粒子の位置を各sliceに記述することで、粒子の経時変化をシミュレーション出来るようにした。
(今回は輝度値も時間と共に減少するようにし、粒子位置の経時変化をより見やすくした)

//粒子の動きを描く
	for (int i = 2; i < sliceN; i++) {
		imp.setSlice(i);
		for (int j = 1; j < number; j++) {
			double th = 2.0*Math.PI*Math.random();
			x[j] = x[j] + (int)(Math.cos(th)*10);
			y[j] = y[j] + (int)(Math.sin(th)*10);
			ip.putPixelValue(x[j], y[j], 65535 - 10*j); //時間と共に粒子の輝度値が減少
		}
	}