以前紹介したお役立ち多目的マクロにくっつけた「コントラスト自動調整マクロ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巻」です。
結果はこんな感じです。
グラフにすると、
アルゴリズムを組んでいる時は「たいした精度も良くなさそうだから、作るだけ無駄かなぁ」と思っていたのですが、案外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などのパラメータを調整してみてください。
基本的な使い方
今までと同様、起動すると処理選択ウインドウが表示されますので、必要なものだけチェックしてください。
その後、パラメータ設定画面が表示されます。
各処理の細かな解説は、以下のページを参考にしてみてください。
・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]でマクロが実行されます。
ライセンスなんかは一切無いので、ぜひぜひ自由に使ってみてください!
更新履歴
ver.3.4.0.0:optMinMaxAutoでカラーページを引き当てた時の動作を少し追加
ver.3.4.1.0:誤字修正
ver.3.4.2.0:誤字修正