皆さまこんにちは。
最近NewJeansにハマってます、yu3xx(ゆーさんちょめちょめ)です。
ImageJ上で自動処理を行うための拡張機能には「Macro」のほかに「Plugin」というものがあります。
PluginはJava言語で記述することになるのですが、レクチャー記事がMacroに比べ極端に少ないため、勉強するのが面倒でとっつきづらいのが現状です。
とはいえ、PluginがMacroよりも「よっぽど」優れているのであれば、まあ...仕方ない、ちょっとは勉強してやるか、と思うところもなくはないのですが...。
ということで、本記事では漫画自炊用Pluginを実際に作って、その処理時間を計測し、Macroの場合と比較してみました。
もくじ
Plugin
画像コントラストを変更するためのPluginです。試行錯誤しながらなんとか作りました...。
Pluginで画像を扱う際には、まずImagePlusというクラス(属性)で画像を取得し、さらに画像処理を行う場合にはImageProcessorというクラスに画素値を入れ込むようです。
ふぇぇ。なんのこっちゃ。
import ij.*;
import ij.process.*;
import ij.gui.*;
import java.awt.*;
import ij.plugin.*;
import ij.plugin.frame.*;
import java.io.File;
public class contrast_Adjust implements PlugIn {
double min;
double max;
public void run(String arg) {
//Select dir
IJ.showMessage("Select Open Folder");
String openDir = IJ.getDirectory("Choose a Directory");
if (openDir == null) return;
IJ.showMessage("Select Save Folder");
String saveDir = IJ.getDirectory("Choose a Directory");
if (saveDir == null) return;
//Get min max
min = IJ.getNumber("Min (42 de OK?):", 42);
max = IJ.getNumber("Max (243 de OK?):", 243);
//Open dir
File dir = new File(openDir);
File[] list = dir.listFiles();
//Operation
for (File file : list) {
if (file.isFile()) {
processImage(file.getAbsolutePath(), saveDir);
}
}
}
private void processImage(String filePath, String saveDir) {
//Open image
ImagePlus imp = IJ.openImage(filePath);
//Get Px data
ImageProcessor ip = imp.getProcessor();
//Apply min max
ip.setMinAndMax(min, max);
IJ.run(imp, "Apply LUT", "");
//Display image
//imp.show();
//Save image
String fileName = new File(filePath).getName();
String savePath = saveDir + fileName;
IJ.saveAs(imp, "JPEG", savePath);
}
}
Macro
マクロは、以前公開した「アレンジ用プログラム」をベースにして、上記プラグインと同じような処理が出来るようにしました。
//ListFilesRecursively_free
//JIBUNN DE IROIRO TSUKUTTE NE
//"??????" NO TOKORONI IROIRO KAITE NE
//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);
//Enter parameter
Dialog.create("Processing Setting");
items = newArray("JPEG", "PNG");
Dialog.addRadioButtonGroup("Output format", items, 1, 2, "JPEG");
Dialog.show;
output = Dialog.getRadioButton();
//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");
//make parentDirectory
parentDir = saveDir+"postProc_"+getTimeStamp()+"/";
File.makeDirectory(parentDir);
//operation
startTime = whatTimeNow();
setBatchMode(true);
var totalFiles = 0;
var procCount = 0;
countFiles(openDir);
listFiles(openDir);
//fin
finishTime = whatTimeNow();
print("");
print("Start Time .... ",startTime);
print("FinishTime ... ",finishTime);
print("oshimai");
beep();
//-----------------------------------------------------------------------------
//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++;
//Name setting
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;
rename(newname);
//--------- KOKO NI IROIRO IRETE NE ----------
//????????????????????????????????????????????
setMinAndMax(42,243);
run("Apply LUT");
//????????????????????????????????????????????
//Save to SubDirectory
subDir = parentDir+trueTitle+"/";
if(!File.exists(subDir)){
File.makeDirectory(subDir);
}
saveAs(output, subDir+newname);
close(newname);
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//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;
}
//-----------------------------------------------------------------------------
比較方法
・Plugin、Macroともに、min 42、max 243でコントラスト調整しJPEG保存
・処理対象はOne Piece(集英社)第107巻、全235ファイル
・処理開始から235ファイル目が保存されるまでの時間を3回計測し平均(ディレクトリ選択やmin maxの手動設定を除いた、実際の処理時間)
結果
・Plugin... 64.2 sec、62.8 sec、63.3 sec。平均 63.4 sec
・Macro... 60.2 sec、59.8 sec、61.4 sec。平均 60.4 sec
感想
なんと
Macroの方がちょびっとだけ速い
という結果でした。
もっと難しい処理であればPlugin側が有利になるかもしれませんが、簡単な自炊画像処理においてはMacroで十分ということでしょうか。
Pluginの処理コードを実際に書いてみてわかったこと
・ネット上に情報が少ないので拾いづらい
・ChatGPT大先生が頼りになるけどウソも多い
・Macroでは使えたRecord機能が無いのがつらい
・でも試行錯誤してやっと動いたときにはとても気持ちいい
まとめ
・漫画自炊処理においては、PluginとMacroの処理時間はほぼ同等
・Pluginに乗り換えるほどの価値は感じないが、なんだかんだでPlugin作りも楽しい
おしまい
・公開したマクロのまとめ