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

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

EclipseでImageJのPlugin作成 -stack画像にROIを描き、その数値データをグラフ化する-

今回は、数値データをグラフ化するプラグインを書いた。

コードは下記。

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;

import ij.IJ;
import ij.ImagePlus;
import ij.io.OpenDialog;
import ij.measure.ResultsTable;
import ij.plugin.PlugIn;
import ij.plugin.frame.RoiManager;

public class Graph_Plot implements PlugIn, ActionListener {

	ImagePlus imp; //解析する画像
	Graphics2D g2;
	MyCanvas mycan;
	RoiManager rm;
	JFrame frame;
	JButton openButton;
	JButton doneButton;
	JLabel xLabel;
	JLabel yLabel;
	JLabel x; //グラフ上でのデータの位置の表記用
	JLabel val; //データの表記用
	int number; //スライスの枚数
	double time []; //時間の入れ物
	double data []; //データの入れ物
	double modiTime []; //補正後の時間の入れ物
	double modiData[]; //補正後のデータの入れ物

///////////////////////////////////////////////////
	//ファイルを開く
	public void openFile() {

		//画像の二重オープンを防ぐ
		openButton.setEnabled(false);

		//オープンダイアログで画像を開く
		OpenDialog od = new OpenDialog("Open");
		String path = od.getDirectory() + od.getFileName();
		imp = IJ.openImage(path);
		number = imp.getNSlices();
		imp.show();

		//Dataの入れ物の作成
		data = new double[number];
		time = new double [number];
		modiData = new double [number];
		modiTime = new double [number];

		//グラフを作れるようにする
		doneButton.setEnabled(true);
	}

///////////////////////////////////////////////////
	//数値の入手
	public void getValue() {

		//RoiManagerのリセット
		if (rm != null) rm.close();

		//数値データの取得
		rm = new RoiManager();
		IJ.run("Set Measurements...", "area mean redirect=None decimal=3");
		rm.addRoi(imp.getRoi());
		ResultsTable rt = rm.multiMeasure(imp);
		for (int i = 0; i < number; i++) {
			time [i] = i;
			data[i] = rt.getValue(1, i);
		}
		rt = null;
	}
///////////////////////////////////////////////////
	//グラフに合わせるためにデータを拡大・縮小する
	public void modify () {
		//Min & Maxの検出
		double min = data[0];
		double max = data[0];
		for (int i = 0; i < number; i++) {
			if (data[i] < min) min = data[i];
			if (data[i] > max) max = data[i];
		}
		double minValue = min;
		double maxValue = max;

		//グラフの拡大・縮小
		double zx = 400.0/number;
		double zy = 300.0/(maxValue - minValue);

		for (int i = 0; i < number ; i++) {
			modiTime[i] = zx*time[i];
			modiData[i] = 300 - ((data[i] - minValue)*zy);
		}
	}

///////////////////////////////////////////////////
	//グラフの作成
	public void makeLine() {
		JFrame drawFrame = new JFrame ("Draw");
		drawFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		drawFrame.setBounds(0, 0, 460, 360);
		mycan = new MyCanvas();
		drawFrame.getContentPane().add(mycan);
		mycan.addMouseListener (new MouseCheck());
		mycan.addMouseMotionListener(new MouseCheck());
		drawFrame.setVisible(true);
	}

///////////////////////////////////////////////////
	public class MyCanvas extends JComponent {
		public void paintComponent(Graphics g) {
			g2 = (Graphics2D)g;
			g2.draw(new Line2D.Double(50, 320, 460, 320));
			g2.draw(new Line2D.Double(50, 10, 50, 320));
			for (int i = 0; i < number - 1; i++) {
				g2.draw(new Line2D.Double(50 + modiTime[i], modiData[i] + 10, 50 + modiTime[i+1],  modiData[i+1] + 10));
			}
		}
	}
///////////////////////////////////////////////////
	//輝度値の表示
	class MouseCheck extends MouseInputAdapter {
		public void mouseMoved(MouseEvent e) {
			int x0 = e.getX();

			//JFrame上での座標をDataの位置になおす
			int xtrue = (x0 - 50) * number/400;

			//グラフ上でのみ数値を表記する
			if (x0 >= 50 &&  x0 < 400) {
				x.setText(String.valueOf(xtrue));
				val.setText(String.valueOf(data[xtrue]));
			} else {
				x.setText("");
				val.setText("");
			}
		}
	}

///////////////////////////////////////////////////
	//コントローラーの作成
	public void run (String arg) {
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		int sw = screenSize.width;
		frame = new JFrame("Graph");
		frame.setBounds(sw-200, 10, 160, 200);

		openButton = new JButton ("Open");
		doneButton = new JButton ("Done");
		xLabel = new JLabel ("x = ");
		x = new JLabel ("");
		yLabel = new JLabel ("val = ");
		val = new JLabel ("");
		openButton.addActionListener(this);
		doneButton.addActionListener(this);
		openButton.setActionCommand("Open");
		doneButton.setActionCommand("Done");
		openButton.setBounds(35, 10 , 90, 30);
		doneButton.setBounds(35, 50, 90, 30);
		xLabel.setBounds(35, 90, 50, 30);
		x.setBounds(60, 90, 60, 30);
		yLabel.setBounds(25, 130, 50, 30);
		val.setBounds(60, 130, 90, 30);

		JPanel pane = new JPanel();
		pane.setLayout(null);
		pane.add(openButton);
		pane.add(doneButton);
		pane.add(xLabel);
		pane.add(x);
		pane.add(val);
		pane.add(yLabel);
		doneButton.setEnabled(false);
		frame.getContentPane().add(pane, BorderLayout.CENTER);
		frame.setVisible(true);
	}

///////////////////////////////////////////////////
	//ボタンを押した時の反応
	public void actionPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();
		if (cmd.equals("Open")) {
			openFile ();
		} else if (cmd.equals("Done")) {
			getValue();
			modify();
			makeLine();
		}
	}
}

このプラグインを実行すると、下記のコントローラーが立ち上がり、
f:id:Aki-Miya:20180216084611p:plain
Openボタンで画像を開き(今回も)、下記のようにROIを置いて Doneボタンを押すと、
f:id:Aki-Miya:20180216084621p:plain
stack画像をMultiMeasureして、その結果を下記のようなグラフとして表示する。
f:id:Aki-Miya:20180216084635p:plain
このグラフ上をマウスでなぞると、コントローラーの下の部分に、データの数値が表示される。
f:id:Aki-Miya:20180216084657p:plain
(テストサンプルとして、stack画像には以前の記事EclipseでImageJのPlugin作成 -マウスでクリックした場所にOval ROIを描き、Excelファイルにデータを書き出す。- - 生物屋さんのためのゼロからのプログラミングと同様に、三浦耕太先生著のImageJではじめる生物画像解析 | 学研メディカル秀潤社の第5章−1 (p197)のサンプル画像"ER-flow.tif"を使用した。)


いくつかコードを見ていく。
PC上では左上が(0, 0)の座標となっているが、我々がグラフを描くときには左下を(0, 0)の座標とすることが多い。
そのため、下記の部分でデータをグラフ化するために補正を行っている。
(ここでの、"300"や"400"はグラフのサイズ)

//グラフに合わせるためにデータを拡大・縮小する
	public void modify () {
		//Min & Maxの検出
		double min = data[0];
		double max = data[0];
		for (int i = 0; i < number; i++) {
			if (data[i] < min) min = data[i];
			if (data[i] > max) max = data[i];
		}
		double minValue = min;
		double maxValue = max;

		//グラフの拡大・縮小
		double zx = 400.0/number;
		double zy = 300.0/(maxValue - minValue);

		for (int i = 0; i < number ; i++) {
			modiTime[i] = zx*time[i];
			modiData[i] = 300 - ((data[i] - minValue)*zy);
		}
	}

そして、拡大・縮小するためにDataの数値をDoubleに変更したため、Doubleのデータを使って線を描くために、下記の部分でGraphics2DのLine2D.Doubleを使用している。

public class MyCanvas extends JComponent {
	public void paintComponent(Graphics g) {
		g2 = (Graphics2D)g;
		g2.draw(new Line2D.Double(50, 320, 460, 320));
		g2.draw(new Line2D.Double(50, 10, 50, 320));
		for (int i = 0; i < number - 1; i++) {
			g2.draw(new Line2D.Double(50 + modiTime[i], modiData[i] + 10, 50 + modiTime[i+1],  modiData[i+1] + 10));
		}
	}
}

また、グラフ上での数値データを読み込むために、下記でMouseInputAdapterを使用している。

	//輝度値の表示
	class MouseCheck extends MouseInputAdapter {
		public void mouseMoved(MouseEvent e) {
			int x0 = e.getX();

			//JFrame上での座標をDataの位置になおす
			int xtrue = (x0 - 50) * number/400;

			//グラフ上でのみ数値を表記する
			if (x0 >= 50 &&  x0 < 400) {
				x.setText(String.valueOf(xtrue));
				val.setText(String.valueOf(data[xtrue]));
			} else {
				x.setText("");
				val.setText("");
			}
		}
	}


とりあえず、数値データをグラフ化できる方法が分かったので、他のプラグイン開発に応用できそう。