その漫画自炊オタクは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

 

 

 

 

 

【上級者向け】寝てる間に自動解析してコントラスト調整やリサイズ処理などなどするMacro

 

f:id:yu3xx:20210627093020p:plain


  

最初から最後まで全く手を触れずに、コントラスト濃度変更処理などを実行することができるマクロです。

 

 

もくじ

 

 

コード

//PreInput Almighty.ijm
//based Almighty ver3.3.0
//ContrastAdjust (As necessary, for 8bit, FullColor)
//Optimize minAndMax (As necessary, Manual 3trials)
//Optimize minAndMaxAuto (As necessary)
//SetResolution (As necessary, for any Type)
//NombreCut type1 (As necessary, UseOneROI)
//NombreCut type2 (As necessary, CaseDivided by ODD/EVEN, [number==0 -> error])
//Renumbering from1 (As necessary, Files must be in subDirectory)
//Renumbering ATMT (As necessary, Automater, Only one trueTitle, trueTitle=pureTitle+kansuu) 
//MakeDirAuto (FileName : trueTitle_pagenumber.ext)
//output txtFile (optMinMaxAuto)

//opDir no hitotsu sita no kaisou no directories ni SOREZORE "Almighty" wo kakeru


version = "4.1.0";

//1.2.0 -> hosei2
//1.3.0 -> enter cFiles
//2.0.0 -> Forced to proceed even if files are not in the subDirectory
//2.2.0 -> targetMeanBlack = 13 (optMinAndMaxAuto)
//3.0.0 -> pageCount
//3,1,0 -> pageCount%mabiki == 0
//3.2.0 -> xStep = 3*xSeg
//3.3.0 -> Sharpen
//3.4.0 -> }else if(procCount < cFiles){ ..... listFiles2
//3.5.0 -> run("Input/Output...", "jpeg=quality gif=-1 file=.csv copy_column copy_row save_column save_row");
//4.0.0 -> flagExtactRedChannel
//4.1.0 -> resize org.jpg (optMinMaxAuto)


print("");
print("Pre Input");
print("Almighty Processing");
print("ver",version);
print("makeDirAuto");
print("listFilesRecursively");

print("");
print("Subfolder Goto ni optMinMaxAuto");
print("Full Auto");
print("");

startTime = whatTimeNow();



//Select opDir
opDir=getDirectory("choose a directory");
listOp = getFileList(opDir);
print("opDir :",opDir);











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

//Select Processing (enter 0 or 1)
flagContr = 1;
flagReso = 1;
flagNombre = 0;
flagSharpen = 1;
flagOptMinMaxAuto = 1;
flagOptMinMax = 0; 
flagExtactRedChannel = 0;
flagRenumFrom1 = 0;
flagRenumATMT = 0;


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

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

//Enter Parameter
min = 0;
max =255;
flagFullColor = 0;
preDpi = 300;
postDpi = 400;
digit = 4;
output = "JPEG";

cFiles = 11;

hosei = -10;	//adjust Max
hosei2 = 0;	//adjust min, default=5 (targetMeanBlack=27), 

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











//Define variable
flagFullColor = 0;
var flagExtactRedChannel;
flagSave = 1;
flagOpenDir = 0;
var totalFiles = 0;
var procCount = 0;

var min;
var max;
var digit;
var compressionRate;
var ext;
var optimizedMin;
var optimizedMax;
var targetMeanWhite;
var targetMeanBlack;
var sumMin;
var sumMax;
var mabiki;
var OPT_MAX;  
var OPT_MIN;
var cFiles;
var aCountMin;
var aCountMax;
var f;
var name;
var pageCount;

if(flagSave == 1){
	saveDir = getDirectory("downloads");
	print("Save to :",saveDir);
	selectWindow("Log");
}
wait(1000);

//make parentDirectory
timeStamp = getTimeStamp();
parentDir = saveDir+"postProc_"+timeStamp+"/";
File.makeDirectory(parentDir);


//output txt (optMinMaxAuto)
if(flagOptMinMaxAuto == 1) {
	outputTxt = saveDir + "postProc_"+timeStamp+".txt";
	 f = File.open(outputTxt);
}


//Loop START!!
for(m=0; m<listOp.length; m++){
	openDir=opDir+listOp[m];
	if(!endsWith(listOp[m],"/")) {
		showMessageWithCancel("Found files that are NOT in the subDirectory. Check opDir. Do you wanna continue?");
		openDir = opDir;
		m = listOp.length + 1;//break after this loop
	}
	print("openDir:", openDir);
	opeLoop();
}


//fin

finishTime = whatTimeNow();
print("Start Time .... ",startTime);
print("FinishTime ... ",finishTime);
print("oshimai");
beep();




//-----------------------------------------------------------------------------
//Define opeLoop

function opeLoop(){
totalFiles = 0;
procCount = 0;

//print flag
print("");
if(flagContr == 1) print("flagContr = ON");
if(flagReso == 1) print("flagReso = ON");
if(flagNombre == 1) print("flagNombre = ON");
if(flagSharpen == 1) print("flagSharpen = ON");
if(flagOptMinMax == 1) print("flagOptMinMax = ON");
if(flagOptMinMaxAuto == 1) print("flagOptMinMaxAuto = ON");
if(flagExtactRedChannel == 1) print("flagExtactRedChannel = ON");
if(flagRenumFrom1 == 1) print("flagRenumFrom1 = ON");
if(flagRenumATMT == 1) print("flagRenumATMT = ON");

//check flag
print("");
if(flagRenumFrom1 == 1 && flagRenumATMT == 1) exit("Two \"Renumbering Processing\" were selected.");
if(flagOptMinMax == 1 && flagOptMinMaxAuto == 1) exit("Two \"OptMinMax Processing\" were selected.");
if(flagFullColor == 1 && flagExtactRedChannel == 1) exit("flagFullColor and flagExtactRedChannel can't be used together");
if(flagOptMinMax == 1 || flagOptMinMaxAuto == 1){
	if(flagContr == 0) print("Only optMinMax processing");
}
if(flagContr == 0 && flagReso == 0 && flagNombre == 0){
	if(flagRenumFrom1 == 1 || flagRenumATMT == 1) print("Only Renumbering processing");
}
if(flagOptMinMax == 1 || flagOptMinMaxAuto == 1){
	if(flagContr == 0 && flagReso == 0 && flagNombre == 0 && flagRenumFrom1 == 0 && flagRenumATMT == 0) {
		flagSave = 0;
		print("flagSave = OFF");
	}
}
if(flagContr == 1 || flagReso == 1 || flagNombre == 1 || flagRenumFrom1 == 1 || flagRenumATMT == 1 || flagOptMinMaxAuto == 1){
	flagOpenDir = 1;
}else{
	print("flagOpenDir = OFF");
}


//Do something for selected folder
print("");
if(flagOpenDir == 1){
	
	print("Processing :",openDir);
	list = getFileList(openDir);
	//count totalFiles
	countFiles(openDir);
	selectWindow("Log");
}


//Select Nombre Type
if(flagNombre == 1){
	Dialog.create("Nombre Cut setting");
	items = newArray("Use One ROI", "Case Divided by ODD/EVEN");
	Dialog.addRadioButtonGroup("Select NombreCut Type", items, 2, 1, "Use One ROI");
	Dialog.show;
	nombreRadio = Dialog.getRadioButton();
	if(nombreRadio == "Use One ROI") NombreType = 1;
	if(nombreRadio== "Case Divided by ODD/EVEN") NombreType = 2;
}


//Clear ROI Manager and set ROI
if(flagNombre == 1){
	roiCount = roiManager("count");
	if(roiCount > 0){
		roiManager("Deselect");
		roiManager("Delete");
	}
	if(NombreType == 1){
		//ROI setting
		waitForUser("ROI setting","Open an image and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();
	}
	if(NombreType == 2){
		//ROI setting ODD(1,3,5,7,...)
		waitForUser("ODD(1,3,5,7,...)","Open an [ODD number] file and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();

		//ROI setting EVEN(2,4,6,8,...)
		waitForUser("EVEN(2,4,6,8,...)","Open an [EVEN number] file and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();
	}
}


//Enter parameter
if(flagSave == 1){

	//print parameter
	print("");
	if(flagFullColor == 1) print("flagFullColor = ON");
	if(flagOptMinMax == 0 && flagOptMinMaxAuto == 0){
		if (flagContr == 1) print("min =",min,", Max =",max);
	}
	if (flagReso == 1) print("pre dpi =",preDpi,", Target dpi =",postDpi);

	//Extension
	if(output == "JPEG") ext = ".jpg";
	if(output == "PNG") ext = ".png";

	//JPEG,PNG quality setting(jpeg=90,gif=-1)
	quality = 90;
	run("Input/Output...", "jpeg=quality gif=-1 file=.csv copy_column copy_row save_column save_row");
	if (output == "JPEG") print("JPEG quality =",quality);
	print("");
}

//SetResolution Setting
if(flagReso == 1) compressionRate = postDpi/preDpi;


//Reference setting
var referLength;
if(flagRenumATMT == 1){
	referenceCheck(openDir);
}









//optimizeMinAndMax

if(flagOptMinMax == 1) {
	sumMin = 0;
	sumMax = 0;

	trials = 3;
	OPT_MAX = newArray(trials);
	OPT_MIN = newArray(trials);
	for(now=0; now<trials; now++){
		optMinMax(now,trials);
		sumMin = sumMin + OPT_MIN[now];
		sumMax = sumMax + OPT_MAX[now];
	}
	min = floor(sumMin/trials);
	max = floor(sumMax/trials);
	print("");
	wait(1000);

	for(i=0; i<trials; i++){
		print(i+1,"...","min = ",OPT_MIN[i],", max = ",OPT_MAX[i]);
	}
	print("");
	beep();
	wait(1000);

	print("Result...","optMin =",min,", optMax =",max);
	wait(3000);
}

if(flagOptMinMaxAuto == 1) {
	//Enter parameter
	
	targetMeanWhite = 254.97;
	targetMeanBlack = 13;
	print("targetMeanWhite =",targetMeanWhite );
	print("targetMeanBlack =",targetMeanBlack );
	//mabiki = 60;

	//Do something for selected folder
	tempDir = saveDir + "Temp_optMinMax/";
	print("Temp dir :",tempDir);
	selectWindow("Log");
	wait(1000);

	//make tempDir
	if(!File.exists(tempDir)){
		File.makeDirectory(tempDir);
	}
	
	//Create Array
	//cFiles = floor(totalFiles/mabiki);
	
	cFiles = floor(cFiles);
	mabiki = floor(totalFiles/cFiles);
	if(mabiki < 1) exit("Error! Mottto cFiles Sukunaku.");
	print("cFiles =",cFiles,", mabiki =",mabiki);	

	OPT_MAX = newArray(cFiles);
	OPT_MIN = newArray(cFiles);

	for(i=0; i<cFiles; i++){
		OPT_MAX[i] = 0;
	}
	for(i=0; i<cFiles; i++){
		OPT_MIN[i] = 255;
	}
	
	procCount = 0;
	setBatchMode(true);
	pageCount = 0;
	listFiles2(openDir);
	setBatchMode(false);

	optMinMaxResults();
	min = optimizedMin;
	max = optimizedMax;
	
	print("");
	wait(1000);

	print("acceptCount...min : ",aCountMin,", max : ",aCountMax);
	beep();
	wait(1000);
	
	print("Result...","optMin =",min,", optMax =",max);
	wait(3000);
}

//exit (only optMinMax)
if(flagSave == 0){
	exit("oshimai");
}



//Renum from1
if (flagRenumFrom1 == 1){
	Table.create("Correspondence Table");
}


//Main Operation
print("Now Operation...");
setBatchMode(true);
procCount = 0;
listFiles(openDir);


//clear ROI Manager
if(flagNombre == 1){
	roiManager("Deselect");
	roiManager("Delete");
}


//fin
if (flagRenumFrom1 == 1){
	Table.update;
}

print("");
if(flagOptMinMax == 1){
	for(i=0; i<trials; i++){
		print(i+1,"...","min =",OPT_MIN[i],", max =",OPT_MAX[i]);
	}
}
if(flagOptMinMaxAuto == 1) print("acceptCount...min : ",aCountMin,", max : ",aCountMax);
if(flagOptMinMax == 1 || flagOptMinMaxAuto == 1){
	print("Result...","optMin =",min,", optMax =",max);
	print("");
}
if (flagContr == 1) {
	print("Contrast Adjust by...");
	print("min =",min,", max =",max);
}
if (flagReso == 1) print("Compression Rate =",compressionRate);
if(flagSharpen == 1) print("Sharpen");
if (output == "JPEG") print("JPEG quality =",quality);




}//Fn_opeLoop End


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


//-----------------------------------------------------------------------------
//Define referenceCheck

function referenceCheck(dir){
	list = getFileList(dir);
	if(endsWith(list[0],"/")){
		referenceCheck(dir+list[0]);
	}else{
		name = list[0];
		dotIndex = lastIndexOf(name,".");
		title = substring(name,0,dotIndex);
		space = lastIndexOf(title," ");
		trueTitle = substring(title,0,space);
		number = substring(title,space+1);

		reference = trueTitle;
		referLength = lengthOf(reference);
		selectWindow("Log");
		print("Check trueTitle. (e.g. NARUTO_60)");
		print("trueTitle=[",trueTitle,"]");
		showMessageWithCancel("Check trueTitle (except : number.ext), and click [OK].");
	}
}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Define countFiles

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


//-----------------------------------------------------------------------------
//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 listFiles2

//caution!! For optMinMaxAuto

function listFiles2(dir){
	list = getFileList(dir);
	for(i=0; i<list.length; i++){
		if(endsWith(list[i],"/")){
			listFiles2(dir+list[i]);
		}else if(procCount < cFiles){
			pageCount++;
			if(pageCount % mabiki == 0){
				open(dir+list[i]);
				optMinMaxAuto();
			}
		}
	}
	
}
//-----------------------------------------------------------------------------


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

function operation(){
	

	procCount++;
	
	//Progress.the second decimal place
	//print(procCount,"/",totalFiles,"...Progress=",d2s(procCount/totalFiles*100,2),"%");
	
	//Rename Section
	if(flagRenumATMT == 1){
		name = getTitle();
		dotIndex = lastIndexOf(name,".");
		title = substring(name,0,dotIndex);

		//ReNumbering
		titleLength = lengthOf(title);

		if(referLength < titleLength){
			space = lastIndexOf(title," ");
			trueTitle = substring(title,0,space);
			number = substring(title,space+1);

			newname = trueTitle+"_"+zeroPad(number,digit)+ext;
		
		}else if(referLength == titleLength){

			newname = title+"_"+zeroPad(1,digit)+ext;

			dotIndex = lastIndexOf(newname,".");
			title = substring(newname,0,dotIndex);
			underBar = lastIndexOf(title,"_");
			trueTitle = substring(title,0,underBar);
			number = substring(title,underBar+1);
		}	
	}else if(flagRenumATMT == 0){
		name = getTitle();
		dotIndex = indexOf(name, ".");
		title = substring(name,0,dotIndex);
		underBar = lastIndexOf(title,"_");
		trueTitle = substring(title,0,underBar);
		number = substring(title,underBar+1);
		newname = title+ext;	
		if (flagRenumFrom1 == 1) newname = trueTitle+"_"+zeroPad(i+1,digit)+ext;
	}
	rename(newname);

	//Output Correspondence table
	if (flagRenumFrom1 == 1){
		Table.set("index",procCount-1,procCount);
		Table.set("Original",procCount-1,name);
		Table.set("Newname",procCount-1,newname);
	}

	//Nombre Cut
	if(flagNombre == 1){
		if(NombreType == 1){
			roiManager("Select", 0);
			run("Crop");
		}
		if(NombreType == 2){
			number = parseInt(number);
			if(number == 0) exit("Error!! Number must not be null.");
			//ODD(1,3,5,7,...)
			if(number%2 == 1){
				roiManager("Select", 0);
				run("Crop");
			}
			//EVEN(2,4,6,8,...)
			if(number%2 == 0){
				roiManager("Select", 1);
				run("Crop");
			}
		}
	}

	//SetResolution
	if (flagReso == 1){
		width = getWidth;
		height = getHeight;
		targetW = floor(width*compressionRate);
		run("Size...", "width=targetW constrain average interpolation=Bicubic");
	}

	//Filtering
	if(flagSharpen == 1){
		run("Sharpen");
		// ---equiv--- //run("Convolve...", "text1=[-1 -1 -1\n-1 12 -1\n-1 -1 -1\n] normalize"); 
	}

	//ContrastAdjust 8 bit
	depth = bitDepth();
	if(flagFullColor == 0) {
		if (depth == 8) {
			//ContrastAdjust (As necessary)
			if (flagContr == 1) setMinAndMax(min, max);
			if (flagContr == 1) run("Apply LUT");
		}
	}
	if(flagFullColor == 1) {
		//ContrastAdjust (As necessary)
		if (flagContr == 1) setMinAndMax(min, max);
		if (flagContr == 1) run("Apply LUT");
	}
	
	if(flagExtactRedChannel == 1) {
		//extract red channel to suppress yellowing
		run("RGB Stack");
		setSlice(1);	//red stack
		//ContrastAdjust
		if (flagContr == 1) setMinAndMax(min, max);
		if (flagContr == 1) run("Apply LUT", "stack");
	}

	

	//Save to SubDirectory
	subDir = parentDir+trueTitle+"/";
	if(!File.exists(subDir)){
		File.makeDirectory(subDir);
	}

	if (flagContr == 0 && flagReso == 0 && flagNombre == 0) {
		if(flagRenumFrom1 == 1 || flagRenumATMT == 1){
			File.copy(dir+name,subDir+newname);
			//print("Copy to...",subDir+newname);
		}
	}else{		
		saveAs(output, subDir+newname);
		//print("Save to...",subDir+newname);
	}
	close(newname);
}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Define optMinMax

function optMinMax(now,trials){

	print("");
	print("OptimizeMinAndMax");
	setBatchMode(false);

	if(nImages == 0) waitForUser("OptMinAndMax.  Open an image, then click [OK]"); 

	//Enter parameter
	if(now == 0){
		Dialog.create("OptMinAndMax Setting");
		Dialog.addNumber("targetMeanWhite",254.97);
		Dialog.addNumber("targetMeanBlack",13); //default->18,27
		Dialog.show;
		targetMeanWhite = Dialog.getNumber();
		targetMeanBlack = Dialog.getNumber();
	}

	if(flagExtactRedChannel == 1) {
		run("RGB Stack");
		setSlice(1);	//red stack
		updateDisplay();
	}

	name = getTitle();
	tempDir = saveDir + "Temp_optMinMax/";
	//make tempDir
	if(!File.exists(tempDir)){
		File.makeDirectory(tempDir);
	}
	saveAs("Jpeg", tempDir+"CheckMaxAndMin.jpg");


	//optimize Max
	setTool("rectangle");
	run("Specify...", "width=100 height=100 x=10 y=10");
	waitForUser("ROI selection","Set ROI on the \"WHITE BACK GROUND\", then click [OK].");
	getStatistics(area,mean);
	setBatchMode(true);

	if(mean < 150){
		exit("Error!! This area is not WHITE BACK GROUND.");
	}
	run("Crop");

	saveAs("Jpeg", tempDir+"CheckMax.jpg");
	close();

	max = 254;
	min = 0;
	print("optimized Max Macro, TargetMean =",targetMeanWhite);

	for (i=0; i<254; i++){
		open(tempDir+"CheckMax.jpg");
		setMinAndMax(min, max);
		run("Apply LUT");
		getStatistics(area,mean);
		print("Max=",max," mean=",mean);
		if(mean >= targetMeanWhite){
			optimizedMax = max;
			i = 255; //break
		}
		max = max-1;
		close();
	}
	print("OptimizedMax =",optimizedMax);
	setBatchMode(false);

	//optimize min
	open(tempDir+"CheckMaxAndMin.jpg");
	setTool("rectangle");
	run("Specify...", "width=100 height=100 x=10 y=10");
	waitForUser("ROI selection","Set ROI on the \"BLACK ZONE\", then click [OK].");
	getStatistics(area,mean);
	setBatchMode(true);

	if(mean > 100){
		exit("Error!! This area is not BLACK ZONE.");
	}
	run("Crop");

	saveAs("Jpeg", tempDir+"CheckMin.jpg");
	close();

	max = optimizedMax;
	min = 1;
	print("");
	print("OptimizedMin Macro, TargetMean=",targetMeanBlack);

	for (i=0; i<254; i++){
		open(tempDir+"CheckMin.jpg");
		setMinAndMax(min, max);
		run("Apply LUT");
		getStatistics(area,mean);
		print("min=",min," mean=",mean);
		if(mean <= targetMeanBlack){
			optimizedMin = min;
			i=255; //break
		}
		min = min+1;
		close();
	}
	
	//Results
	OPT_MIN[now] = optimizedMin;
	OPT_MAX[now] = optimizedMax;
	
	print("");
	print(now+1,"/",trials);
	print("Title =",name);
	print("OptimizedMin =",optimizedMin);
	print("OptimizedMax =",optimizedMax);
	setBatchMode(false);
} 

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


//-----------------------------------------------------------------------------
//Define optMinMaxAuto

function optMinMaxAuto(){

	name = getTitle();

	//Progress
	print("");
	print(procCount+1, "/", cFiles, "...Progress=",d2s(procCount/cFiles*100,2),"%" , " [OptMinMaxAuto]");
	print("pageCount = ", pageCount);
	print("...",name);

	if(flagExtactRedChannel == 1) {
		run("RGB Stack");
		setSlice(1);	//red stack
		updateDisplay();
	}

	depth = bitDepth();
	if (depth == 8) {
		run("Size...", "width=400 depth=1 constrain average interpolation=Bilinear");
		saveAs("Jpeg", tempDir+"org.jpg");
		
		width = getWidth();
		height = getHeight();

		x = 0;
		y = 0;

		sum = 0;
		whiteCheckCount = 0;
		blackCheckCount = 0;

		whiteSegCount = 0;
		blackSegCount = 0;

		whiteMax = 0;
		blackMin = 255;

		//whiteBackGround
		segCount = 0;
		xSeg = 10;
		ySeg = 10;
		xStep = xSeg;
		yStep = ySeg;

		for(x=0; x<width-xSeg; x=x+xStep){
			for(xi=0; xi<xSeg; xi++){
				for(yi=0; yi<ySeg; yi++){
					pixelValue = getPixel(x+xi,y+yi);
					sum = sum+pixelValue;
				}
			}
			mean = sum/xSeg/ySeg;
			if(mean > whiteMax) {
				whiteMax = mean;
				whiteSegCount = segCount;
				whiteCheckCount++;
				//print("ImgNo.",procCount+1, ", white-",whiteCheckCount);
				
			}
			makeRectangle(x, y, xSeg, ySeg);
			run("Crop");
			saveTitle = "wSegNo"+zeroPad(segCount,6);
			saveAs("Jpeg", tempDir+saveTitle+".jpg");

			close();
			open(tempDir+"org.jpg");


			sum = 0;
			segCount++;
		}

		//blackKuroBeta
		segCount = 0;
		xSeg = 8;
		ySeg = 8;
		xStep = 2*xSeg;
		yStep = 2*ySeg;

		for(y=0; y<height-ySeg; y=y+yStep){
			for(x=0; x<width-xSeg; x=x+xStep){
				for(xi=0; xi<xSeg; xi++){
					for(yi=0; yi<ySeg; yi++){
						pixelValue = getPixel(x+xi,y+yi);
						sum = sum+pixelValue;
					}
				}
				mean = sum/xSeg/ySeg;
			
				if(mean < blackMin){
					blackMin = mean;
					blackSegCount = segCount;
					blackCheckCount++;
					//print("ImgNo.",procCount+1, ", black-",blackCheckCount);
				}
				makeRectangle(x, y, xSeg, ySeg);
				run("Crop");
				saveTitle = "bSegNo"+zeroPad(segCount,6);
				saveAs("Jpeg", tempDir+saveTitle+".jpg");
	
				close();
				open(tempDir+"org.jpg");

	
				sum = 0;
				segCount++;
			}
		}
		close("org.jpg"); //originalImage
				
		//checkMax Loop
		
		optimizedMax = 0; //If not found
		max = 254;
		min = 0;
	
		print("Checking Max..." , "wSegNo" + zeroPad(whiteSegCount,6) + ".jpg");

		for (i=0; i<254; i++){
			open(tempDir+"wSegNo"+zeroPad(whiteSegCount,6)+".jpg");
			setMinAndMax(min, max);
			run("Apply LUT");
			getStatistics(area,mean);
			if(mean >= targetMeanWhite){
				optimizedMax = max;
				i = 255; //break
			}
			max = max-1;

			//	print(min,max,mean);
			//	print("Checking Max..." , "wSegNo" + zeroPad(whiteSegCount,6) + ".jpg");
			
			close();
		}
		print("Temp_OptimizedMax =",optimizedMax);
		OPT_MAX[procCount] = optimizedMax;
	
		//checkMin Loop

		optimizedMin = 255; //If not found
		max = 220; //Daitai 200 Gurai Ooi kara
		min = 1;
		
		print("Checking Min..." , "bSegNo" + zeroPad(blackSegCount,6) + ".jpg");
	
		for (i=0; i<254; i++){
			open(tempDir+"bSegNo"+zeroPad(blackSegCount,6)+".jpg");
		
			setMinAndMax(min, max);
			run("Apply LUT");
			getStatistics(area,mean);
			if(mean <= targetMeanBlack){
				optimizedMin = min;
				i=255; //break
			}
			min = min+1;

			//	print(min,max,mean);
			//	print("Checking Min..." , "bSegNo" + zeroPad(blackSegCount,6) + ".jpg");
			
			if(min == max){
				i=255; //break
			}

			close();
		}
		print("Temp_OptimizedMin =",optimizedMin);
		OPT_MIN[procCount] = optimizedMin;	

	}else{ //else if(depth != 8bit)
		close(name); //original Image
	}	

	procCount++; //and back to listFiles2
}

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


//-----------------------------------------------------------------------------
//Define optMinMaxResults 

function optMinMaxResults(){

	//	//Plot Graphs
	//	i=linspace(1,cFiles,cFiles);
	//	Plot.create("Max","No.","Max",i,OPT_MAX);
	//	Plot.show();
	//	Plot.create("Min","No.","Min",i,OPT_MIN);
	//	Plot.show();
	
	//Max
	aCountMax=0;
	//Outlier Exclusion
	for(i=0; i<cFiles; i++){
		if(OPT_MAX[i] > 150){
			aCountMax++;
		}
	}
	if(aCountMax == 0) exit("Error! Mabiki motto sukunaku.");
	OPT_MAX = bubbleSort(OPT_MAX);
	OPT_MAX = Array.slice(OPT_MAX,cFiles-aCountMax,cFiles);
	
	median = floor(aCountMax/2);
	if(aCountMax % 2 == 0) median = median-1;
	optimizedMax = OPT_MAX[median] + hosei;

	//Min
	aCountMin=0;
	for(i=0; i<cFiles; i++){
		if(OPT_MIN[i] < 100){
			aCountMin++;
		}
	}
	if(aCountMin == 0) exit("Error! Mabiki motto sukunaku.");
	OPT_MIN = bubbleSort(OPT_MIN);
	OPT_MIN = Array.slice(OPT_MIN,0,aCountMin);

	median = floor(aCountMin/2);
	if(aCountMin % 2 == 0) median = median-1;
	optimizedMin = OPT_MIN[median] + hosei2;

	//Results
	print("");
	print("OPT_MIN = ");
	Array.print(OPT_MIN);
	print("OPT_MAX = ");
	Array.print(OPT_MAX);
	print("OptimizedMin =",optimizedMin);
	print("OptimizedMax =",optimizedMax);

	//output txt
	print(f, name + "\t" + "optMin = " + optimizedMin + "\t" + "optMax = " + optimizedMax + "\t" + "acceptCount (min, max) = " + aCountMin + ", " + aCountMax);

}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Define function getTimeStamp

function getTimeStamp(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	timeStamp = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	timeStamp = strYear+strMonth+strDayOfMonth+"_"+strHour+"h"+strMinute+"m"+strSecond+"s";
	return timeStamp;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define function whatTimeNow

function whatTimeNow(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	stringTime = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	stringTime = strYear+"/"+strMonth+"/"+strDayOfMonth+"_"+strHour+":"+strMinute+":"+strSecond;
	return stringTime;
}

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

//-----------------------------------------------------------------------------
//Define 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;
}	

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


//-----------------------------------------------------------------------------
//Define function linspace (generate Arithmetric Sequence)
//a->1st term, an->last term, n->term number

function linspace(a,an,n){
	A = newArray(n);
	d = (an-a)/(n-1);

	for(i=0;i<n;i++){
		A[i] = a+i*d;
	}	

	return A;
}

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


//-----------------------------------------------------------------------------
//Define function bubble_sort

function bubbleSort(A){
	n=A.length;
	for(i=0;i<n-1;i++){
		for(j=n-1;j>i;j--){
			if(A[j]<A[j-1]){
				tmp=A[j];
				A[j]=A[j-1];
				A[j-1]=tmp;
			}
		}
	}
	return A;
}

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

 

 

ポイント

 

基本的には多目的自炊マクロ「Almighty Processing ver.3」のアレンジ版です。

 

今までと違うのは

① 起動後のダイアログを極力排除して、起動したら終わるまで何もしなくてOK!

 

② 保存先フォルダをPC内の「ダウンロード先フォルダ」に設定したので、いちいち選ばなくてOK!

 

③ 選択した処理フォルダの1つ下の階層のフォルダごとに「Almighty Processing」をかけるイメージなので、コントラスト濃度変更の「Min, Max」が独立別個!

 

④ 解析結果の最適Min Max値をtxtファイルにして書き出し!

 

f:id:yu3xx:20210627094007p:plain

 

プログラムコード内の値を直接いじることで処理内容を決定しているので、ちょっと上級者むけですが、

寝てる間にドカっとまとめて処理したい!

って時におすすめです。

 

 

基本的に、optMinMaxAutoで自動解析した後に、Contrasut Adjustmentでコントラスト・濃度を変更する用に作っています。

なので他の処理を噛ませた時のデバッグはしていないのでご注意を。

 

 

使い分け

 

① 1タイトルだけが対象で、自分の目でカクニンしながら確実に最適なMin, Maxでコントラスト濃度変更したい時

→「Almighty Processing ver.3」で「Optimize MinAndMax (Manual)」

【真・改良版】コントラスト調整やリサイズ処理などなどを、一気にやってしまうためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

② 複数タイトルが対象で、1タイトルずつMin, Maxを調べるのがメンドウなのでまとめて一気に自動処理させたい時

→本記事の「Pre Input Almighty」で「Optimize MinAndMax (Auto)」

 

 

デフォルトの処理内容

 

0がオフで、1がオンです。このあたりをいじって処理内容を変更してください。

 

 //Select Processing (enter 0 or 1)

 flagContr = 1;

 flagReso = 0;

 flagNombre = 0;

 flagOptMinMaxAuto = 1;

 flagOptMinMax = 0;

 flagRenumFrom1 = 1;

 flagRenumATMT = 0;

 

各パラメータも基本的にダイアログではなく、あらかじめ打ち込んでおくタイプなので、コード内を探して変更してください。

(解像度変更の設定dpiとか)

 

保存先フォルダは、とりあえずどんなPCでも動作する(はず)なので「ダウンロード先フォルダ」にしています。

自分の好きな場所に変更すると、より使いやすいと思います。 

 

 

各機能の解説

 

各処理の細かな解説は、以下のページを参考にしてみてください。

 

 ・Contrast Adjustment

コントラスト・濃度を調整するMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Optimize Min And Max [Manual] 

コントラスト調整に必要なMax値とMin値を自動で求めるためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Change the Resolution

解像度を変更するMacro - その漫画自炊オタクはImageJマクロに恋をする

  

 ・Nombre Cut

実用版!ノンブル領域を削除し, コントラスト調整, 解像度変更も一括で行えるMacro - その漫画自炊オタクはImageJマクロに恋をする

 

・Renumbering [from 1] 

【多目的】コントラスト調整やリサイズ処理などなどを、一気にやってしまうためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Renumbering [Automator]

【Mac】実用版!PDFからJPEGに変換した画像のコントラスト調整+フォルダ収納のためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 

 

 

 

 

 

ライセンスなんかは一切無いので、ぜひぜひ自由に使ってみてください!

  

imagej-jisui.hatenablog.com

 

 

 

 

 

【寄り道】UNIXTIMEを普通の日付に変換するためのプログラム

 

 

ファイルの更新日を調べる命令であるFile.lastModified(path)を実行すると、1970年1月1日午前0時0分0秒から数えた時間である「UNIXTIME(単位はmsec)」が出力されます。

 

このままだと「1621927895482」のように、ケタがとんでもなく多い数字を一つ渡されるだけなので上手く使えません。

 

これを我々が理解できる「普通の日付」に変換するプログラムを紹介します。

 

 

 

ネットで調べたものを参考に、というよりしっかりパクってImageJマクロ用にアレンジしました。ありがとうございました!

 

コード

//unixtime(msec) to tokyotime(yyyy/mm/dd hh:mm:ss)

var year, month, day, hour, minute, second;

tokyotime = ConvertUnixTimeToTokyoTime(1621927895482);

print(tokyotime);


//-----------------------------------------------------------------------------
//Define function ConvertUnixTimeToTokyoTime

function ConvertUnixTimeToTokyoTime(unixtime) {
   
	year = 0;
	leap = 0;
	GMT_TOKYO = 9*60*60;
	SECONDS_IN_A_DAY = 24*60*60;
	UNIX_EPOCH_DAY = 1969*365 + floor(1969/4) - floor(1969/100) + floor(1969/400) + 306; //  days from 0000/03/01 to 1970/01/01
	YEAR_ONE = 365;
	YEAR_FOUR = YEAR_ONE * 4 + 1; 
	YEAR_100 = YEAR_FOUR * 25 - 1;
	YEAR_400 = YEAR_100*4 + 1;

	monthday = newArray (0,31,61,92,122,153,184,214,245,275,306,337);

	unixtime = unixtime / 1000; //msec to sec

	unixtime = unixtime + GMT_TOKYO;
	second = floor(unixtime % 60);
	minute = floor(unixtime / 60) % 60;
	hour = floor(unixtime / 3600) % 24;

	unixday = floor(unixtime / SECONDS_IN_A_DAY);
	unixday = unixday + UNIX_EPOCH_DAY;

	year = year + 400 * floor(unixday / YEAR_400);
	unixday = unixday % YEAR_400 ;

	n = floor(unixday / YEAR_100);
	year = year + n * 100;
	unixday = unixday % YEAR_100 ;

	if (n == 4){
		leap = 1;
	}else{
		year = year + 4 * floor(unixday / YEAR_FOUR);
		unixday = unixday % YEAR_FOUR ;
        
		n = floor(unixday / YEAR_ONE);
		year = year + n;
 		unixday = unixday % YEAR_ONE ;

		if (n == 4) {
			leap = 1;
		}
	}
	if (leap != 0) {
		month = 2;
		day = 29;
	}else{
		month = floor(((unixday + 0.4) * 5) / 153);
		day = unixday - monthday[month] + 1;
		month = month + 3;
		if (month > 12) {
			year++;
			month = month - 12;
		}
	}
	tokyotime = "" + year + "/" + zeroPad(month,2) + "/" + zeroPad(day,2) + "  " + zeroPad(hour,2) + ":" +zeroPad(minute,2) + ":" + zeroPad(second,2);
	return tokyotime;
}

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


//-----------------------------------------------------------------------------
//Define 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;
}	

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


 

実行すると、unixtime「1621927895482 (msec)」は、「2021/05/25 16:31:35」であると出力されるはずです。

 

よく理解できていないけど大事なポイント

 

① 時差を修正

グリニッジ標準時間と日本の時差が9時間なので、9×60×60秒を足す。

 

② 西暦0年3月1日からの日数に変換

→3月1日というのがミソ。最終日が平年なら2月28日、うるう年なら2月29日になる。最後の一日をカウントするのかしないのかを考えるだけで良いので、プログラムを組むのがラクになるらしい。

 

③ うるう年に注意して年数をカウント

→うるう年のルールは

 1.  西暦の年号が4で割り切れるなら「うるう年」

 2.  でも、100で割り切れる年は「うるう年ではない」

 3. それなのに、400で割り切れる年はやっぱり「うるう年」

このへんてこで複雑なルールを取り込んだうえで年数を正しくカウントするために、YEAR_400とかYEAR_ONEとかを使って場合分けしているみたい。

YEAR_FOURは4年間の日数。4年ごとにうるう年が1日来るからプラス1されて365×4+1日。YEAR_100は、100年に一度うるう年から除外する日が来るからYEAR_FOUR×25回分-1日。YEAR_400は、400年に一度除外ルールを無視してうるう年が来るのでYEAR_100×4回分+1日。

これらがunixday(単位は日数)の中に何回入るのかで、年数を計算してるっぽい。

 

④ 先人の知恵で月を算出

→謎アルゴリズムです。

 month = floor(((unixday + 0.4) * 5) / 153); のあたり。

なぜこんなことに気付けるのでしょうか。考えた方の素晴らしさに敬意を払いつつ、そのまま使いましょう。

 

⑤ 日の算出

→3月1日からある月の初めまでの日数を配列monthdayにあらかじめテーブル化しておく。このテーブルと上で算出した「月」を使って、「unixday - その月の初めまでの日数」を計算することで、その月の何日なのかが計算されるらしい。

 

⑥ 仕上げ

→3月スタートだからmonthに+3する。13月、14月が計算で出てくるから-12して1月、2月に直す。終了。

 

実際に使ってみた

 

自炊した本を一覧化するマクロに「ファイル最終更新日」表示機能を盛り込んでみた。


 

 

おしまい

 

 

 

Thanks to

UNIXTIME (ciao.jp)

 

 

【真・改良版】コントラスト調整やリサイズ処理などなどを、一気にやってしまうためのMacro

 

f:id:yu3xx:20210529173457p:plain


  

以前紹介したお役立ち多目的マクロにくっつけた「コントラスト自動調整マクロoptimizeMinAndMax」を完全自動型にアップデートしました。

 

旧バージョン

 

もくじ

 

 

コード

//Almighty_Processing.txt
//ContrastAdjust (As necessary, for 8bit, FullColor)
//Optimize minAndMax (As necessary, Manual 3trials)
//Optimize minAndMaxAuto (As necessary)
//SetResolution (As necessary, for any Type)
//NombreCut type1 (As necessary, UseOneROI)
//NombreCut type2 (As necessary, CaseDivided by ODD/EVEN, [number==0 -> error])
//Sharpen Filltering (As necessary)
//Renumbering from1 (As necessary, Files must be in subDirectory)
//Renumbering ATMT (As necessary, Automater, Only one trueTitle, trueTitle=pureTitle+kansuu) 
//MakeDirAuto (FileName : trueTitle_pagenumber.ext)

version = "4.0.2";

//3.7.0 -> sharpen
//3.8.0 -> procTag
//4.0.0 -> flagExtactRedChannel

print("");
print("Almighty Processing");
print("ver",version);
print("makeDirAuto,listFilesRecursively");

startTime = whatTimeNow();


//Define variable
flagFullColor = 0;
var flagExtactRedChannel = 0;
flagSave = 1;
flagOpenDir = 0;

var totalFiles = 0;
var procCount = 0;
var min;
var max;
var digit;
var compressionRate;
var ext;
var trueTitle;

//Select Processing
Dialog.create("Processing setting");
Dialog.addMessage("Select Processing");
Dialog.addMessage("");
Dialog.addMessage("- Main -");
Dialog.addCheckbox("Contrast Adjustment", false);
Dialog.addCheckbox("Change the Resolution",false);
Dialog.addCheckbox("Nombre Cut",false);
Dialog.addCheckbox("Sharpen",false);
Dialog.addMessage("");
Dialog.addMessage("- Optimize minAndMax -");
Dialog.addMessage("");
Dialog.addCheckbox("Optimize minAndMax[Auto]",false);
Dialog.addCheckbox("Optimize minAndMax[Manual]",false);
Dialog.addMessage("");
Dialog.addMessage("- Renumbering -");
Dialog.addMessage("");
Dialog.addCheckbox("Renumbering [from 1]",false);
Dialog.addCheckbox("Renumbering [Automator]",false);
Dialog.show;
flagContr = Dialog.getCheckbox();
flagReso = Dialog.getCheckbox();
flagNombre = Dialog.getCheckbox();
flagSharpen = Dialog.getCheckbox();
flagOptMinMaxAuto = Dialog.getCheckbox();
flagOptMinMax = Dialog.getCheckbox(); //Manual
flagRenumFrom1 = Dialog.getCheckbox();
flagRenumATMT = Dialog.getCheckbox();

//print flag and set procTag
procTag = "";
if(flagContr == 1) {
	print("flagContr = ON");
	procTag = procTag + "Contr";
}
if(flagReso == 1) {
	print("flagReso = ON");
	procTag = procTag + "Reso";
}
if(flagNombre == 1) {
	print("flagNombre = ON");
	procTag = procTag + "Nombre";
}
if(flagSharpen == 1){
	 print("flagSharpen = ON");
	procTag = procTag + "Sharpen";
}
if(flagOptMinMax == 1) print("flagOptMinMax = ON");
if(flagOptMinMaxAuto == 1) print("flagOptMinMaxAuto = ON");
if(flagRenumFrom1 == 1) {
	print("flagRenumFrom1 = ON");
	procTag = procTag + "From1";
}
if(flagRenumATMT == 1){
	print("flagRenumATMT = ON");
	procTag = procTag + "ATMT";
}

//check flag
if(flagRenumFrom1 == 1 && flagRenumATMT == 1) exit("Two \"Renumbering Processing\" were selected.");
if(flagOptMinMax == 1 && flagOptMinMaxAuto == 1) exit("Two \"OptMinMax Processing\" were selected.");
if(flagOptMinMax == 1 || flagOptMinMaxAuto == 1){
	if(flagContr == 0) showMessageWithCancel("Only optMinMax processing");
}
if(flagContr == 0 && flagReso == 0 && flagNombre == 0 && flagSharpen == 0){
	if(flagRenumFrom1 == 1 || flagRenumATMT == 1) showMessageWithCancel("Only Renumbering processing");
}
if(flagOptMinMax == 1 || flagOptMinMaxAuto == 1){
	if(flagContr == 0 && flagReso == 0 && flagNombre == 0 && flagSharpen == 0 && flagRenumFrom1 == 0 && flagRenumATMT == 0) {
		flagSave = 0;
		print("flagSave = OFF");
	}
}
if(flagContr == 1 || flagReso == 1 || flagNombre == 1 || flagSharpen == 1|| flagRenumFrom1 == 1 || flagRenumATMT == 1 || flagOptMinMaxAuto == 1){
	flagOpenDir = 1;
}else{
	print("flagOpenDir = OFF");
}


//Do something for selected folder
if(flagOpenDir == 1){
	showMessage("Select Processing Folder");
	openDir = getDirectory("Choose a Directory");
	print("Processing :",openDir);
	list = getFileList(openDir);
	//count totalFiles
	countFiles(openDir);
	selectWindow("Log");
}
if(flagSave == 1){
	showMessage("Select Save Folder");
	saveDir = getDirectory("Choose a Directory");
	print("Save to :",saveDir);
	selectWindow("Log");
}
wait(1000);


//Select Nombre Type
if(flagNombre == 1){
	Dialog.create("Nombre Cut setting");
	items = newArray("Use One ROI", "Case Divided by ODD/EVEN");
	Dialog.addRadioButtonGroup("Select NombreCut Type", items, 2, 1, "Use One ROI");
	Dialog.show;
	nombreRadio = Dialog.getRadioButton();
	if(nombreRadio == "Use One ROI") NombreType = 1;
	if(nombreRadio== "Case Divided by ODD/EVEN") NombreType = 2;
}


//Clear ROI Manager and set ROI
if(flagNombre == 1){
	roiCount = roiManager("count");
	if(roiCount > 0){
		roiManager("Deselect");
		roiManager("Delete");
	}
	if(NombreType == 1){
		//ROI setting
		waitForUser("ROI setting","Open an image and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();
	}
	if(NombreType == 2){
		//ROI setting ODD(1,3,5,7,...)
		waitForUser("ODD(1,3,5,7,...)","Open an [ODD number] file and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();

		//ROI setting EVEN(2,4,6,8,...)
		waitForUser("EVEN(2,4,6,8,...)","Open an [EVEN number] file and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();
	}
}


//Enter parameter
if(flagSave == 1){
	Dialog.create("Processing Setting");
	if(flagOptMinMax == 0 && flagOptMinMaxAuto == 0){
		if(flagContr == 1 ) Dialog.addNumber("min (0-255)",0);
		if(flagContr == 1) Dialog.addNumber("Max (0-255)",255);
		if(flagContr == 1) Dialog.addCheckbox("Full Color", false);
		if(flagContr == 1) Dialog.addCheckbox("Extract Red Channel", false);
		if(flagContr == 1) Dialog.addMessage("");
	}
	if(flagReso == 1) Dialog.addNumber("pre dpi",600);
	if(flagReso == 1) Dialog.addNumber("post dpi(Target)",400);
	if(flagRenumATMT == 1 || flagRenumFrom1 == 1) Dialog.addNumber("Digit of Page Number",4);
	items = newArray("JPEG", "PNG");
	Dialog.addRadioButtonGroup("Output format", items, 1, 2, "JPEG");
	Dialog.show;
	if(flagOptMinMax == 0 && flagOptMinMaxAuto == 0){
		if(flagContr == 1) min = Dialog.getNumber();
		if(flagContr == 1) max = Dialog.getNumber();
	}
	if(flagContr == 1) flagFullColor = Dialog.getCheckbox();
	if(flagContr == 1) flagExtactRedChannel= Dialog.getCheckbox();
	if(flagReso == 1) preDpi = Dialog.getNumber();
	if(flagReso == 1) postDpi = Dialog.getNumber();
	if(flagRenumATMT == 1 || flagRenumFrom1 == 1) digit = Dialog.getNumber();
	output = Dialog.getRadioButton();

	//check flag
	if(flagFullColor == 1 && flagExtactRedChannel == 1) exit("flagFullColor and flagExtactRedChannel can't be used together");

	//print parameter
	if(flagFullColor == 1) print("flagFullColor = ON");
	if(flagExtactRedChannel == 1) print("flagExtactRedChannel = ON");
	if(flagOptMinMax == 0 && flagOptMinMaxAuto == 0){
		if (flagContr == 1) print("min =",min,", Max =",max);
	}
	if (flagReso == 1) print("pre dpi =",preDpi,", Target dpi =",postDpi);

	//Extension
	if(output == "JPEG") ext = ".jpg";
	if(output == "PNG") ext = ".png";

	//JPEG,PNG quality setting(jpeg=90,gif=-1)
	quality = 90;
	run("Input/Output...", "jpeg=quality gif=-1 file=.csv copy_column copy_row save_column save_row");

	if (output == "JPEG") print("JPEG quality =",quality);
}

//SetResolution Setting
if(flagReso == 1) compressionRate = postDpi/preDpi;


//Reference setting
var referLength;
if(flagRenumATMT == 1){
	referenceCheck(openDir);
}


//optimizeMinAndMax
var optimizedMin;
var optimizedMax;
var targetMeanWhite;
var targetMeanBlack;
var sumMin;
var sumMax;
var mabiki;
var OPT_MAX;  
var OPT_MIN;
var cFiles;
var aCountMin;
var aCountMax;

if(flagOptMinMax == 1) {
	sumMin = 0;
	sumMax = 0;

	trials = 3;
	OPT_MAX = newArray(trials);
	OPT_MIN = newArray(trials);
	for(now=0; now<trials; now++){
		optMinMax(now,trials);
		sumMin = sumMin + OPT_MIN[now];
		sumMax = sumMax + OPT_MAX[now];
	}
	min = floor(sumMin/trials);
	max = floor(sumMax/trials);
	print("");
	wait(1000);

	for(i=0; i<trials; i++){
		print(i+1,"...","min = ",OPT_MIN[i],", max = ",OPT_MAX[i]);
	}
	print("");
	beep();
	wait(1000);

	print("Result...","optMin =",min,", optMax =",max);
	wait(3000);
}

if(flagOptMinMaxAuto == 1) {
	//Enter parameter
	Dialog.create("Processing Setting");
	Dialog.addNumber("targetMeanWhite",254.97);
	Dialog.addNumber("targetMeanBlack",13);//default->18,27
	Dialog.addNumber("mabiki",19);
	Dialog.addCheckbox("Extract Red Channel", false);
	Dialog.show;
	targetMeanWhite = Dialog.getNumber();
	targetMeanBlack = Dialog.getNumber();
	mabiki = Dialog.getNumber();
	flagExtactRedChannel= Dialog.getCheckbox();
	if(flagExtactRedChannel == 1) print("flagExtactRedChannel = ON");

	//Do something for selected folder
	tempDir = saveDir;
	print("Temp dir :",tempDir);
	selectWindow("Log");
	wait(1000);

	//make tempDir
	if(!File.exists(tempDir)){
		File.makeDirectory(tempDir);
	}
	
	//Create Array
	cFiles = floor(totalFiles/mabiki);
	OPT_MAX = newArray(cFiles);
	OPT_MIN = newArray(cFiles);

	for(i=0; i<cFiles; i++){
		OPT_MAX[i] = 0;
	}
	for(i=0; i<cFiles; i++){
		OPT_MIN[i] = 255;
	}

	setBatchMode(true);
	listFiles2(openDir);
	setBatchMode(false);

	optMinMaxResults();
	min = optimizedMin;
	max = optimizedMax;
	
	print("");
	wait(1000);

	print("acceptCount...min : ",aCountMin,", max : ",aCountMax);
	beep();
	wait(1000);
	
	print("Result...","optMin =",min,", optMax =",max);
	wait(3000);
}

//exit (only optMinMax)
if(flagSave == 0){
	exit("oshimai");
}

//make parentDirectory
parentDir = saveDir+"postProc_"+getTimeStamp()+"_"+procTag+"/";
File.makeDirectory(parentDir);


//Renum from1
if (flagRenumFrom1 == 1){
	Table.create("Correspondence Table");
}


//Main Operation
setBatchMode(true);
procCount = 0;
listFiles(openDir);


//clear ROI Manager
if(flagNombre == 1){
	roiManager("Deselect");
	roiManager("Delete");
}


//fin
if (flagRenumFrom1 == 1){
	Table.update;
}
finishTime = whatTimeNow();
print("");
print(trueTitle);
if(flagOptMinMax == 1){
	for(i=0; i<trials; i++){
		print(i+1,"...","min =",OPT_MIN[i],", max =",OPT_MAX[i]);
	}
}
if(flagOptMinMaxAuto == 1) print("acceptCount...min : ",aCountMin,", max : ",aCountMax);
if(flagOptMinMax == 1 || flagOptMinMaxAuto == 1){
	print("Result...","optMin =",min,", optMax =",max);
	print("");
}
if (flagContr == 1) {
	print("Contrast Adjusted by...");
	print("min =",min,", max =",max);
}
if (flagReso == 1) print("Compression Rate =",compressionRate);
if(flagSharpen == 1) print("Sharpen");
if (output == "JPEG") print("JPEG quality =",quality);
print("Start Time .... ",startTime);
print("FinishTime ... ",finishTime);
print("oshimai");
beep();


//-----------------------------------------------------------------------------
//Define referenceCheck

function referenceCheck(dir){
	list = getFileList(dir);
	if(endsWith(list[0],"/")){
		referenceCheck(dir+list[0]);
	}else{
		name = list[0];
		dotIndex = lastIndexOf(name,".");
		title = substring(name,0,dotIndex);
		space = lastIndexOf(title," ");
		trueTitle = substring(title,0,space);
		number = substring(title,space+1);

		reference = trueTitle;
		referLength = lengthOf(reference);
		selectWindow("Log");
		print("Check trueTitle. (e.g. NARUTO_60)");
		print("trueTitle=[",trueTitle,"]");
		showMessageWithCancel("Check trueTitle (except : number.ext), and click [OK].");
	}
}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Define countFiles

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


//-----------------------------------------------------------------------------
//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 listFiles2

//caution!! For optMinMaxAuto

function listFiles2(dir){
	list = getFileList(dir);
	for(i=0; i<list.length; i++){
		if(endsWith(list[i],"/")){
			listFiles2(dir+list[i]);
		}else if(i % mabiki == mabiki-1){
			open(dir+list[i]);
			optMinMaxAuto();
		}
	}
	
}
//-----------------------------------------------------------------------------


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

function operation(){
	
	procCount++;
	
	//Progress.the second decimal place
	print(procCount,"/",totalFiles,"...Progress=",d2s(procCount/totalFiles*100,2),"%");
	
	//Rename Section
	if(flagRenumATMT == 1){
		name = getTitle();
		dotIndex = lastIndexOf(name,".");
		title = substring(name,0,dotIndex);

		//ReNumbering
		titleLength = lengthOf(title);

		if(referLength < titleLength){
			space = lastIndexOf(title," ");
			trueTitle = substring(title,0,space);
			number = substring(title,space+1);

			newname = trueTitle+"_"+zeroPad(number,digit)+ext;
		
		}else if(referLength == titleLength){

			newname = title+"_"+zeroPad(1,digit)+ext;

			dotIndex = lastIndexOf(newname,".");
			title = substring(newname,0,dotIndex);
			underBar = lastIndexOf(title,"_");
			trueTitle = substring(title,0,underBar);
			number = substring(title,underBar+1);
		}	
	}else if(flagRenumATMT == 0){
		name = getTitle();
		dotIndex = indexOf(name, ".");
		title = substring(name,0,dotIndex);
		underBar = lastIndexOf(title,"_");
		trueTitle = substring(title,0,underBar);
		number = substring(title,underBar+1);
		newname = title+ext;	
		if (flagRenumFrom1 == 1) newname = trueTitle+"_"+zeroPad(i+1,digit)+ext;
	}
	rename(newname);

	//Output Correspondence table
	if (flagRenumFrom1 == 1){
		Table.set("index",procCount-1,procCount);
		Table.set("Original",procCount-1,name);
		Table.set("Newname",procCount-1,newname);
	}

	//Nombre Cut
	if(flagNombre == 1){
		if(NombreType == 1){
			roiManager("Select", 0);
			run("Crop");
		}
		if(NombreType == 2){
			number = parseInt(number);
			if(number == 0) exit("Error!! Number must not be null.");
			//ODD(1,3,5,7,...)
			if(number%2 == 1){
				roiManager("Select", 0);
				run("Crop");
			}
			//EVEN(2,4,6,8,...)
			if(number%2 == 0){
				roiManager("Select", 1);
				run("Crop");
			}
		}
	}

	//SetResolution
	if (flagReso == 1){
		width = getWidth;
		height = getHeight;
		targetW = floor(width*compressionRate);
		run("Size...", "width=targetW constrain average interpolation=Bicubic");
	}
	
	//Filtering
	if(flagSharpen == 1){
		run("Sharpen");
		// ---equiv--- //run("Convolve...", "text1=[-1 -1 -1\n-1 12 -1\n-1 -1 -1\n] normalize"); 
	}

	//ContrastAdjust 8 bit
	depth = bitDepth();
	if(flagFullColor == 0 && flagExtactRedChannel == 0) {
		if (depth == 8) {
			//ContrastAdjust
			if (flagContr == 1) setMinAndMax(min, max);
			if (flagContr == 1) run("Apply LUT");
		}
	}
	if(flagFullColor == 1) {
		//ContrastAdjust
		if (flagContr == 1) setMinAndMax(min, max);
		if (flagContr == 1) run("Apply LUT");
	}

	if(flagExtactRedChannel == 1) {
		//extract red channel to suppress yellowing
		run("RGB Stack");
		setSlice(1);	//red stack
		//ContrastAdjust
		if (flagContr == 1) setMinAndMax(min, max);
		if (flagContr == 1) run("Apply LUT", "stack");
	}

	//Save to SubDirectory
	subDir = parentDir+trueTitle+"/";
	if(!File.exists(subDir)){
		File.makeDirectory(subDir);
	}

	if (flagContr == 0 && flagReso == 0 && flagNombre == 0 && flagSharpen == 0) {
		if(flagRenumFrom1 == 1 || flagRenumATMT == 1){
			File.copy(dir+name,subDir+newname);
			print("Copy to...",subDir+newname);
		}
	}else{		
		saveAs(output, subDir+newname);
		print("Save to...",subDir+newname);
	}
	close(newname);
}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Define optMinMax

function optMinMax(now,trials){

	print("");
	print("OptimizeMinAndMax");
	setBatchMode(false);

	if(nImages == 0) waitForUser("OptMinAndMax.  Open an image, then click [OK]"); 

	//Enter parameter
	if(now == 0){
		Dialog.create("OptMinAndMax Setting");
		Dialog.addNumber("targetMeanWhite",254.97);
		Dialog.addNumber("targetMeanBlack",13); //default->18
		Dialog.addCheckbox("Extract Red Channel", false);
		Dialog.show;
		targetMeanWhite = Dialog.getNumber();
		targetMeanBlack = Dialog.getNumber();
		flagExtactRedChannel= Dialog.getCheckbox();
		if(flagExtactRedChannel == 1) print("flagExtactRedChannel = ON");
	}
	

	if(flagExtactRedChannel == 1) {
		run("RGB Stack");
		setSlice(1);	//red stack
		updateDisplay();
	}

	name = getTitle();
	tempDir = saveDir;
	//make tempDir
	if(!File.exists(tempDir)){
		File.makeDirectory(tempDir);
	}
	saveAs("Jpeg", tempDir+"CheckMaxAndMin.jpg");


	//optimize Max
	setTool("rectangle");
	run("Specify...", "width=100 height=100 x=10 y=10");
	waitForUser("ROI selection","Set ROI on the \"WHITE BACK GROUND\", then click [OK].");
	getStatistics(area,mean);
	setBatchMode(true);

	if(mean < 150){
		exit("Error!! This area is not WHITE BACK GROUND.");
	}
	run("Crop");

	saveAs("Jpeg", tempDir+"CheckMax.jpg");
	close();

	max = 254;
	min = 0;
	print("optimized Max Macro, TargetMean =",targetMeanWhite);

	for (i=0; i<254; i++){
		open(tempDir+"CheckMax.jpg");
		setMinAndMax(min, max);
		run("Apply LUT");
		getStatistics(area,mean);
		print("Max=",max," mean=",mean);
		if(mean >= targetMeanWhite){
			optimizedMax = max;
			i = 255; //break
		}
		max = max-1;
		close();
	}
	print("OptimizedMax =",optimizedMax);
	setBatchMode(false);

	//optimize min
	open(tempDir+"CheckMaxAndMin.jpg");
	setTool("rectangle");
	run("Specify...", "width=100 height=100 x=10 y=10");
	waitForUser("ROI selection","Set ROI on the \"BLACK ZONE\", then click [OK].");
	getStatistics(area,mean);
	setBatchMode(true);

	if(mean > 100){
		exit("Error!! This area is not BLACK ZONE.");
	}
	run("Crop");

	saveAs("Jpeg", tempDir+"CheckMin.jpg");
	close();

	max = optimizedMax;
	min = 1;
	print("");
	print("OptimizedMin Macro, TargetMean=",targetMeanBlack);

	for (i=0; i<254; i++){
		open(tempDir+"CheckMin.jpg");
		setMinAndMax(min, max);
		run("Apply LUT");
		getStatistics(area,mean);
		print("min=",min," mean=",mean);
		if(mean <= targetMeanBlack){
			optimizedMin = min;
			i=255; //break
		}
		min = min+1;
		close();
	}
	
	//Results
	OPT_MIN[now] = optimizedMin;
	OPT_MAX[now] = optimizedMax;
	
	print("");
	print(now+1,"/",trials);
	print("Title =",name);
	print("OptimizedMin =",optimizedMin);
	print("OptimizedMax =",optimizedMax);
	setBatchMode(false);
} 

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


//-----------------------------------------------------------------------------
//Define optMinMaxAuto

function optMinMaxAuto(){

	name = getTitle();

	//Progress
	print(procCount+1, "/", cFiles, "...Progress=",floor((procCount+1)/cFiles*10000)/100,"%" , " [OptMinMaxAuto]");
	print("...",name);
	
	if(flagExtactRedChannel == 1) {
		run("RGB Stack");
		setSlice(1);	//red stack
		updateDisplay();
	}

	depth = bitDepth();
	if (depth == 8) {
		width = getWidth();
		height = getHeight();

		x = 0;
		y = 0;

		sum = 0;
		whiteCheckCount = 0;
		blackCheckCount = 0;

		whiteSegCount = 0;
		blackSegCount = 0;

		whiteMax = 0;
		blackMin = 255;

		//whiteBackGround
		segCount = 0;
		xSeg = 60;
		ySeg = 60;
		xStep = xSeg;
		yStep = ySeg;

		for(x=0; x<width-xSeg; x=x+xStep){
			for(xi=0; xi<xSeg; xi++){
				for(yi=0; yi<ySeg; yi++){
					pixelValue = getPixel(x+xi,y+yi);
					sum = sum+pixelValue;
				}
			}
			mean = sum/xSeg/ySeg;
			if(mean > whiteMax) {
				whiteMax = mean;
				whiteSegCount = segCount;
				whiteCheckCount++;
				//print("ImgNo.",procCount+1, ", white-",whiteCheckCount);
				
			}
			makeRectangle(x, y, xSeg, ySeg);
			run("Crop");
			saveTitle = "wSegNo"+zeroPad(segCount,6);
			saveAs("Jpeg", tempDir+saveTitle+".jpg");

			close();
			open(dir+list[i]);

			if(flagExtactRedChannel == 1) {
				run("RGB Stack");
				setSlice(1);	//red stack
				updateDisplay();
			}	

			sum = 0;
			segCount++;
		}

		//blackKuroBeta
		segCount = 0;
		xSeg = 70;
		ySeg = 70;
		xStep = 2*xSeg;
		yStep = 2*ySeg;

		for(y=0; y<height-ySeg; y=y+yStep){
			for(x=0; x<width-xSeg; x=x+xStep){
				for(xi=0; xi<xSeg; xi++){
					for(yi=0; yi<ySeg; yi++){
						pixelValue = getPixel(x+xi,y+yi);
						sum = sum+pixelValue;
					}
				}
				mean = sum/xSeg/ySeg;
			
				if(mean < blackMin){
					blackMin = mean;
					blackSegCount = segCount;
					blackCheckCount++;
					//print("ImgNo.",procCount+1, ", black-",blackCheckCount);
				}
				makeRectangle(x, y, xSeg, ySeg);
				run("Crop");
				saveTitle = "bSegNo"+zeroPad(segCount,6);
				saveAs("Jpeg", tempDir+saveTitle+".jpg");
	
				close();
				open(dir+list[i]);

				if(flagExtactRedChannel == 1) {
					run("RGB Stack");
					setSlice(1);	//red stack
					updateDisplay();
				}	

				sum = 0;
				segCount++;
			}
		}
		close(name); //originalImage
				
		//checkMax Loop
		
		optimizedMax = 0; //If not found
		max = 254;
		min = 0;
	
		print("Checking Max..." , "wSegNo" + zeroPad(whiteSegCount,6) + ".jpg");

		for (i=0; i<254; i++){
			open(tempDir+"wSegNo"+zeroPad(whiteSegCount,6)+".jpg");
			setMinAndMax(min, max);
			run("Apply LUT");
			getStatistics(area,mean);
			if(mean >= targetMeanWhite){
				optimizedMax = max;
				i = 255; //break
			}
			max = max-1;

			//	print(min,max,mean);
			//	print("Checking Max..." , "wSegNo" + zeroPad(whiteSegCount,6) + ".jpg");
			
			close();
		}
		print("Temp_OptimizedMax =",optimizedMax);
		OPT_MAX[procCount] = optimizedMax;
	
		//checkMin Loop

		optimizedMin = 255; //If not found
		max = 220; //Daitai 200 Gurai Ooi kara
		min = 1;
		
		print("Checking Min..." , "bSegNo" + zeroPad(blackSegCount,6) + ".jpg");
	
		for (i=0; i<254; i++){
			open(tempDir+"bSegNo"+zeroPad(blackSegCount,6)+".jpg");
		
			setMinAndMax(min, max);
			run("Apply LUT");
			getStatistics(area,mean);
			if(mean <= targetMeanBlack){
				optimizedMin = min;
				i=255; //break
			}
			min = min+1;

			//	print(min,max,mean);
			//	print("Checking Min..." , "bSegNo" + zeroPad(blackSegCount,6) + ".jpg");
			
			if(min == max){
				i=255; //break
			}

			close();
		}
		print("Temp_OptimizedMin =",optimizedMin);
		OPT_MIN[procCount] = optimizedMin;	

	}else{ //else if(depth != 8bit)
		close(name); //original Image
		print("detect color image");
		print("Temp_OptimizedMin = 255");
		print("Temp_OptimizedMax = 0");
	}	

	procCount++; //and back to listFiles2
}

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


//-----------------------------------------------------------------------------
//Define optMinMaxResults 

function optMinMaxResults(){

	//	//Plot Graphs
	//	i=linspace(1,cFiles,cFiles);
	//	Plot.create("Max","No.","Max",i,OPT_MAX);
	//	Plot.show();
	//	Plot.create("Min","No.","Min",i,OPT_MIN);
	//	Plot.show();
	
	//Max
	aCountMax=0;
	//Outlier Exclusion
	for(i=0; i<cFiles; i++){
		if(OPT_MAX[i] > 150){
			aCountMax++;
		}
	}
	if(aCountMax == 0) exit("Error! Mabiki motto sukunaku.");
	OPT_MAX = bubbleSort(OPT_MAX);
	OPT_MAX = Array.slice(OPT_MAX,cFiles-aCountMax,cFiles);
	
	hosei = -10;
	median = floor(aCountMax/2);
	if(aCountMax % 2 == 0) median = median-1;
	optimizedMax = OPT_MAX[median] + hosei;

	//Min
	aCountMin=0;
	for(i=0; i<cFiles; i++){
		if(OPT_MIN[i] < 100){
			aCountMin++;
		}
	}
	if(aCountMin == 0) exit("Error! Mabiki motto sukunaku.");
	OPT_MIN = bubbleSort(OPT_MIN);
	OPT_MIN = Array.slice(OPT_MIN,0,aCountMin);

	median = floor(aCountMin/2);
	if(aCountMin % 2 == 0) median = median-1;
	optimizedMin = OPT_MIN[median];

	//Results
	print("OptimizedMin =",optimizedMin);
	print("OptimizedMax =",optimizedMax);
	
}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Define function getTimeStamp

function getTimeStamp(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	timeStamp = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	timeStamp = strYear+strMonth+strDayOfMonth+"_"+strHour+"h"+strMinute+"m"+strSecond+"s";
	return timeStamp;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define function whatTimeNow

function whatTimeNow(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	stringTime = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	stringTime = strYear+"/"+strMonth+"/"+strDayOfMonth+"_"+strHour+":"+strMinute+":"+strSecond;
	return stringTime;
}

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

//-----------------------------------------------------------------------------
//Define 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;
}	

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


//-----------------------------------------------------------------------------
//Define function linspace (generate Arithmetric Sequence)
//a->1st term, an->last term, n->term number

function linspace(a,an,n){
	A = newArray(n);
	d = (an-a)/(n-1);

	for(i=0;i<n;i++){
		A[i] = a+i*d;
	}	

	return A;
}

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


//-----------------------------------------------------------------------------
//Define function bubble_sort

function bubbleSort(A){
	n=A.length;
	for(i=0;i<n-1;i++){
		for(j=n-1;j>i;j--){
			if(A[j]<A[j-1]){
				tmp=A[j];
				A[j]=A[j-1];
				A[j-1]=tmp;
			}
		}
	}
	return A;
}

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

 

 

新機能

 

① 適切なコントラストと濃度を自動で調べてくれるマクロ、「Optimize Min And Max」を完全自動化

 

「Optimize Min And Max」を完全自動化して「Optimize Min And Max Auto」として実装しました。

 

アルゴリズムの概要は、

① 開きたいフォルダopenDir内の画像を「mabiki」の数だけ飛ばしながら開いて、解析する。

② ある画像に対し、xSeg,ySegの大きさのROIを置いて切り出し、最も白い部分を最適なmaxの解析に、最も黒い部分を最適なminの解析に使用する。

③ 通常のoptimize Min And Maxと同様、minとmaxを変えながらROI内の平均pixel値を計測し、設定したtarget値を超えた時点でのminとmaxを、その画像での最適な値と考える。

④ 手順②と③を全調査画像( = totalFiles / mabiki)で繰り返し、収集した「最適min、最適maxの候補たち」に対して、外れ値の除外処理をした上で残った値のだいたい中央値にあたる値を「最適min、max」と決定する。

⑤ このmin、maxを自動で引き継いで、コントラスト・濃度変更処理「Contrast Adjustment」に進む。

 

ようするに、勝手に進んで勝手に決めてくれる処理です。

 

② 従来の手動「Optimize Min And Max」も搭載

 

従来の、白い背景と黒ベタ部分にROIを手動で置くタイプの「Optimiz Min And Max」も、少しアレンジしたものを搭載しています。

 

アレンジした部分は以下の二つです。

① 設置と計測を3回繰り返してその平均値を利用する。

② 計測後、そのまま最適min、最適maxを自動で引き継いで、コントラスト・濃度変更処理「Contrast Adjustment」に進む。

 

解析の精度

 

最適min・maxの自動解析の精度についてですが、あんまり精度が悪いと使い物にならないですよね。

ということで以下の3つの方法で導き出される最適min・maxを比較してみました。

 ①  新型Optimize Min And Max Auto(Auto)

 ②  従来型Optimize Min And Max (Manual)

 ③ マクロを使わず目視で、いい感じのmin・maxに合わせる(目視)

 

それぞれ5回試して調べています。Autoはmabikiが同じだと同じ結果になるので、mabikiを少しづつ変えました。Manualは3回ずつの試行×5回繰り返しですが、なるべく違う画像を選ぶようにしました。

使った漫画はscansnap IX500で読み込んだ「ONE PIECE第3巻」です。

 

結果はこんな感じです。

f:id:yu3xx:20210529170822p:plain


グラフにすると、

f:id:yu3xx:20210529170813p:plain

f:id:yu3xx:20210529170817p:plain

 

アルゴリズムを組んでいる時は「たいした精度も良くなさそうだから、作るだけ無駄かなぁ」と思っていたのですが、案外Autoがいい感じなのです。

 

使用上のコツと、細かなお知らせ

 

Optimize Min And Max Autoの細かなコツやお知らせです。

 

調べるページ数cFiles(= totalFiles / mabiki) を10ぐらいまで増やすとまあまあ精度がいい感じになります。漫画1冊であれば、mabikiは20程度。20巻のシリーズをまとめて処理する際は、mabikiを100以上にして、1冊につき1ページ拾ってくる感じがいいと思います。cFilesを増やすと時間も長くなります。PCにもよりますが、cFiles = 10なら5分ぐらいでチェックが終わるかなと思います。

 

② 間引きする際のページカウントはサブフォルダごとでリセットされます。ですので総ファイル数totalFilesから算出したcFilesと、実際に調査するページ数は少しズレることがあります。ズレても問題無いように組んであります。

 

③ 大きさがxSeg,ySegのROIで最も白い場所を探す際に、なるべくページ上部のヤケ付近探すように組んでいます。なのでmax用の最も白い場所探しは、ページの最上段だけで行っています。min用の黒い場所探しはxStepとyStepで少しインチキしつつ画像全体で探しています。

 

④ 最も白い場所探しでは、本当は一番ヤケている場所に合わせたいのですが、アルゴリズムの仕様上、一番ヤケている部分ではなく一番白いところを検出してしまいます。この誤差を少し補正するためにhoseiというパラメータでoptimizedMaxを補正しています。

 

上部に余白が少なかったり、白い部分がほとんど無い漫画はAutoだと失敗する可能性が高くなるので、Manualを使うか、xSeg・ySeg・mabikiなどのパラメータを調整してみてください。

 

基本的な使い方

 

今までと同様、起動すると処理選択ウインドウが表示されますので、必要なものだけチェックしてください。

f:id:yu3xx:20210529170039p:plain

その後、パラメータ設定画面が表示されます。

f:id:yu3xx:20210522043539p:plain

各処理の細かな解説は、以下のページを参考にしてみてください。

 

 ・Contrast Adjustment

コントラスト・濃度を調整するMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Optimize Min And Max [Manual] 

コントラスト調整に必要なMax値とMin値を自動で求めるためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Change the Resolution

解像度を変更するMacro - その漫画自炊オタクはImageJマクロに恋をする

  

 ・Nombre Cut

実用版!ノンブル領域を削除し, コントラスト調整, 解像度変更も一括で行えるMacro - その漫画自炊オタクはImageJマクロに恋をする

 

・Renumbering [from 1] 

【多目的】コントラスト調整やリサイズ処理などなどを、一気にやってしまうためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Renumbering [Automator]

【Mac】実用版!PDFからJPEGに変換した画像のコントラスト調整+フォルダ収納のためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 

マクロの起動方法

 

①ImageJ上部タブの[Plugins]→[New]→[Macro]で起動したエディタに、記事の一番上のコードをコピペしてtxtファイル(Almighty_Processing.txt)を作成・保存する。 拡張子は「.ijm」でもOKです。

 

②保存したファイルをImageJフォルダ内の[plugins]フォルダにしまう。

このとき、[plugins]フォルダの中に新たに適当な名前のフォルダを作って、その中にしまってもOKです。ここでは仮に「自炊」というフォルダにtxtファイルを突っ込んだとします。

 

③一度ImageJを再起動すると、マクロがインストールされ、起動準備OK。

 

④上部タブ[Plugins]→[自炊]→[Almighty Processing]でマクロが実行されます。

 

 

imagej-jisui.hatenablog.com

 

 

 

 

ライセンスなんかは一切無いので、ぜひぜひ自由に使ってみてください!

  

imagej-jisui.hatenablog.com

 

 

更新履歴

ver.3.4.0.0:optMinMaxAutoでカラーページを引き当てた時の動作を少し追加

ver.3.4.1.0:誤字修正

ver.3.4.2.0:誤字修正

 

 

 

【切り取り】表紙カバーの処理に便利なマクロ

 

f:id:yu3xx:20210529104057p:plain

 

 

切り取り処理と解像度変更を一緒に行う、表紙カバーの処理に適したマクロです。

 

 

コード 

//BookJacket_Processing.ijm
//SetResolution
//Crop_UsingROI

//ver3.0.0

print("");
print("BookJacket_Processing");
print("Multi ROI");

setBatchMode(true);


//Do something for selected folder
showMessage("Select Open Folder");
openDir = getDirectory("Choose a Directory");
print("Processing :",openDir);
selectWindow("Log");
showMessage("Select Save Folder");
saveDir = getDirectory("Choose a Directory");
print("Save to :",saveDir);
selectWindow("Log");
list = getFileList(openDir);
wait(1000);


//clear ROI Manager
roiCount = roiManager("count");
if(roiCount > 0){
	roiManager("Deselect");
	roiManager("Delete");
}


//Enter parameter
Dialog.create("Processing Setting");
Dialog.addMessage("Enter dpi"); 
Dialog.addNumber("pre dpi",600);
Dialog.addNumber("post dpi(Target)",400);
items = newArray("JPEG", "PNG");
Dialog.addRadioButtonGroup("Output format", items, 1, 2, "JPEG");
Dialog.addCheckbox("Rename",false);
Dialog.show;
preDpi = Dialog.getNumber();
postDpi = Dialog.getNumber();
output = Dialog.getRadioButton();
flagRename = Dialog.getCheckbox();


//Extention
if(output == "JPEG") ext = ".jpg";
if(output == "PNG") ext = ".png";


//Rename
if(flagRename == 1){
	Dialog.create("Processing setting");
	Dialog.addString("newTrueTitle (e.g. BORUTO)","BORUTO");
	Dialog.addNumber("Start Number (NAN-KAN KARA?)",1);
	Dialog.addNumber("Digit of KAN-SUU",2);

	Dialog.show;
	newTrueTitle = Dialog.getString();
	startNumber = Dialog.getNumber();
	digit = Dialog.getNumber();
}


//ROI Setting 
Dialog.create("ROI no KAZU");
items = newArray("1", "2", "3", "4", "5");
Dialog.addRadioButtonGroup("Select ROI no KAZU", items, 1, 5, "2");
Dialog.show;
roiNumber = Dialog.getRadioButton();
roiNumber = parseInt(roiNumber);


//Page Name
Dialog.create("Page Name Setting");
if(roiNumber >= 1) Dialog.addString("Page Number of 1st ROI","0");
if(roiNumber >= 2) Dialog.addString("Page Number of 2nd ROI","00");
if(roiNumber >= 3) Dialog.addString("Page Number of 3rd ROI","000");
if(roiNumber >= 4) Dialog.addString("Page Number of 4th ROI","0000");
if(roiNumber >= 5) Dialog.addString("Page Number of 5th ROI","00000");
Dialog.addString("Page Number of Full Cover (Non Crop)","000000");
Dialog.show;
pageROI = newArray(roiNumber);
if(roiNumber >= 1) pageROI[0] = Dialog.getString();
if(roiNumber >= 2) pageROI[1] = Dialog.getString();
if(roiNumber >= 3) pageROI[2] = Dialog.getString();
if(roiNumber >= 4) pageROI[3] = Dialog.getString();
if(roiNumber >= 5) pageROI[4] = Dialog.getString();
pageFull = Dialog.getString();


//JPEG,PNG quality setting(jpeg=90,gif=-1)
quality = 90;
run("Input/Output...", "jpeg=quality gif=-1 file=.csv save_column save_row");


//SetResolution Setting
compressionRate = postDpi/preDpi;


//make parentDirectory
parentDir = saveDir+"postProc_"+getTimeStamp()+"/";
File.makeDirectory(parentDir);


//operation
for (i=0; i<list.length; i++){
	ope();
}


//fin
roiManager("Deselect");
roiManager("Delete");
print("Compression Rate =",compressionRate);
print("oshimai");
beep();

//-----------------------------------------------------------------------------
//Define operation
function ope(){
		
	//progress
	print(i+1,"/",list.length,"...Progress=",floor((i+1)/list.length*10000)/100,"%");
	
	open(openDir + list[i]);
	
	name = getTitle();
	dotIndex = lastIndexOf(name, ".");
	title = substring(name,0,dotIndex);

	if(flagRename == 1) {
		newTitle = newTrueTitle+"_"+zeroPad(startNumber,digit);
		title = newTitle;
	}

	//Change Resolution
	width = getWidth();
	height = getHeight();
	targetW = floor(width * compressionRate);
	run("Size...", "width=targetW constrain average interpolation=Bicubic");

	//Put the ROI 
	if(i == 0){
		setBatchMode(false);
		for(ii=0; ii<roiNumber; ii++){
			setTool("rectangle");
			if(ii == 0) waitForUser("ROI selection","Set 1st ROI using rectangle tool, then click \"OK\".");
			if(ii == 1) waitForUser("ROI selection","Set 2nd ROI using rectangle tool, then click \"OK\".");
			if(ii == 2) waitForUser("ROI selection","Set 3rd ROI using rectangle tool, then click \"OK\".");
			if(ii == 3) waitForUser("ROI selection","Set 4th ROI using rectangle tool, then click \"OK\".");
			if(ii == 4) waitForUser("ROI selection","Set 5th ROI using rectangle tool, then click \"OK\".");
			roiManager("Add");
			print("ROI Number", ii+1, "...ok"); 
			roiManager("Show None");
		}
		setBatchMode(true);
	}
	
	//Save FullCover
	newname = title + "_" + pageFull + ext;
	saveAs(output, parentDir + newname);
	print("Save to...",parentDir + newname);

	//Save CroppedCover
	for(ii=0; ii<roiNumber; ii++){
		roiManager("Select", ii);
		run("Crop");
		newname = title + "_" + pageROI[ii] + ext;
		saveAs(output, parentDir + newname);
		print("Save to...",parentDir + newname);
		close();
		if(ii != roiNumber-1) open(parentDir + title + "_" + pageFull + ext); //FullCover
	}
	
	if(flagRename == 1) startNumber++;
}

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

//-----------------------------------------------------------------------------
//Define function getTimeStamp

function getTimeStamp(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	timeStamp = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	timeStamp = strYear+strMonth+strDayOfMonth+"_"+strHour+strMinute+strSecond;
	return timeStamp;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define 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;
}	

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

2021.06.06更新 (ver2.2.1)Renameを選ばない限りエラーで進まなくなるバグを修正 

2021.06.14更新 (ver3.0.0)複数のROIを設置できるように機能追加  

 

機能のポイント

 

① 裁断せずにスキャンした表紙カバーを、切り取りCrop処理で切り抜く

 

マニュアルでROIを設置してCropします。物理的には切らずに処理できるのでラクチン!

 

 

② 解像度をわざと落としてモアレを軽減

 

カラー300dpi読み取りはモアレが目立つので、600dpi読み取りしたデータを300〜400dpiにリサイズすることで、モアレの影響を少なくしたデータに!

 

この処理は「切り取り前の画像」「切り取り後の画像」の両方に行うのがポイント!

 

③ 複数のROIで「表表紙、裏表紙、折り返し」を一気に切り取り出来る

 

ver 3.0.0以降の機能です

f:id:yu3xx:20210614060821p:plain

設置するROIの数を選択することが出来るので、例えば「表表紙、裏表紙、切り取り前の画像」を保存したい場合はROIの数を「2」にします。

(切り取り前画像は必ず保存されるのでROIいらず) 

 

 

④ 処理後のファイル名に、自分で設定した文字列を付け足す

 

 ver 3.0.0以降の機能です

f:id:yu3xx:20210614061334p:plain

ファイル名に好きな文字列を付け足します。

 表表紙...呪術廻戦_16_0.png

 裏表紙...呪術廻戦_16_00.png

 切り取り前...呪術廻戦_16_000000.png

 本文...呪術廻戦_16_0001.png

処理前のファイル名を「呪術廻戦_16.png」としておくのがミソです。

 

この処理が終わってから、『Renumbering from1』でさらにファイル名を整理しておくとさらに良しです。

 

Renumbering from1のコードは下記記事の後半です。


 

 マクロの起動方法

 

①ImageJ上部タブの[Plugins]→[New]→[Macro]で起動したエディタに、記事の一番上のコードをコピペしてtxtファイル(BookJacket_Processing.txt)を作成・保存する。拡張子は「.ijm」でもOK!

 

②保存したファイルをImageJフォルダ内の[plugins]フォルダにしまう。

このとき、[plugins]フォルダの中に新たに適当な名前のフォルダを作って、その中にしまってもOKです。ここでは仮に「自炊」というフォルダにtxtファイルを突っ込んだとします。

 

③一度ImageJを再起動すると、マクロがインストールされ、起動準備OK。

 

④上部タブ[Plugins]→[自炊]→[BookJacket Processing]でマクロが実行されます。

 

 

imagej-jisui.hatenablog.com

 

 

 

 

ライセンスなんかは一切無いので、ぜひぜひ自由に使ってみてください!

  

imagej-jisui.hatenablog.com

 

 

 


 

【改良版】コントラスト調整やリサイズ処理などなどを、一気にやってしまうためのMacro

  

f:id:yu3xx:20210522051919j:plain

 

以前紹介したお役立ち多目的マクロに、「コントラスト自動調整マクロoptimizeMinAndMax」をくっつけました。

 

 

何かと出番の多い汎用型マクロの最新版です!

 

//Almighty_Processing.txt
//ContrastAdjust (As necessary, for 8bit, FullColor)
//Optimize minAndMax (As necessary)
//SetResolution (As necessary, for any Type)
//NombreCut type1 (As necessary, UseOneROI)
//NombreCut type2 (As necessary, CaseDivided by ODD/EVEN, [number==0 -> error])
//Renumbering from1 (As necessary, Files must be in subDirectory)
//Renumbering (As necessary, Automater)
//MakeDirAuto (FileName : trueTitle_number.ext)


print("");
print("Almighty Processing");
print("makeDirAuto");
print("listFilesRecursively");


//Define variable
flagFullColor = 0;

//Select Processing
Dialog.create("Processing setting");
Dialog.addMessage("Select Processing");
Dialog.addMessage("");
Dialog.addMessage("- Main -");
Dialog.addCheckbox("Contrast Adjustment", false);
Dialog.addCheckbox("Change the Resolution",false);
Dialog.addCheckbox("Nombre Cut",false);
Dialog.addMessage("");
Dialog.addMessage("- Additional -");
Dialog.addMessage("");
Dialog.addCheckbox("Optimize minAndMax",false);
Dialog.addCheckbox("Renumbering [from 1]",false);
Dialog.addCheckbox("Renumbering [Automator]",false);
Dialog.show;
flagContr = Dialog.getCheckbox();
flagReso = Dialog.getCheckbox();
flagNombre = Dialog.getCheckbox();
flagOptMinMax = Dialog.getCheckbox();
flagRenumFrom1 = Dialog.getCheckbox();
flagRenumATMT = Dialog.getCheckbox();
//print
print("");
if(flagContr == 1) print("flagContra = ON");
if(flagReso == 1) print("flagReso = ON");
if(flagNombre == 1) print("flagNombre = ON");
if(flagOptMinMax == 1) print("flagOptMinMax = ON");
if(flagRenumFrom1 == 1) print("flagRenumFron1 = ON");
if(flagRenumATMT == 1) print("flagRenumATMT = ON");
print("");

if(flagRenumFrom1 == 1 && flagRenumATMT == 1) exit("Two \"Renumbering Processing\" were selected.");
if (flagContr == 0 && flagReso == 0 && flagNombre == 0) exit("You must select at least one processing!!");


//Do something for selected folder
showMessage("Select Processing Folder");
openDir = getDirectory("Choose a Directory");
print("Processing :",openDir);
selectWindow("Log");
showMessage("Select Save Folder");
saveDir = getDirectory("Choose a Directory");
print("Save to :",saveDir);
selectWindow("Log");
list = getFileList(openDir);
wait(1000);


//optimizeMinAndMax
var optimizedMin = 0;
var optimizedMax = 255;
if(flagOptMinMax == 1) optMinMax();


//Select Nombre Type
if(flagNombre == 1){
	Dialog.create("Nombre Cut setting");
	items = newArray("Use One ROI", "Case Divided by ODD/EVEN");
	Dialog.addRadioButtonGroup("Select NombreCut Type", items, 2, 1, "Use One ROI");
	Dialog.show;
	nombreRadio = Dialog.getRadioButton();
	if(nombreRadio == "Use One ROI") NombreType = 1;
	if(nombreRadio== "Case Divided by ODD/EVEN") NombreType = 2;
}

//Clear ROI Manager and set ROI
if(flagNombre == 1){
	roiCount = roiManager("count");
	if(roiCount > 0){
		roiManager("Deselect");
		roiManager("Delete");
	}
	if(NombreType == 1){
		//ROI setting
		waitForUser("ROI setting","Open an image and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();
	}
	if(NombreType == 2){
		//ROI setting ODD(1,3,5,7,...)
		waitForUser("ODD(1,3,5,7,...)","Open an [ODD number] file and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();

		//ROI setting EVEN(2,4,6,8,...)
		waitForUser("EVEN(2,4,6,8,...)","Open an [EVEN number] file and set [ROI] using rectangle tool, then click [OK].");
		roiManager("Add");
		close();
	}
}

//Enter parameter
Dialog.create("Processing Setting");
if(flagContr == 1) Dialog.addNumber("min (0-255)",optimizedMin);
if(flagContr == 1) Dialog.addNumber("Max (0-255)",optimizedMax);
if(flagContr == 1) Dialog.addCheckbox("Full Color", false);
if(flagContr == 1) Dialog.addMessage("");
if(flagReso == 1) Dialog.addNumber("pre dpi",600);
if(flagReso == 1) Dialog.addNumber("post dpi(Target)",400);
if(flagRenumATMT == 1 || flagRenumFrom1 == 1) Dialog.addNumber("Digit of Page Number",4);
items = newArray("JPEG", "PNG");
Dialog.addRadioButtonGroup("Output format", items, 1, 2, "JPEG");
Dialog.show;
if(flagContr == 1) min = Dialog.getNumber();
if(flagContr == 1) max = Dialog.getNumber();
if(flagContr == 1) flagFullColor = Dialog.getCheckbox();
if(flagReso == 1) preDpi = Dialog.getNumber();
if(flagReso == 1) postDpi = Dialog.getNumber();
if(flagRenumATMT == 1 || flagRenumFrom1 == 1) digit = Dialog.getNumber();
output = Dialog.getRadioButton();
//print
print("");
if(flagFullColor == 1) print("flagFullColor = ON");
if (flagContr == 1) print("min =",min,", Max =",max);
if (flagReso == 1) print("pre dpi =",preDpi,", Target dpi =",postDpi);

//Extension
if(output == "JPEG") ext = ".jpg";
if(output == "PNG") ext = ".png";

//JPEG,PNG quality setting(jpeg=90,gif=-1)
quality = 90;
run("Input/Output...", "jpeg=quality gif=-1 file=.csv save_column save_row");
if (output == "JPEG") print("JPEG quality =",quality);
print("");
wait(1000);

//SetResolution Setting
if(flagReso == 1) compressionRate = postDpi/preDpi;

//reference setting
var referLength;
if(flagRenumATMT == 1){
	referenceCheck(openDir);
}

//make parentDirectory
parentDir = saveDir+"postProc_"+getTimeStamp()+"/";
File.makeDirectory(parentDir);


//operation
startTime = whatTimeNow();
setBatchMode(true);
var totalFiles = 0;
var procCount = 0;

if (flagRenumFrom1 == 1){
	Table.create("Correspondence Table");
}
countFiles(openDir);
listFiles(openDir);

//clear ROI Manager
if(flagNombre == 1){
	roiManager("Deselect");
	roiManager("Delete");
}

//fin
if (flagRenumFrom1 == 1){
	Table.update;
}
finishTime = whatTimeNow();
print("");
if (flagContr == 1) print("min =",min,", Max =",max);
if (flagReso == 1) print("Compression Rate =",compressionRate);
if (output == "JPEG") print("JPEG quality =",quality);
print("Start Time .... ",startTime);
print("FinishTime ... ",finishTime);
print("oshimai");
beep();


//-----------------------------------------------------------------------------
//Define referenceCheck

function referenceCheck(dir){
	list = getFileList(dir);
	if(endsWith(list[0],"/")){
		referenceCheck(dir+list[0]);
	}else{
		name = list[0];
		dotIndex = lastIndexOf(name,".");
		title = substring(name,0,dotIndex);
		space = lastIndexOf(title," ");
		trueTitle = substring(title,0,space);
		number = substring(title,space+1);

		reference = trueTitle;
		referLength = lengthOf(reference);
		print("Check trueTitle. (e.g. NARUTO_60)");
		print("trueTitle=[",trueTitle,"]");
		showMessageWithCancel("Check trueTitle (except : number.ext), and click [OK].");
	}
}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//Define countFiles

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

//-----------------------------------------------------------------------------
//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(){
	
	procCount++;
	
	//Rename Section
	if(flagRenumATMT == 1){
		name = getTitle();
		dotIndex = lastIndexOf(name,".");
		title = substring(name,0,dotIndex);

		//ReNumbering
		titleLength = lengthOf(title);

		if(referLength < titleLength){
			space = lastIndexOf(title," ");
			trueTitle = substring(title,0,space);
			number = substring(title,space+1);

			newname = trueTitle+"_"+zeroPad(number,digit)+ext;
		
		}else if(referLength == titleLength){

			newname = title+"_"+zeroPad(1,digit)+ext;

			dotIndex = lastIndexOf(newname,".");
			title = substring(newname,0,dotIndex);
			underBar = lastIndexOf(title,"_");
			trueTitle = substring(title,0,underBar);
			number = substring(title,underBar+1);
		}	
	}else if(flagRenumATMT == 0){
		name = getTitle();
		dotIndex = indexOf(name, ".");
		title = substring(name,0,dotIndex);
		underBar = lastIndexOf(title,"_");
		trueTitle = substring(title,0,underBar);
		number = substring(title,underBar+1);
		newname = title+ext;	
		if (flagRenumFrom1 == 1) newname = trueTitle+"_"+zeroPad(i+1,digit)+ext;
	}
	rename(newname);

	//Output Correspondence table
	if (flagRenumFrom1 == 1){
		Table.set("index",procCount-1,procCount);
		Table.set("Original",procCount-1,name);
		Table.set("Newname",procCount-1,newname);
	}
	//Nombre Cut
	if(flagNombre == 1){
		if(NombreType == 1){
			roiManager("Select", 0);
			run("Crop");
		}
		if(NombreType == 2){
			number = parseInt(number);
			if(number == 0) exit("Error!! Number must not be null.");
			//ODD(1,3,5,7,...)
			if(number%2 == 1){
				roiManager("Select", 0);
				run("Crop");
			}
			//EVEN(2,4,6,8,...)
			if(number%2 == 0){
				roiManager("Select", 1);
				run("Crop");
			}
		}
	}

	//ContrastAdjust 8 bit
	depth = bitDepth();
	if(flagFullColor == 0) {
		if (depth == 8) {
			//ContrastAdjust (As necessary)
			if (flagContr == 1) setMinAndMax(min, max);
			if (flagContr == 1) run("Apply LUT");
		}
	}
	if(flagFullColor == 1) {
		//ContrastAdjust (As necessary)
		if (flagContr == 1) setMinAndMax(min, max);
		if (flagContr == 1) run("Apply LUT");
	}

	//SetResolution
	if (flagReso == 1){
		width = getWidth;
		height = getHeight;
		targetW = floor(width*compressionRate);
		run("Size...", "width=targetW constrain average interpolation=Bicubic");
	}

	//Progress.the second decimal place by 10000/100
	print(procCount,"/",totalFiles,"...Progress=",floor(procCount/totalFiles*10000)/100,"%");
	
	//Save to SubDirectory
	subDir = parentDir+trueTitle+"/";
	if(!File.exists(subDir)){
		File.makeDirectory(subDir);
	}
	
	saveAs(output, subDir+newname);
	print("Save to...",subDir+newname);

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

//-----------------------------------------------------------------------------
//Define optMinMax

function optMinMax(){

print("");
print("OptimizeMinAndMax");

if(nImages == 0) waitForUser("OptMinAndMax.  Open an image, then click [OK]"); 

//Enter parameter
Dialog.create("OptMinAndMax Setting");
Dialog.addNumber("targetMeanWhite",254.97);
Dialog.addNumber("targetMeanBlack",27); //default->18
Dialog.show;
targetMeanWhite = Dialog.getNumber();
targetMeanBlack = Dialog.getNumber();


name = getTitle();
showMessage("Select Desktop  (OptimizeMinAndMax) ");
saveDir = getDirectory("OptimizeMinAndMax. Choose Desktop");
print("");
print("Your Desktop is...");
print(saveDir);
print("");
saveAs("Jpeg", saveDir+"CheckMaxAndMin.jpg");

//optimize Max

setTool("rectangle");
run("Specify...", "width=100 height=100 x=10 y=10");
waitForUser("ROI selection","Set ROI on the \"WHITE BACK GROUND\", then click [OK].");
getStatistics(area,mean);

setBatchMode(true);

if(mean < 150){
	exit("Error!! This area is not WHITE BACK GROUND.");
}

run("Crop");


saveAs("Jpeg", saveDir+"CheckMax.jpg");
close();

Max = 254;
min = 0;

print("optimized Max Macro, TargetMean =",targetMeanWhite);


for (i=0; i<254; i++){
	open(saveDir+"CheckMax.jpg");
	setMinAndMax(min, Max);
	run("Apply LUT");
	getStatistics(area,mean);
	print("Max=",Max," mean=",mean);
	if(mean >= targetMeanWhite){
		optimizedMax = Max;
		i = 255; //break
	}
	Max = Max-1;
	close();
}

print("OptimizedMax =",optimizedMax);

setBatchMode(false);

//optimize min

open(saveDir+"CheckMaxAndMin.jpg");

setTool("rectangle");
run("Specify...", "width=100 height=100 x=10 y=10");
waitForUser("ROI selection","Set ROI on the \"BLACK ZONE\", then click [OK].");
getStatistics(area,mean);

setBatchMode(true);

if(mean > 100){
	exit("Error!! This area is not BLACK ZONE.");
}

run("Crop");


saveAs("Jpeg", saveDir+"CheckMin.jpg");
close();

max = optimizedMax;
min = 1;

print("");
print("OptimizedMin Macro, TargetMean=",targetMeanBlack);


for (i=0; i<254; i++){
	open(saveDir+"CheckMin.jpg");
	setMinAndMax(min, max);
	run("Apply LUT");
	getStatistics(area,mean);
	print("min=",min," mean=",mean);
	if(mean <= targetMeanBlack){
		optimizedMin = min;
		i=255; //break
	}
	min = min+1;
	close();
}

print("");
print("Title =",name);
print("OptimizedMin =",optimizedMin);
print("OptimizedMax =",optimizedMax);
print("oshimai");
setBatchMode(false);

} //function end

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

//-----------------------------------------------------------------------------
//Define function getTimeStamp

function getTimeStamp(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	timeStamp = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	timeStamp = strYear+strMonth+strDayOfMonth+"_"+strHour+strMinute+strSecond;
	return timeStamp;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define function whatTimeNow

function whatTimeNow(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	stringTime = "string";
	strYear = ""+year;
	month = month+1;
	if(month < 10){
		strMonth = "0"+month;
	}else{
		strMonth = ""+month;
	}
	if(dayOfMonth < 10){
		strDayOfMonth = "0"+dayOfMonth;
	}else{
		strDayOfMonth = ""+dayOfMonth;
	}
	if(hour < 10){
		strHour = "0"+hour;
	}else{
		strHour = ""+hour;
	}
	if(minute < 10){
		strMinute = "0"+minute;
	}else{
		strMinute = ""+minute;
	}
	if(second < 10){
		strSecond = "0"+second;
	}else{
		strSecond = ""+second;
	}
	stringTime = strYear+"/"+strMonth+"/"+strDayOfMonth+"_"+strHour+":"+strMinute+":"+strSecond;
	return stringTime;
}

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

//-----------------------------------------------------------------------------
//Define 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;
}	

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

 

新機能

 

① 適切なコントラストと濃度を自動で調べてくれるマクロ、「Optimize Min And Max」を搭載

 

「Optimize Min And Max」で調べた値をそのまま「Contrast Adjustment」に引き継いでくれるので、少し作業がラクになります。min値Max値入力ダイアログの初期値として引き継ぐので、微調整も可能です。

 

そのままコードコピペだけで使えるように、「function optMinMax」内のsaveDirのデスクトップ指定を「ディレクトリをそのつど選択」の形式にしています。いちいち面倒だと思うので、コードをいじれる方は「saveDir = getDirectoryなにがし」のところを、「saveDir = /Users/yu3xx/Desktop/」とかに書き換えると幸せになれると思います。

 

ちなみにここで保存するcheckMaxなどの3つのファイルは、解析に使う目的で一時的に保存しているだけなので、あとで捨ててOKなファイルです。

 

② フルカラー画像に対するコントラスト・濃度調整を実装

 

地味に新機能です。いままでの「Contrast Adjustment」は白黒画像に対してでしたが、フルカラー画像に対しても、min値Max値の調整で簡易的にコントラスト・濃度を調整できるようにしました。使いたい場合はパラメータ設定画面で[Full Color]にチェック。

 

※ 残念なことに、Optimize Min And Maxはフルカラーには対応していないので、あらかじめ適切なmin値Max値を調べておいてください。手作業です。

f:id:yu3xx:20210522043539p:plain

使い方のポイント

 

① 起動すると処理選択ウインドウが表示されますので、必要なものだけチェックしてください。

f:id:yu3xx:20210522043533p:plain

② 選択したProcessing Folderの中に入っているサブフォルダ内のデータまで全て処理します(ListFiles)。 

 

フォルダ自動生成を命令しているので、各ファイルの名前は「trueTitle_number.拡張子」じゃないと動きません。(numberの前のアンダーバーが大事!)

例)僕のヒーローアカデミア_07_0149.png

 trueTitle = 僕のヒーローアカデミア_07

 number = 0149

 ※Renumbering [Automator] は例外です。numberの前にspaceが来るはずです。

 

④ 各処理の細かな解説は、以下のページを参考にしてみてください。

 

 ・Contrast Adjustment

コントラスト・濃度を調整するMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Optimize Min And Max 

コントラスト調整に必要なMax値とMin値を自動で求めるためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Change the Resolution

解像度を変更するMacro - その漫画自炊オタクはImageJマクロに恋をする

  

 ・Nombre Cut

実用版!ノンブル領域を削除し, コントラスト調整, 解像度変更も一括で行えるMacro - その漫画自炊オタクはImageJマクロに恋をする

 

・Renumbering [from 1] 

【多目的】コントラスト調整やリサイズ処理などなどを、一気にやってしまうためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

 ・Renumbering [Automator]

【Mac】実用版!PDFからJPEGに変換した画像のコントラスト調整+フォルダ収納のためのMacro - その漫画自炊オタクはImageJマクロに恋をする

 

  

マクロの起動方法

 

①ImageJ上部タブの[Plugins]→[New]→[Macro]で起動したエディタに、記事の一番上のコードをコピペしてtxtファイル(Almighty_Processing.txt)を作成・保存する。 

 

②保存したファイルをImageJフォルダ内の[plugins]フォルダにしまう。

このとき、[plugins]フォルダの中に新たに適当な名前のフォルダを作って、その中にしまってもOKです。ここでは仮に「自炊」というフォルダにtxtファイルを突っ込んだとします。

 

③一度ImageJを再起動すると、マクロがインストールされ、起動準備OK。

 

④上部タブ[Plugins]→[自炊]→[Almighty Processing]でマクロが実行されます。

 

 

imagej-jisui.hatenablog.com

 

 

 

 

ライセンスなんかは一切無いので、ぜひぜひ自由に使ってみてください!

  

imagej-jisui.hatenablog.com

 

 

 


 

【ImageJマクロ超入門】#9 情報入力ダイアログの作り方

 

f:id:yu3xx:20210515005746j:plain




 

皆さまこんにちは。

yu3xx(ゆーさんちょめちょめ)です。

 

 

 

前回のおさらい


 

前回は「二次元画像からピクセル値を直接拾って解析する方法」を紹介しました。

今回は「情報入力ダイアログを自分で作ってみよう!」です。


もくじ

 

 

 

情報入力ダイアログで出来ること

f:id:yu3xx:20210515020157p:plain

情報入力ダイアログを表示すると、解析や処理に必要な情報を一括で入力することが出来ます。

 

以前「ifで条件分岐」の回で「a = getNumber(Label,Default)」のカタチでの「数値入力ダイアログ」の紹介もしました。この数値入力ダイアログは簡便なので使いやすいのですが、1つのダイアログに1つの情報しか入力することが出来ません

 

ですので、複数の情報を入力したいときは今回お話しする「情報入力ダイアログ」を使うと便利です。

 

作り方

 

ダイアログ関係のコードは少しクセがあります。

 

ダイアログ呼び出しから情報取得までの流れ
 ① ダイアログ作成開始の命令
 ② 表示項目や入力させたい内容をまとめて設定
 ③ ダイアログを表示させ、ユーザーが入力
 ④ ダイアログに入力した情報をまとめて変数に取得

 

コードで表すと、
 ① Dialog.create
 ② Dialog.addなにがし
 ③ Dialog.show
 ④ Dialog.getなにがし


細かく解説していきます。

 

① ダイアログ作成開始の命令

Dialog.create("文章");
・ダイアログウィンドウのタイトル設定にあたる

 

② 表示項目や入力させたい内容をまとめて設定

入力したい情報や、入力形式によって命令が異なります。


Dialog.addMessage("文章");
・文章を表示させるだけ。入力は無し。コメントとして使う

 

Dialog.addString("文章",初期値);
・文字列の入力領域を表示

 

Dialog.addNumber("文章",初期値);
・数値を入力領域を表示

 

Dialog.addChoice("文章",items);
・プルダウン式の入力領域を表示。あらかじめプルダウンの項目を格納した配列(items)を作成しておく

 

Dialog.addRadioButtonGroup("文章",items,行,列,初期値);
・どれか一つだけ選択するラジオボタンを表示。あらかじめラジオボタンの項目を格納した配列(items)を作成しておく。行と列はラジオボタンの配置の仕方

 

Dialog.addCheckbox("文章",初期値);
・チェック欄ボックスを表示。初期値は、チェック状態なら「true」、チェック無しなら「false」

 

 

③ ダイアログを表示させ、ユーザーが入力
Diaog.show;
・ここで初めてダイアログが表示され、このタイミングでユーザーが入力することになる

 

④ ダイアログに入力した情報をまとめて変数に取得

上の②で入力した命令に対応した「getなにがし」で入力情報を拾っていく


記述順で紐づくので、正しく取得するためには「getなにがし」の記述順は「addなにがし」と同じ順番にしなければいけない

 

Dialog.getString();
・addStringに対応。返り値は入力した文字列

 

Dialog.getNumber();
・addNumberに対応。返り値は入力した数値

 

Dialog.getChoice();
・addChoiceに対応。返り値は選択したitems内の要素

 

Dialog.getRadioButton();
・addRadioButtonGroupに対応。get側はGroupが付かないので注意。返り値は選択したitems内の要素

 

Dialog.getCheckbox();
・addCheckboxに対応。返り値はtrueなら「1」、falseなら「0」

 

では実例で確認してみましょう!

 

情報入力ダイアログのサンプルコード


//example 9

Dialog.create("Your Data")

Dialog.addMessage("Enter Your Personal Information");
Dialog.addString("Name","David Lynch");
Dialog.addNumber("Age",75);

items = newArray("Male","Female","Other");
Dialog.addChoice("Gender",items);

items2 = newArray("Cofee","Cherry Pie","Donut");
Dialog.addRadioButtonGroup("Favorite food",items2,3,1,"Cofee");

Dialog.addCheckbox("Smoking",true);


Dialog.show;


name = Dialog.getString();
age = Dialog.getNumber();
gender = Dialog.getChoice();
food = Dialog.getRadioButton();
smoking = Dialog.getCheckbox();

print("Name : ", name);
print("Age : ",age);
print("Gender : ",gender);
print("Favorite Food : ",food);
print("Smoking : ",smoking);

 

f:id:yu3xx:20210515021954p:plain
こんな感じです!

 

ダイアログ表示までの手続きが少し煩雑ですが、一括で情報を入力したい時にはぜひ試してみて下さい!

 

 

 


おしまい

 

 

 

 

 

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

imagej-jisui.hatenablog.com