package ij.plugin; import ij.*; import ij.gui.*; import ij.process.*; import ij.measure.*; import ij.util.Tools; import java.awt.*; import java.awt.event.*; import java.util.*; /** This plugin implements the Edit/Crop and Image/Adjust/Size commands. */ public class Resizer implements PlugIn, TextListener, ItemListener { public static final int IN_PLACE=16, SCALE_T=32; private static int newWidth; private static int newHeight; private static boolean constrain = true; private static boolean averageWhenDownsizing = true; private static int interpolationMethod = ImageProcessor.BILINEAR; private String[] methods = ImageProcessor.getInterpolationMethods(); private Vector fields, checkboxes; private double origWidth, origHeight; private boolean sizeToHeight; public void run(String arg) { boolean crop = arg.equals("crop"); ImagePlus imp = IJ.getImage(); ImageProcessor ip = imp.getProcessor(); Roi roi = imp.getRoi(); if (roi==null && crop) { IJ.error("Crop", "Area selection required"); return; } if (roi!=null && roi.isLine()) { IJ.error("The Crop and Adjust>Size commands\ndo not work with line selections."); return; } Rectangle r = ip.getRoi(); origWidth = r.width;; origHeight = r.height; sizeToHeight=false; boolean restoreRoi = crop && roi!=null && roi.getType()!=Roi.RECTANGLE; if (roi!=null) { Rectangle b = roi.getBounds(); int w = ip.getWidth(); int h = ip.getHeight(); if (b.x<0 || b.y<0 || b.x+b.width>w || b.y+b.height>h) { ShapeRoi shape1 = new ShapeRoi(roi); ShapeRoi shape2 = new ShapeRoi(new Roi(0, 0, w, h)); roi = shape2.and(shape1); if (restoreRoi) imp.setRoi(roi); } } int stackSize= imp.getStackSize(); int z1 = imp.getStackSize(); int t1 = 0; int z2=0, t2=0; int saveMethod = interpolationMethod; if (crop) { Rectangle bounds = roi.getBounds(); newWidth = bounds.width; newHeight = bounds.height; interpolationMethod = ImageProcessor.NONE; } else { if (newWidth==0 || newHeight==0) { newWidth = (int)origWidth/2; newHeight = (int)origHeight/2; } if (constrain) newHeight = (int)(newWidth*(origHeight/origWidth)); if (stackSize>1) { newWidth = (int)origWidth; newHeight = (int)origHeight; } GenericDialog gd = new GenericDialog("Resize", IJ.getInstance()); gd.addNumericField("Width (pixels):", newWidth, 0); gd.addNumericField("Height (pixels):", newHeight, 0); if (imp.isHyperStack()) { z1 = imp.getNSlices(); t1 = imp.getNFrames(); } if (z1>1 && z1==stackSize) gd.addNumericField("Depth (images):", z1, 0); else if (z1>1 && z1<stackSize) gd.addNumericField("Depth (slices):", z1, 0); if (t1>1) gd.addNumericField("Time (frames):", t1, 0); gd.addCheckbox("Constrain aspect ratio", constrain); gd.addCheckbox("Average when downsizing", averageWhenDownsizing); gd.addChoice("Interpolation:", methods, methods[interpolationMethod]); fields = gd.getNumericFields(); for (int i=0; i<2; i++) ((TextField)fields.elementAt(i)).addTextListener(this); checkboxes = gd.getCheckboxes(); ((Checkbox)checkboxes.elementAt(0)).addItemListener(this); gd.showDialog(); if (gd.wasCanceled()) return; newWidth = (int)gd.getNextNumber(); newHeight = (int)gd.getNextNumber(); if (z1>1) z2 = (int)gd.getNextNumber(); if (t1>1) t2 = (int)gd.getNextNumber(); if (gd.invalidNumber()) { IJ.error("Width or height are invalid."); return; } constrain = gd.getNextBoolean(); averageWhenDownsizing = gd.getNextBoolean(); interpolationMethod = gd.getNextChoiceIndex(); if (constrain && newWidth==0) sizeToHeight = true; if (newWidth<=0.0 && !constrain) newWidth = 50; if (newHeight<=0.0) newHeight = 50; } if (!crop && constrain) { if (sizeToHeight) newWidth = (int)Math.round(newHeight*(origWidth/origHeight)); else newHeight = (int)Math.round(newWidth*(origHeight/origWidth)); } if (ip.getWidth()==1 || ip.getHeight()==1) ip.setInterpolationMethod(ImageProcessor.NONE); else ip.setInterpolationMethod(interpolationMethod); if (!crop && stackSize==1) Undo.setup(Undo.TYPE_CONVERSION, imp); if (roi!=null || newWidth!=origWidth || newHeight!=origHeight) { try { StackProcessor sp = new StackProcessor(imp.getStack(), ip); ImageStack s2 = sp.resize(newWidth, newHeight, averageWhenDownsizing); int newSize = s2.getSize(); if (s2.getWidth()>0 && newSize>0) { if (restoreRoi) imp.killRoi(); Calibration cal = imp.getCalibration(); if (cal.scaled()) { cal.pixelWidth *= origWidth/newWidth; cal.pixelHeight *= origHeight/newHeight; } if (crop&&roi!=null&&(cal.xOrigin!=0.0||cal.yOrigin!=0.0)) { cal.xOrigin -= roi.getBounds().x; cal.yOrigin -= roi.getBounds().y; } imp.setStack(null, s2); if (restoreRoi && roi!=null) { roi.setLocation(0, 0); imp.setRoi(roi); imp.draw(); } } if (stackSize>1 && newSize<stackSize) IJ.error("ImageJ ran out of memory causing \nthe last "+(stackSize-newSize)+" slices to be lost."); } catch(OutOfMemoryError o) { IJ.outOfMemory("Resize"); } imp.changes = true; if (crop) interpolationMethod = saveMethod; } ImagePlus imp2 = null; if (z2>0 && z2!=z1) imp2 = zScale(imp, z2, interpolationMethod+IN_PLACE); if (t2>0 && t2!=t1) imp2 = zScale(imp2!=null?imp2:imp, t2, interpolationMethod+IN_PLACE+SCALE_T); if (imp2!=null && imp2!=imp) { imp.changes = false; imp.close(); imp2.show(); } } public ImagePlus zScale(ImagePlus imp, int newDepth, int interpolationMethod) { ImagePlus imp2 = null; if (imp.isHyperStack()) imp2 = zScaleHyperstack(imp, newDepth, interpolationMethod); else { boolean inPlace = (interpolationMethod&IN_PLACE)!=0; interpolationMethod = interpolationMethod&15; int stackSize = imp.getStackSize(); int bitDepth = imp.getBitDepth(); if (newDepth<=stackSize/2 && interpolationMethod==ImageProcessor.NONE) imp2 = shrinkZ(imp, newDepth, inPlace); else imp2 = resizeZ(imp, newDepth, interpolationMethod); if (imp2==null) return null; ImageProcessor ip = imp.getProcessor(); double min = ip.getMin(); double max = ip.getMax(); if (bitDepth==16||bitDepth==32) imp2.getProcessor().setMinAndMax(min, max); } if (imp2==null) return null; if (imp2!=imp && imp.isComposite()) { imp2 = new CompositeImage(imp2, ((CompositeImage)imp).getMode()); ((CompositeImage)imp2).copyLuts(imp); } imp2.setCalibration(imp.getCalibration()); Calibration cal = imp2.getCalibration(); if (cal.scaled()) cal.pixelDepth *= (double)imp.getNSlices()/imp2.getNSlices(); Object info = imp.getProperty("Info"); if (info!=null) imp2.setProperty("Info", info); if (imp.isHyperStack()) imp2.setOpenAsHyperStack(imp.isHyperStack()); return imp2; } private ImagePlus zScaleHyperstack(ImagePlus imp, int depth2, int interpolationMethod) { boolean inPlace = (interpolationMethod&IN_PLACE)!=0; boolean scaleT = (interpolationMethod&SCALE_T)!=0; interpolationMethod = interpolationMethod&15; int channels = imp.getNChannels(); int slices = imp.getNSlices(); int frames = imp.getNFrames(); int slices2 = slices; int frames2 = frames; int bitDepth = imp.getBitDepth(); if (slices==1 && frames>1) scaleT = true; if (scaleT) frames2 = depth2; else slices2 = depth2; double scale = (double)(depth2-1)/slices; if (scaleT) scale = (double)(depth2-1)/frames; if (scale<=0.5 && interpolationMethod==ImageProcessor.NONE) return shrinkHyperstack(imp, depth2, inPlace, scaleT); ImageStack stack1 = imp.getStack(); int width = stack1.getWidth(); int height = stack1.getHeight(); ImagePlus imp2 = IJ.createImage(imp.getTitle(), bitDepth+"-bit", width, height, channels*slices2*frames2); if (imp2==null) return null; imp2.setDimensions(channels, slices2, frames2); ImageStack stack2 = imp2.getStack(); ImageProcessor ip = imp.getProcessor(); int count = 0; if (scaleT) { IJ.showStatus("T Scaling..."); ImageProcessor xtPlane1 = ip.createProcessor(width, frames); xtPlane1.setInterpolationMethod(interpolationMethod); ImageProcessor xtPlane2; Object xtpixels1 = xtPlane1.getPixels(); int last = slices*channels*height-1; for (int z=1; z<=slices; z++) { for (int c=1; c<=channels; c++) { for (int y=0; y<height; y++) { IJ.showProgress(count++, last); for (int t=1; t<=frames; t++) { int index = imp.getStackIndex(c, z, t); //IJ.log("1: "+c+" "+z+" "+t+" "+index+" "+xzPlane1); Object pixels1 = stack1.getPixels(index); System.arraycopy(pixels1, y*width, xtpixels1, (t-1)*width, width); } xtPlane2 = xtPlane1.resize(width, depth2, averageWhenDownsizing); Object xtpixels2 = xtPlane2.getPixels(); for (int t=1; t<=frames2; t++) { int index = imp2.getStackIndex(c, z, t); //IJ.log("2: "+c+" "+z+" "+t+" "+index+" "+xzPlane2); Object pixels2 = stack2.getPixels(index); System.arraycopy(xtpixels2, (t-1)*width, pixels2, y*width, width); } } } } } else { IJ.showStatus("Z Scaling..."); ImageProcessor xzPlane1 = ip.createProcessor(width, slices); xzPlane1.setInterpolationMethod(interpolationMethod); ImageProcessor xzPlane2; Object xypixels1 = xzPlane1.getPixels(); int last = frames*channels*height-1; for (int t=1; t<=frames; t++) { for (int c=1; c<=channels; c++) { for (int y=0; y<height; y++) { IJ.showProgress(count++, last); for (int z=1; z<=slices; z++) { int index = imp.getStackIndex(c, z, t); Object pixels1 = stack1.getPixels(index); System.arraycopy(pixels1, y*width, xypixels1, (z-1)*width, width); } xzPlane2 = xzPlane1.resize(width, depth2, averageWhenDownsizing); Object xypixels2 = xzPlane2.getPixels(); for (int z=1; z<=slices2; z++) { int index = imp2.getStackIndex(c, z, t); Object pixels2 = stack2.getPixels(index); System.arraycopy(xypixels2, (z-1)*width, pixels2, y*width, width); } } } } } imp2.setDimensions(channels, slices2, frames2); return imp2; } private ImagePlus shrinkHyperstack(ImagePlus imp, int newDepth, boolean inPlace, boolean scaleT) { int channels = imp.getNChannels(); int slices = imp.getNSlices(); int frames = imp.getNFrames(); int factor = (int)Math.round((double)slices/newDepth); if (scaleT) factor = frames/newDepth; int zfactor = scaleT?1:factor; int tfactor = scaleT?factor:1; ImageStack stack = imp.getStack(); ImageStack stack2 = new ImageStack(imp.getWidth(), imp.getHeight()); boolean virtual = stack.isVirtual(); int slices2 = slices/zfactor + ((slices%zfactor)!=0?1:0); int frames2 = frames/tfactor + ((frames%tfactor)!=0?1:0); int n = channels*slices2*frames2; int count = 1; for (int t=1; t<=frames; t+=tfactor) { for (int z=1; z<=slices; z+=zfactor) { for (int c=1; c<=channels; c++) { int i = imp.getStackIndex(c, z, t); IJ.showProgress(i, n); ImageProcessor ip = stack.getProcessor(imp.getStackIndex(c, z, t)); if (!inPlace) ip=ip.duplicate(); //IJ.log(count++ +" "+i+" "+c+" "+z+" "+t); stack2.addSlice(stack.getSliceLabel(i), ip); } } } ImagePlus imp2 = new ImagePlus(imp.getTitle(), stack2); imp2.setDimensions(channels, slices2, frames2); IJ.showProgress(1.0); return imp2; } private ImagePlus shrinkZ(ImagePlus imp, int newDepth, boolean inPlace) { ImageStack stack = imp.getStack(); int factor = imp.getStackSize()/newDepth; boolean virtual = stack.isVirtual(); int n = stack.getSize(); ImageStack stack2 = new ImageStack(stack.getWidth(), stack.getHeight()); for (int i=1; i<=n; i+=factor) { if (virtual) IJ.showProgress(i, n); ImageProcessor ip2 = stack.getProcessor(i); if (!inPlace) ip2 = ip2.duplicate(); stack2.addSlice(stack.getSliceLabel(i), ip2); } return new ImagePlus(imp.getTitle(), stack2); } private ImagePlus resizeZ(ImagePlus imp, int newDepth, int interpolationMethod) { ImageStack stack1 = imp.getStack(); int width = stack1.getWidth(); int height = stack1.getHeight(); int depth = stack1.getSize(); int bitDepth = imp.getBitDepth(); ImagePlus imp2 = IJ.createImage(imp.getTitle(), bitDepth+"-bit", width, height, newDepth); if (imp2==null) return null; ImageStack stack2 = imp2.getStack(); ImageProcessor ip = imp.getProcessor(); ImageProcessor xzPlane1 = ip.createProcessor(width, depth); xzPlane1.setInterpolationMethod(interpolationMethod); ImageProcessor xzPlane2; Object xypixels1 = xzPlane1.getPixels(); IJ.showStatus("Z Scaling..."); for (int y=0; y<height; y++) { IJ.showProgress(y, height-1); for (int z=0; z<depth; z++) { Object pixels1 = stack1.getPixels(z+1); System.arraycopy(pixels1, y*width, xypixels1, z*width, width); } xzPlane2 = xzPlane1.resize(width, newDepth, averageWhenDownsizing); Object xypixels2 = xzPlane2.getPixels(); for (int z=0; z<newDepth; z++) { Object pixels2 = stack2.getPixels(z+1); System.arraycopy(xypixels2, z*width, pixels2, y*width, width); } } return imp2; } public void textValueChanged(TextEvent e) { TextField widthField = (TextField)fields.elementAt(0); TextField heightField = (TextField)fields.elementAt(1); int width = (int)Tools.parseDouble(widthField.getText(),-99); int height = (int)Tools.parseDouble(heightField.getText(),-99); if (width==-99 || height==-99) return; if (constrain) { if (width!=newWidth) { sizeToHeight = false; newWidth = width; updateFields(); } else if (height!=newHeight) { sizeToHeight = true; newHeight = height; updateFields(); } } } void updateFields() { if (sizeToHeight) { newWidth = (int)(newHeight*(origWidth/origHeight)); TextField widthField = (TextField)fields.elementAt(0); widthField.setText(""+newWidth); } else { newHeight = (int)(newWidth*(origHeight/origWidth)); TextField heightField = (TextField)fields.elementAt(1); heightField.setText(""+newHeight); } } public void itemStateChanged(ItemEvent e) { Checkbox cb = (Checkbox)checkboxes.elementAt(0); boolean newConstrain = cb.getState(); if (newConstrain && newConstrain!=constrain) updateFields(); constrain = newConstrain; } }