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

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

【真・改良版】コントラスト調整やリサイズ処理などなどを、一気にやってしまうための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:誤字修正