その漫画自炊オタクはImageJマクロに恋をする

プログラミングを用いた、自炊漫画の画像処理

【ImageJマクロ応用編】#1 子フォルダ内を含めた全画像を一括処理する方法

 

f:id:yu3xx:20210821060125p:plain



 

 

 おさらい



以前「フォルダ内全画像一括処理」について紹介しましたが、その方法だと「あるフォルダの下にある子フォルダの中にある画像ファイルまでは開くことが出来ませんでした。

 

今回は、自分で自分を呼び出す再帰的な関数」を使って、

 

「子フォルダ内まで全画像一括処理!」

 

する方法を紹介したいと思います。

 

さらに医用画像の規格であるDICOM(Digital Imaging and Communications in Medicineに対する全画像処理にも触れていきます。

 

 

 もくじ

 

 

例1. 子フォルダ内全画像処理の基本プログラム

 

//example 1

//process_subfolder.txt


//"??????" のところにいろいろ打ち込む




//フォルダ選択
showMessage("Select Processing Folder");
openDir = getDirectory("Choose a Directory");
showMessage("Select Save Folder");
saveDir = getDirectory("Choose a Directory");
list = getFileList(openDir);



//深掘り関数ListFilesを呼び出す

listFiles(openDir);



//-----------------------------------------------------------------------------
//深掘り関数ListFilesを定義

function listFiles(dir){
	
	list = getFileList(dir);
	for(i=0; i<list.length; i++){
		if(endsWith(list[i],"/")){
			listFiles(dir+list[i]);
		}else{
			open(dir+list[i]);
			operation();
		}
	}
	
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//関数operationを定義

function operation(){
	

	//ファイル名を作り直す
	name = getTitle();
	newname = name + ".jpg";	
	rename(newname);



	//ここに実際に行いたい処理を打ち込む

	//????????????????????????????????????????????



	run("Flip Horizontally");





	//????????????????????????????????????????????


	saveAs("JPEG", saveDir+newname);
	print("Save to...",saveDir+newname);

	close(newname);
}
//-----------------------------------------------------------------------------


 

 プログラムの流れは、

 ①「開きたいフォルダ」と「保存先フォルダ」を実際に選ぶ

 ②「開きたいフォルダ」の中を再帰関数ListFilesでチェック

 ③  ListFilesでチェックした対象が「フォルダ」ではなく「ファイル」だったら関数operationを呼び出す

 ④ 呼び出された関数operationで処理を実行する(「Flip Horizontally」で左右反転処理してjpeg保存)

 ⑤ また再帰関数ListFilesでチェックを続行する

  

再帰関数とは「自分で自分を呼び出す関数」のことです。

 

ListFilesの定義部分をよく見てみてください。

 

ListFilesのプログラム内容を日本語で翻訳すると、

 

list[i]、つまり「開きたいフォルダの中にあるもの」の名前が もし「/」 で終わっとるならそれは「フォルダ」やさかい、もっぺん関数ListFilesを呼んでそのフォルダの中身をチェックするねんで!

もし「/」 で終わってへんかったらそれは「ファイル」やさかい、関数operationを呼んでお好みの処理をするんやで!

 

って感じです。

 

「子フォルダまでの全画像処理」はこのListFilesがキモとなります。

 

実際に使うときは「????????」で囲んでいる部分をお好みで書き換えてください。

実行したい処理の書き方がわからない時は、上部タブ[Plugins]→[Macros]→[Record]カンニングすると速いです。

f:id:yu3xx:20210821071332p:plain

 

 

次に、いまのプログラム例を使って、医療画像の画像形式である「DICOM」に対して処理を行ってみます。

 

 

例2. DICOM画像に対する全画像処理

 

//example 2

//process_subfolder_TIFF.txt


//"??????" のところにいろいろ打ち込む




//フォルダ選択
showMessage("Select Processing Folder");
openDir = getDirectory("Choose a Directory");
showMessage("Select Save Folder");
saveDir = getDirectory("Choose a Directory");
list = getFileList(openDir);



//深掘り関数ListFilesを呼び出す

listFiles(openDir);



//-----------------------------------------------------------------------------
//深掘り関数ListFilesを定義

function listFiles(dir){
	
	list = getFileList(dir);
	for(i=0; i<list.length; i++){
		if(endsWith(list[i],"/")){
			listFiles(dir+list[i]);
		}else{
			open(dir+list[i]);
			operation();
		}
	}
	
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//関数operationを定義

function operation(){
	

	//ファイル名を作り直す
	name = getTitle();
	newname = name + ".tif";	
	rename(newname);


	//ここに実際に行いたい処理を打ち込む

	//????????????????????????????????????????????



	run("Flip Horizontally");





	//????????????????????????????????????????????


	saveAs("Tiff", saveDir+newname);
	print("Save to...",saveDir+newname);

	close(newname);
}
//-----------------------------------------------------------------------------


 

 

 書き換えた部分は

newname = name + ".tif";

saveAs("Tiff", saveDir+newname);

だけです。

 

ん?「DICOM」なのに「TIFF」?

ということで、ちょっと脱線します。

 

DICOM画像保存の予備知識

 

ここで補足なんですが、DICOMというのは医療で使われるデータの形式で、「画像だけじゃなく他の情報も一緒に詰め込んじゃえ!」という形式になります。

 

いちばん欲しい「画像データ」の他に、検査日だとか画素の大きさだとかの「おまけ情報」を一緒に持っています。この「おまけ情報」のことを「DICOMタグ」と呼びます。

 

このDICOMタグはImageJの上部タブ[Image]から[Show Info]で見ることが出来ます。

 

では、ImageJでDICOMを保存したい時はどうすればいいか?

 

画像データだけなら「PNG」とか「BMP」でもいいのかもしれませんが、これだと「DICOMタグ」の情報は失われてしまいます。せっかくなので「DICOMタグ」も保存したいですよね。

 

そこで選ぶのがTIFF形式です。[File]→[Save As]で[TIFF]を選択して、「.tif」で保存します。

 

TIFFだと、画像データは余計な処理を入れずにそのまま保存できますし、テキストデータのDICOMタグもそのまま保存できます。上部タブ[Image]→[Show Info]で見ることが出来ます。

 

「.dcm」の完全なDICOM形式でも保存出来るプラグインも世の中には存在するらしいですが、ImageJでの解析目的だったら「.tif」で困ることはあまり無いと思います。

 

 

戻って、例2の問題点

上記example2だと、「ファイル名が同じものが複数ある場合、保存先フォルダ内で同じファイル名で上書き保存されてしまう」という問題が起こります。

 

DICOM画像だと「シリーズ内のイメージ番号」だけで命名されていることもあるので、例えば複数の子フォルダをまたいで「00001.dcm」がいくつかある場合、全て「00001.tif」で上書き保存されてしまいます。

 

これを避けるために、「DICOMタグから情報を拾ってきて、ファイル名に利用する」という方法を使います。 

 

 

例3. DICOMタグから拾った情報をファイル名にする全画像処理

 

//example 3

//process_subfolder_TIFF_rename.txt



//JIBUNN DE IROIRO TSUKUTTE NE
//"??????" NO TOKORONI IROIRO KAITE NE




//Do something for selected folder
showMessage("Select Processing Folder");
openDir = getDirectory("Choose a Directory");
showMessage("Select Save Folder");
saveDir = getDirectory("Choose a Directory");
list = getFileList(openDir);



//operation

listFiles(openDir);



//-----------------------------------------------------------------------------
//Define listFiles

function listFiles(dir){
	
	list = getFileList(dir);
	for(i=0; i<list.length; i++){
		if(endsWith(list[i],"/")){
			listFiles(dir+list[i]);
		}else{
			open(dir+list[i]);
			operation();
		}
	}
	
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define operation

function operation(){
	

	//Name setting

	studyID = replace(getInfo("0020,0010")," ","");
	dateKensa = replace(getInfo("0008,0020")," ","");
	modality = replace(getInfo("0008,0060")," ","");

	series = replace(getInfo("0020,0011")," ","");
	series = zeroPad(parseInt(series),6);

	number = replace(getInfo("0020,0013")," ","");
	number = zeroPad(parseInt(number),6);

	newname = dateKensa + "_" + modality + "_" + "studyID" + studyID + "_" + series + "_" + number + ".tif";
	//newname = number + ".tif";
	rename(newname);




	//--------- KOKO NI IROIRO IRETE NE ----------

	//????????????????????????????????????????????



	run("Flip Horizontally");





	//????????????????????????????????????????????

	saveAs("Tiff", saveDir+newname);
	print("Save to...",saveDir+newname);

	close(newname);
}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Difine function zeroPadding

function zeroPad(int,digitZeroPad){
	if(int<0){
		exit("ZeroPadding Error!!  int<0");
	}
	stringInt=""+int;
	digitStringInt=lengthOf(stringInt);
	digitSubtra=digitZeroPad-digitStringInt;
	if(digitSubtra<0){
		exit("ZeroPadding Error!!  digitSubtra<0");
	}
	if(digitZeroPad>0){
		for(i=0;i<digitSubtra;i++){
			stringInt="0"+stringInt;
		}
	}
	return stringInt;
}	

//-----------------------------------------------------------------------------



 

 書き換えた部分は「Name setting」と「関数zeroPad」の部分です。

 

かんたんにいうと、

「getInfo」で、「欲しい番地のタグ情報」を拾ってくる。(「0008,0020」は検査日)

「replace」で「半角スペース」を消去。

「parseInt」で「文字列」を「整数」にしたものを、 「zeroPad」で6桁に0埋め(ゼロパディング)。

 

これらの情報を合体させてファイル名にして、拡張子「.tif」として保存しています。

 

この方法なら、同一施設からの画像である限り、ファイル名が重複することは無いんじゃないかなと思います。

 

「画像を開かずともファイル名だけで中身が予想できる!」のもおすすめポイントです。よければ使ってみてください!

 

 

 

 

 

おしまい 

 

 

 

 

・公開したマクロのまとめ

imagej-jisui.hatenablog.com