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.*; /** Implements the Image/Stacks/Make Montage command. */ 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 int fontSize = 12; 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==CompositeImage.COMPOSITE) ci.setMode(CompositeImage.COLOR); ImageStack stack = new ImageStack(imp.getWidth(), imp.getHeight()); for (int c=1; c<=channels; c++) { imp.setPositionWithoutUpdate(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()); imp = new ImagePlus(imp.getTitle(), stack); } 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(); } if (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; } saveStackSize = nSlices; GenericDialog gd = new GenericDialog("Make Montage", IJ.getInstance()); 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(); 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(); } /** Creates a montage and displays it. */ 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(); } /** Creates a montage and returns it as an ImagePlus. */ 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; int montageHeight = height*rows; ImageProcessor ip = imp.getProcessor(); ImageProcessor montage = ip.createProcessor(montageWidth+borderWidth/2, montageHeight+borderWidth/2); 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; } } montage.setColor(bgColor); montage.fill(); montage.setColor(fgColor); Dimension screen = IJ.getScreenSize(); 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 = aSlice.resize(width, height); montage.insert(aSlice, x, y); String label = stack.getShortSliceLabel(slice); if (borderWidth>0) drawBorder(montage, x, y, width, height, borderWidth); if (labels) drawLabel(montage, slice, label, x, y, width, height, borderWidth); x += width; if (x>=montageWidth) { x = 0; y += height; if (y>=montageHeight) break; } IJ.showProgress((double)(slice-first)/(last-first)); slice += inc; } if (borderWidth>0) { int w2 = borderWidth/2; drawBorder(montage, w2, w2, montageWidth-w2, montageHeight-w2, borderWidth); } 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; } /** Creates a hyperstack montage and returns it as an ImagePlus. */ 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.setTitle("Montage"); return montage; } private void error(String msg) { IJ.error("Make Montage", msg); } void drawBorder(ImageProcessor montage, int x, int y, int width, int height, int borderWidth) { montage.setLineWidth(borderWidth); montage.moveTo(x, y); montage.lineTo(x+width, y); montage.lineTo(x+width, y+height); montage.lineTo(x, y+height); montage.lineTo(x, y); } 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); } }