package ij.plugin;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
public class MontageMaker implements PlugIn {
private static int columns, rows, first, last, inc, borderWidth;
private static double scale;
private static boolean label;
private static boolean useForegroundColor;
private static int saveID;
private static int saveStackSize;
private static final int defaultFontSize = 12;
private static int fontSize = defaultFontSize;
private boolean hyperstack;
public void run(String arg) {
ImagePlus imp = WindowManager.getCurrentImage();
if (imp==null || imp.getStackSize()==1) {
error("Stack required");
return;
}
hyperstack = imp.isHyperStack();
if (hyperstack && imp.getNSlices()>1 && imp.getNFrames()>1) {
error("5D hyperstacks are not supported");
return;
}
int channels = imp.getNChannels();
if (!hyperstack && imp.isComposite() && channels>1) {
int channel = imp.getChannel();
CompositeImage ci = (CompositeImage)imp;
int mode = ci.getMode();
if (mode==IJ.COMPOSITE)
ci.setMode(IJ.COLOR);
ImageStack stack = new ImageStack(imp.getWidth(), imp.getHeight());
for (int c=1; c<=channels; c++) {
imp.setPosition(c, imp.getSlice(), imp.getFrame());
Image img = imp.getImage();
stack.addSlice(null, new ColorProcessor(img));
}
if (ci.getMode()!=mode)
ci.setMode(mode);
imp.setPosition(channel, imp.getSlice(), imp.getFrame());
Calibration cal = imp.getCalibration();
imp = new ImagePlus(imp.getTitle(), stack);
imp.setCalibration(cal);
}
makeMontage(imp);
imp.updateImage();
saveID = imp.getID();
IJ.register(MontageMaker.class);
}
public void makeMontage(ImagePlus imp) {
int nSlices = imp.getStackSize();
if (hyperstack) {
nSlices = imp.getNSlices();
if (nSlices==1)
nSlices = imp.getNFrames();
}
boolean macro = Macro.getOptions()!=null;
if (macro || columns==0 || !(imp.getID()==saveID || nSlices==saveStackSize)) {
columns = (int)Math.sqrt(nSlices);
rows = columns;
int n = nSlices - columns*rows;
if (n>0) columns += (int)Math.ceil((double)n/rows);
scale = 1.0;
if (imp.getWidth()*columns>800)
scale = 0.5;
if (imp.getWidth()*columns>1600)
scale = 0.25;
inc = 1;
first = 1;
last = nSlices;
}
if (macro) {
fontSize = defaultFontSize;
borderWidth = 0;
label = false;
useForegroundColor = false;
}
saveStackSize = nSlices;
GenericDialog gd = new GenericDialog("Make Montage");
gd.addNumericField("Columns:", columns, 0);
gd.addNumericField("Rows:", rows, 0);
gd.addNumericField("Scale factor:", scale, 2);
if (!hyperstack) {
gd.addNumericField("First slice:", first, 0);
gd.addNumericField("Last slice:", last, 0);
}
gd.addNumericField("Increment:", inc, 0);
gd.addNumericField("Border width:", borderWidth, 0);
gd.addNumericField("Font size:", fontSize, 0);
gd.addCheckbox("Label slices", label);
gd.addCheckbox("Use foreground color", useForegroundColor);
gd.showDialog();
if (gd.wasCanceled())
return;
columns = (int)gd.getNextNumber();
rows = (int)gd.getNextNumber();
scale = gd.getNextNumber();
gd.setSmartRecording(true);
if (!hyperstack) {
first = (int)gd.getNextNumber();
last = (int)gd.getNextNumber();
}
inc = (int)gd.getNextNumber();
borderWidth = (int)gd.getNextNumber();
fontSize = (int)gd.getNextNumber();
if (borderWidth<0) borderWidth = 0;
if (first<1) first = 1;
if (last>nSlices) last = nSlices;
if (first>last)
{first=1; last=nSlices;}
if (inc<1) inc = 1;
if (gd.invalidNumber()) {
error("Invalid number");
return;
}
label = gd.getNextBoolean();
useForegroundColor = gd.getNextBoolean();
ImagePlus imp2 = null;
if (hyperstack)
imp2 = makeHyperstackMontage(imp, columns, rows, scale, inc, borderWidth, label);
else
imp2 = makeMontage2(imp, columns, rows, scale, first, last, inc, borderWidth, label);
if (imp2!=null)
imp2.show();
if (macro) {
fontSize = defaultFontSize;
borderWidth = 0;
label = false;
useForegroundColor = false;
columns = 0;
}
}
public void makeMontage(ImagePlus imp, int columns, int rows, double scale, int first, int last, int inc, int borderWidth, boolean labels) {
ImagePlus imp2 = makeMontage2(imp, columns, rows, scale, first, last, inc, borderWidth, labels);
imp2.show();
}
public ImagePlus makeMontage2(ImagePlus imp, int columns, int rows, double scale, int first, int last, int inc, int borderWidth, boolean labels) {
int stackWidth = imp.getWidth();
int stackHeight = imp.getHeight();
int nSlices = imp.getStackSize();
int width = (int)(stackWidth*scale);
int height = (int)(stackHeight*scale);
int montageWidth = width*columns + borderWidth*(columns-1);
int montageHeight = height*rows + borderWidth*(rows-1);
ImageProcessor ip = imp.getProcessor();
ImageProcessor montage = ip.createProcessor(montageWidth, montageHeight);
ImagePlus imp2 = new ImagePlus("Montage", montage);
imp2.setCalibration(imp.getCalibration());
montage = imp2.getProcessor();
Color fgColor=Color.white;
Color bgColor = Color.black;
if (useForegroundColor) {
fgColor = Toolbar.getForegroundColor();
bgColor = Toolbar.getBackgroundColor();
} else {
boolean whiteBackground = false;
if ((ip instanceof ByteProcessor) || (ip instanceof ColorProcessor)) {
ip.setRoi(0, stackHeight-12, stackWidth, 12);
ImageStatistics stats = ImageStatistics.getStatistics(ip, Measurements.MODE, null);
ip.resetRoi();
whiteBackground = stats.mode>=200;
if (imp.isInvertedLut())
whiteBackground = !whiteBackground;
}
if (whiteBackground) {
fgColor=Color.black;
bgColor = Color.white;
}
}
if (Double.isNaN(Toolbar.getBackgroundValue()))
montage.setColor(bgColor);
else
montage.setGlobalBackgroundColor();
montage.fill();
if (Double.isNaN(Toolbar.getForegroundValue()))
montage.setColor(fgColor);
else
montage.setGlobalForegroundColor();
montage.setFont(new Font("SansSerif", Font.PLAIN, fontSize));
montage.setAntialiasedText(true);
ImageStack stack = imp.getStack();
int x = 0;
int y = 0;
ImageProcessor aSlice;
int slice = first;
while (slice<=last) {
aSlice = stack.getProcessor(slice);
if (scale!=1.0) {
aSlice.setInterpolationMethod(ImageProcessor.BILINEAR);
boolean averageWhenDownSizing = width<200;
aSlice = aSlice.resize(width, height, averageWhenDownSizing);
}
montage.insert(aSlice, x, y);
String label = stack.getShortSliceLabel(slice);
if (labels)
drawLabel(montage, slice, label, x, y, width, height, borderWidth);
x += width + borderWidth;
if (x>=montageWidth) {
x = 0;
y += height + borderWidth;;
if (y>=montageHeight)
break;
}
IJ.showProgress((double)(slice-first)/(last-first));
slice += inc;
}
if (borderWidth>0) {
for (x=width; x<montageWidth; x+=width+borderWidth) {
montage.setRoi(x, 0, borderWidth, montageHeight);
montage.fill();
}
for (y=height; y<montageHeight; y+=height+borderWidth) {
montage.setRoi(0, y, montageWidth, borderWidth);
montage.fill();
}
}
IJ.showProgress(1.0);
Calibration cal = imp2.getCalibration();
if (cal.scaled()) {
cal.pixelWidth /= scale;
cal.pixelHeight /= scale;
}
imp2.setProperty("Info", "xMontage="+columns+"\nyMontage="+rows+"\n");
return imp2;
}
private ImagePlus makeHyperstackMontage(ImagePlus imp, int columns, int rows, double scale, int inc, int borderWidth, boolean labels) {
ImagePlus[] channels = ChannelSplitter.split(imp);
int n = channels.length;
ImagePlus[] montages = new ImagePlus[n];
for (int i=0; i<n; i++) {
int last = channels[i].getStackSize();
montages[i] = makeMontage2(channels[i], columns, rows, scale, 1, last, inc, borderWidth, labels);
}
ImagePlus montage = (new RGBStackMerge()).mergeHyperstacks(montages, false);
montage.setCalibration(montages[0].getCalibration());
montage.setTitle("Montage");
return montage;
}
private void error(String msg) {
IJ.error("Make Montage", msg);
}
void drawLabel(ImageProcessor montage, int slice, String label, int x, int y, int width, int height, int borderWidth) {
if (label!=null && !label.equals("") && montage.getStringWidth(label)>=width) {
do {
label = label.substring(0, label.length()-1);
} while (label.length()>1 && montage.getStringWidth(label)>=width);
}
if (label==null || label.equals(""))
label = ""+slice;
int swidth = montage.getStringWidth(label);
x += width/2 - swidth/2;
y -= borderWidth/2;
y += height;
montage.drawString(label, x, y);
}
public void setFontSize(int fontSize) {
this.fontSize = fontSize;
}
}