package ij.plugin;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.util.Tools;
import ij.io.*;
import ij.macro.Interpreter;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Vector;
public class BatchProcessor implements PlugIn, ActionListener, ItemListener, Runnable {
private static final String MACRO_FILE_NAME = "BatchMacro.ijm";
private static final String[] formats = {"TIFF", "8-bit TIFF", "JPEG", "GIF", "PNG", "PGM", "BMP", "FITS", "Text Image", "ZIP", "Raw"};
private static String format = Prefs.get("batch.format", formats[0]);
private static final String[] code = {
"[Select from list]",
"Add Border",
"Convert to RGB",
"Crop",
"Gaussian Blur",
"Invert",
"Label",
"Timestamp",
"Max Dimension",
"Measure",
"Print Index and Title",
"Resize",
"Scale",
"Show File Info",
"Unsharp Mask",
};
private static final String help = "<html>"
+"<h1>Process>Batch>Virtual Stack</h1>"
+"<font size=+1>"
+"This command runs macro code on each image in a virtual stack.<br>"
+"The processed images are saved in the <i>Output</i> folder,<br>"
+"in the specified <i>Format</i>, allowing them to be opened as a<br>"
+"virtual stack. Make sure the <i>Output</i> folder is empty<br>"
+"before clicking on <i>Process</i>.<br>"
+"<br>"
+"In the macro code, the 'i' (slice index) and 'n' (stack size) variables<br>"
+"are predefined. Call <i>setOption('SaveBatchOutput',false)</i> to<br>"
+"prevent the image currently being processed from being saved,<br>"
+"effectively removing it from the output virtual stack.<br> <br>"
+"</font>";
private String macro = "";
private int testImage;
private Button input, output, open, save, test;
private TextField inputDir, outputDir;
private GenericDialog gd;
private Thread thread;
private ImagePlus virtualStack;
private ImagePlus outputImage;
private boolean errorDisplayed;
private String filter;
private static boolean saveOutput = true;
public void run(String arg) {
if (arg.equals("stack")) {
virtualStack = IJ.getImage();
if (virtualStack.getStackSize()==1) {
error("This command requires a stack or virtual stack.");
return;
}
}
String macroPath = IJ.getDirectory("macros")+MACRO_FILE_NAME;
macro = IJ.openAsString(macroPath);
if (macro==null || macro.startsWith("Error: ")) {
IJ.showStatus(macro.substring(7) + ": "+macroPath);
macro = "";
}
if (!showDialog()) return;
String inputPath = null;
if (virtualStack==null) {
inputPath = inputDir.getText();
if (inputPath.equals("")) {
error("Please choose an input folder");
return;
}
inputPath = addSeparator(inputPath);
File f1 = new File(inputPath);
if (!f1.exists() || !f1.isDirectory()) {
error("Input does not exist or is not a folder\n \n"+inputPath);
return;
}
}
String outputPath = outputDir.getText();
outputPath = addSeparator(outputPath);
File f2 = new File(outputPath);
if (!outputPath.equals("") && (!f2.exists() || !f2.isDirectory())) {
error("Output does not exist or is not a folder\n \n"+outputPath);
return;
}
if (macro.equals("")) {
error("There is no macro code in the text area");
return;
}
ImageJ ij = IJ.getInstance();
if (ij!=null) ij.getProgressBar().setBatchMode(true);
IJ.resetEscape();
if (virtualStack!=null)
processVirtualStack(outputPath);
else
processFolder(inputPath, outputPath);
IJ.showProgress(1,1);
if (virtualStack==null)
Prefs.set("batch.input", inputDir.getText());
Prefs.set("batch.output", outputDir.getText());
Prefs.set("batch.format", format);
macro = gd.getTextArea1().getText();
if (!macro.equals(""))
IJ.saveString(macro, IJ.getDirectory("macros")+MACRO_FILE_NAME);
}
boolean showDialog() {
validateFormat();
gd = GUI.newNonBlockingDialog("Batch Process");
addPanels(gd);
gd.setInsets(15, 0, 5);
gd.addChoice("Output_format:", formats, format);
gd.setInsets(0, 0, 5);
gd.addChoice("Add macro code:", code, code[0]);
if (virtualStack==null)
gd.addStringField("File name contains:", "", 10);
gd.setInsets(15, 10, 0);
Dimension screen = IJ.getScreenSize();
gd.addTextAreas(macro, null, screen.width<=600?10:15, 60);
addButtons(gd);
gd.setOKLabel("Process");
Vector choices = gd.getChoices();
Choice choice = (Choice)choices.elementAt(1);
if (virtualStack!=null)
gd.addHelp(help);
choice.addItemListener(this);
gd.showDialog();
format = gd.getNextChoice();
if (virtualStack==null)
filter = gd.getNextString();
macro = gd.getNextText();
return !gd.wasCanceled();
}
void processVirtualStack(String outputPath) {
ImageStack stack = virtualStack.getStack();
int n = stack.size();
int index = 0;
for (int i=1; i<=n; i++) {
if (IJ.escapePressed()) break;
IJ.showProgress(i, n);
ImageProcessor ip = stack.getProcessor(i);
if (ip==null) return;
ImagePlus imp = new ImagePlus(i+"/"+stack.size(), ip);
if (!macro.equals("")) {
if (!runMacro("i="+(index++)+";"+"n="+stack.size()+";"+macro, imp))
break;
}
if (saveOutput && !outputPath.equals("")) {
if (format.equals("8-bit TIFF") || format.equals("GIF")) {
if (imp.getBitDepth()==24)
IJ.run(imp, "8-bit Color", "number=256");
else
IJ.run(imp, "8-bit", "");
}
IJ.saveAs(imp, format, outputPath+pad(i));
}
saveOutput = true;
imp.close();
}
if (outputPath!=null && !outputPath.equals(""))
IJ.run("Image Sequence...", "open=[" + outputPath + "]"+" use");
}
String pad(int n) {
String str = ""+n;
while (str.length()<5)
str = "0" + str;
return str;
}
void processFolder(String inputPath, String outputPath) {
String[] list = (new File(inputPath)).list();
list = FolderOpener.getFilteredList(list, filter, "Batch Processor");
if (list==null)
return;
int index = 0;
int startingCount = WindowManager.getImageCount();
for (int i=0; i<list.length; i++) {
if (IJ.escapePressed()) break;
String path = inputPath + list[i];
if (IJ.debugMode) IJ.log(i+": "+path);
if ((new File(path)).isDirectory())
continue;
if (list[i].startsWith(".")||list[i].endsWith(".avi")||list[i].endsWith(".AVI") || list[i].equals("Thumbs.db"))
continue;
IJ.showProgress(i+1, list.length);
IJ.redirectErrorMessages(true);
ImagePlus imp = IJ.openImage(path);
IJ.redirectErrorMessages(false);
if (imp==null && WindowManager.getImageCount()>startingCount)
imp = WindowManager.getCurrentImage();
if (imp==null)
imp = Opener.openUsingBioFormats(path);
if (imp==null) {
IJ.log("openImage() and openUsingBioFormats() returned null: "+path);
continue;
}
if (!macro.equals("")) {
outputImage = null;
if (!runMacro("i="+(index++)+";"+macro, imp))
break;
}
if (saveOutput && !outputPath.equals("")) {
if (format.equals("8-bit TIFF") || format.equals("GIF")) {
if (imp.getBitDepth()==24)
IJ.run(imp, "8-bit Color", "number=256");
else
IJ.run(imp, "8-bit", "");
}
if (outputImage!=null && outputImage!=imp)
IJ.saveAs(outputImage, format, outputPath+list[i]);
else
IJ.saveAs(imp, format, outputPath+list[i]);
}
saveOutput = true;
imp.close();
}
}
private boolean runMacro(String macro, ImagePlus imp) {
WindowManager.setTempCurrentImage(imp);
Interpreter interp = new Interpreter();
try {
outputImage = interp.runBatchMacro(macro, imp);
} catch(Throwable e) {
interp.abortMacro();
String msg = e.getMessage();
if (!(e instanceof RuntimeException && msg!=null && e.getMessage().equals(Macro.MACRO_CANCELED)))
IJ.handleException(e);
return false;
} finally {
WindowManager.setTempCurrentImage(null);
}
return true;
}
String addSeparator(String path) {
if (path.equals("")) return path;
if (!(path.endsWith("/")||path.endsWith("\\")))
path = path + File.separator;
return path;
}
void validateFormat() {
boolean validFormat = false;
for (int i=0; i<formats.length; i++) {
if (format.equals(formats[i])) {
validFormat = true;
break;
}
}
if (!validFormat) format = formats[0];
}
void addPanels(GenericDialog gd) {
Panel p = new Panel();
p.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
if (virtualStack==null) {
input = new Button("Input...");
input.addActionListener(this);
p.add(input);
inputDir = new TextField(Prefs.get("batch.input", ""), 45);
p.add(inputDir);
gd.addPanel(p);
}
p = new Panel();
p.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
output = new Button("Output...");
output.addActionListener(this);
p.add(output);
outputDir = new TextField(Prefs.get("batch.output", ""), 45);
p.add(outputDir);
gd.addPanel(p);
}
void addButtons(GenericDialog gd) {
Panel p = new Panel();
p.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
test = new Button("Test");
test.addActionListener(this);
p.add(test);
open = new Button("Open...");
open.addActionListener(this);
p.add(open);
save = new Button("Save...");
save.addActionListener(this);
p.add(save);
gd.addPanel(p);
}
public void itemStateChanged(ItemEvent e) {
Choice choice = (Choice)e.getSource();
String item = choice.getSelectedItem();
String code = null;
if (item.equals("Convert to RGB"))
code = "run(\"RGB Color\");\n";
else if (item.equals("Measure"))
code = "run(\"Measure\");\n";
else if (item.equals("Resize"))
code = "run(\"Size...\", \"width=512 height=512 interpolation=Bicubic\");\n";
else if (item.equals("Scale"))
code = "scale=1.5;\nw=getWidth*scale; h=getHeight*scale;\nrun(\"Size...\", \"width=w height=h interpolation=Bilinear\");\n";
else if (item.equals("Label"))
code = "setFont(\"SansSerif\", 18, \"antialiased\");\nsetColor(\"red\");\ndrawString(\"Hello\", 20, 30);\n";
else if (item.equals("Timestamp"))
code = openMacroFromJar("TimeStamp.ijm");
else if (item.equals("Crop"))
code = "makeRectangle(getWidth/4, getHeight/4, getWidth/2, getHeight/2);\nrun(\"Crop\");\n";
else if (item.equals("Add Border"))
code = "border=25;\nw=getWidth+border*2; h=getHeight+border*2;\nrun(\"Canvas Size...\", \"width=w height=h position=Center zero\");\n";
else if (item.equals("Invert"))
code = "run(\"Invert\");\n";
else if (item.equals("Gaussian Blur"))
code = "run(\"Gaussian Blur...\", \"sigma=2\");\n";
else if (item.equals("Unsharp Mask"))
code = "run(\"Unsharp Mask...\", \"radius=1 mask=0.60\");\n";
else if (item.equals("Show File Info"))
code = "path=File.directory+File.name;\ndate=File.dateLastModified(path);\nsize=File.length(path);\nprint(i+\", \"+getTitle+\", \"+date+\", \"+size);\n";
else if (item.equals("Max Dimension"))
code = "max=2048;\nw=getWidth; h=getHeight;\nsize=maxOf(w,h);\nif (size>max) {\n scale = max/size;\n w*=scale; h*=scale;\n run(\"Size...\", \"width=w height=h interpolation=Bicubic average\");\n}";
else if (item.equals("Print Index and Title"))
code = "if (i==0) print(\"\\\\Clear\"); print(IJ.pad(i,4)+\": \"+getTitle());\n";
if (code!=null) {
TextArea ta = gd.getTextArea1();
ta.insert(code, ta.getCaretPosition());
if (IJ.isMacOSX()) ta.requestFocus();
}
}
public static String openMacroFromJar(String name) {
ImageJ ij = IJ.getInstance();
Class c = ij!=null?ij.getClass():(new ImageStack()).getClass();
String macro = null;
try {
InputStream is = c .getResourceAsStream("/macros/"+name);
if (is==null) return null;
InputStreamReader isr = new InputStreamReader(is);
StringBuffer sb = new StringBuffer();
char [] b = new char [8192];
int n;
while ((n = isr.read(b)) > 0)
sb.append(b,0, n);
macro = sb.toString();
}
catch (IOException e) {
return null;
}
return macro;
}
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source==input) {
String path = IJ.getDirectory("Input Folder");
if (path==null) return;
inputDir.setText(path);
} else if (source==output) {
String path = IJ.getDirectory("Output Folder");
if (path==null) return;
outputDir.setText(path);
} else if (source==test) {
thread = new Thread(this, "BatchTest");
thread.setPriority(Math.max(thread.getPriority()-2, Thread.MIN_PRIORITY));
thread.start();
} else if (source==open)
open();
else if (source==save)
save();
}
void open() {
String text = IJ.openAsString("");
if (text==null) return;
if (text.startsWith("Error: "))
error(text.substring(7));
else {
if (text.length()>30000)
error("File is too large");
else
gd.getTextArea1().setText(text);
}
}
void save() {
macro = gd.getTextArea1().getText();
if (!macro.equals(""))
IJ.saveString(macro, "");
}
void error(String msg) {
IJ.error("Batch Processor", msg);
}
public void run() {
TextArea ta = gd.getTextArea1();
String macro = ta.getText();
if (macro.equals("")) {
error("There is no macro code in the text area");
return;
}
ImagePlus imp = null;
IJ.redirectErrorMessages(true);
if (virtualStack!=null)
imp = getVirtualStackImage();
else
imp = getFolderImage();
IJ.redirectErrorMessages(false);
if (imp==null) {
if (!errorDisplayed)
IJ.log("IJ.openImage() returned null");
return;
}
runMacro("i=0;"+macro, imp);
Point loc = new Point(10, 30);
if (testImage!=0) {
ImagePlus imp2 = WindowManager.getImage(testImage);
if (imp2!=null) {
ImageWindow win = imp2.getWindow();
if (win!=null) loc = win.getLocation();
imp2.changes=false;
imp2.close();
}
}
imp.show();
ImageWindow iw = imp.getWindow();
if (iw!=null) iw.setLocation(loc);
testImage = imp.getID();
}
ImagePlus getVirtualStackImage() {
ImagePlus imp = virtualStack.createImagePlus();
imp.setProcessor("", virtualStack.getProcessor().duplicate());
return imp;
}
ImagePlus getFolderImage() {
String inputPath = inputDir.getText();
inputPath = addSeparator(inputPath);
File f1 = new File(inputPath);
if (!f1.exists() || !f1.isDirectory()) {
error("Input does not exist or is not a folder\n \n"+inputPath);
errorDisplayed = true;
return null;
}
String[] list = (new File(inputPath)).list();
String name = list[0];
if (name.startsWith(".")&&list.length>1) name = list[1];
String path = inputPath + name;
setDirAndName(path);
return IJ.openImage(path);
}
void setDirAndName(String path) {
File f = new File(path);
OpenDialog.setLastDirectory(f.getParent()+File.separator);
OpenDialog.setLastName(f.getName());
}
public static void saveOutput(boolean b) {
saveOutput = b;
}
}