package ij.plugin; import ij.*; import ij.process.*; import ij.gui.*; import ij.measure.*; import ij.util.Tools; import java.awt.*; import java.awt.event.*; import java.util.*; /** Implements the Image/Stacks/Reslice command. Known shortcomings: for FREELINE or POLYLINE ROI, spatial calibration is ignored: the image is sampled at constant _pixel_ increments (distance 1), so (if y/x aspect ratio != 1 in source image) one dimension in the output is not homogeneous (i.e. pixelWidth not the same everywhere). */ public class Slicer implements PlugIn, TextListener, ItemListener { private static final String[] starts = {"Top", "Left", "Bottom", "Right"}; private static String startAtS = starts[0]; private static boolean rotateS; private static boolean flipS; private static int sliceCountS = 1; private String startAt = starts[0]; private boolean rotate; private boolean flip; private int sliceCount = 1; private boolean nointerpolate = Prefs.avoidResliceInterpolation; private double inputZSpacing = 1.0; private double outputZSpacing = 1.0; private int outputSlices = 1; private boolean noRoi; private boolean rgb, notFloat; private Vector fields, checkboxes; private Label message; private ImagePlus imp; private double gx1, gy1, gx2, gy2, gLength; private Color lineColor = new Color(1f, 1f, 0f, 0.4f); // Variables used by getIrregularProfile and doIrregularSetup private int n; private double[] x; private double[] y; private int xbase; private int ybase; private double length; private double[] segmentLengths; private double[] dx; private double[] dy; public void run(String arg) { imp = WindowManager.getCurrentImage(); if (imp==null) { IJ.noImage(); return; } int stackSize = imp.getStackSize(); Roi roi = imp.getRoi(); int roiType = roi!=null?roi.getType():0; // stack required except for ROI = none or RECT if (stackSize<2 && roi!=null && roiType!=Roi.RECTANGLE) { IJ.error("Reslice...", "Stack required"); return; } // permissible ROI types: none,RECT,*LINE if (roi!=null && roiType!=Roi.RECTANGLE && roiType!=Roi.LINE && roiType!=Roi.POLYLINE && roiType!=Roi.FREELINE) { IJ.error("Reslice...", "Line or rectangular selection required"); return; } if (!showDialog(imp)) return; long startTime = System.currentTimeMillis(); ImagePlus imp2 = null; rgb = imp.getType()==ImagePlus.COLOR_RGB; notFloat = !rgb && imp.getType()!=ImagePlus.GRAY32; if (imp.isHyperStack()) imp2 = resliceHyperstack(imp); else imp2 = reslice(imp); if (imp2==null) return; ImageProcessor ip = imp.getProcessor(); double min = ip.getMin(); double max = ip.getMax(); if (!rgb) imp2.getProcessor().setMinAndMax(min, max); imp2.show(); if (noRoi) imp.deleteRoi(); else imp.draw(); IJ.showStatus(IJ.d2s(((System.currentTimeMillis()-startTime)/1000.0),2)+" seconds"); } public ImagePlus reslice(ImagePlus imp) { ImagePlus imp2; Roi roi = imp.getRoi(); int roiType = roi!=null?roi.getType():0; Calibration origCal = imp.getCalibration(); boolean globalCalibration = false; if (nointerpolate) {// temporarily clear spatial calibration globalCalibration = imp.getGlobalCalibration()!=null; imp.setGlobalCalibration(null); Calibration tmpCal = origCal.copy(); tmpCal.pixelWidth = 1.0; tmpCal.pixelHeight = 1.0; tmpCal.pixelDepth = 1.0; imp.setCalibration(tmpCal); inputZSpacing = 1.0; if (roiType!=Roi.LINE) outputZSpacing = 1.0; } double zSpacing = inputZSpacing/imp.getCalibration().pixelWidth; if (roi==null || roiType==Roi.RECTANGLE || roiType==Roi.LINE) { imp2 = resliceRectOrLine(imp); } else {// we assert roiType==Roi.POLYLINE || roiType==Roi.FREELINE String status = imp.getStack().isVirtual()?"":null; IJ.showStatus("Reslice..."); ImageProcessor ip2 = getSlice(imp, 0.0, 0.0, 0.0, 0.0, status); imp2 = new ImagePlus("Reslice of "+imp.getShortTitle(), ip2); } if (nointerpolate) { // restore calibration if (globalCalibration) imp.setGlobalCalibration(origCal); imp.setCalibration(origCal); } // create Calibration for new stack // start from previous cal and swap appropriate fields boolean horizontal = false; boolean vertical = false; if (roi==null || roiType==Roi.RECTANGLE) { if (startAt.equals(starts[0]) || startAt.equals(starts[2])) horizontal = true; else vertical = true; } if (roi!=null && roiType==Roi.LINE) { Line l = (Line)roi; horizontal = (l.y2-l.y1)==0; vertical = (l.x2-l.x1)==0; } if (imp2==null) return null; imp2.setCalibration(imp.getCalibration()); Calibration cal = imp2.getCalibration(); if (horizontal) { cal.pixelWidth = origCal.pixelWidth; cal.pixelHeight = origCal.pixelDepth/zSpacing; cal.pixelDepth = origCal.pixelHeight*outputZSpacing; } else if (vertical) { cal.pixelWidth = origCal.pixelHeight; cal.pixelHeight = origCal.pixelDepth/zSpacing; //cal.pixelWidth = origCal.pixelDepth/zSpacing; //cal.pixelHeight = origCal.pixelHeight; cal.pixelDepth = origCal.pixelWidth*outputZSpacing;; } else { // oblique line, polyLine or freeline if (origCal.pixelHeight==origCal.pixelWidth) { cal.pixelWidth = origCal.pixelWidth; cal.pixelHeight=origCal.pixelDepth/zSpacing; cal.pixelDepth = origCal.pixelWidth*outputZSpacing; } else { cal.pixelWidth = cal.pixelHeight=cal.pixelDepth=1.0; cal.setUnit("pixel"); } } double tmp; if (rotate) {// if rotated flip X and Y tmp = cal.pixelWidth; cal.pixelWidth = cal.pixelHeight; cal.pixelHeight = tmp; } return imp2; } ImagePlus resliceHyperstack(ImagePlus imp) { int channels = imp.getNChannels(); int slices = imp.getNSlices(); int frames = imp.getNFrames(); if (slices==1) return resliceTimeLapseHyperstack(imp); int c1 = imp.getChannel(); int z1 = imp.getSlice(); int t1 = imp.getFrame(); int width = imp.getWidth(); int height = imp.getHeight(); ImagePlus imp2 = null; ImageStack stack2 = null; Roi roi = imp.getRoi(); for (int t=1; t<=frames; t++) { for (int c=1; c<=channels; c++) { ImageStack tmp1Stack = new ImageStack(width, height); for (int z=1; z<=slices; z++) { imp.setPositionWithoutUpdate(c, z, t); tmp1Stack.addSlice(null, imp.getProcessor()); } ImagePlus tmp1 = new ImagePlus("tmp", tmp1Stack); tmp1.setCalibration(imp.getCalibration()); tmp1.setRoi(roi); ImagePlus tmp2 = reslice(tmp1); int slices2 = tmp2.getStackSize(); if (imp2==null) { imp2 = tmp2.createHyperStack("Reslice of "+imp.getTitle(), channels, slices2, frames, tmp2.getBitDepth()); stack2 = imp2.getStack(); } ImageStack tmp2Stack = tmp2.getStack(); for (int z=1; z<=slices2; z++) { imp.setPositionWithoutUpdate(c, z, t); int n2 = imp2.getStackIndex(c, z, t); stack2.setPixels(tmp2Stack.getPixels(z), n2); } } } imp.setPosition(c1, z1, t1); if (channels>1 && imp.isComposite()) { imp2 = new CompositeImage(imp2, ((CompositeImage)imp).getMode()); ((CompositeImage)imp2).copyLuts(imp); } return imp2; } ImagePlus resliceTimeLapseHyperstack(ImagePlus imp) { int channels = imp.getNChannels(); int frames = imp.getNFrames(); int c1 = imp.getChannel(); int t1 = imp.getFrame(); int width = imp.getWidth(); int height = imp.getHeight(); ImagePlus imp2 = null; ImageStack stack2 = null; Roi roi = imp.getRoi(); int z = 1; for (int c=1; c<=channels; c++) { ImageStack tmp1Stack = new ImageStack(width, height); for (int t=1; t<=frames; t++) { imp.setPositionWithoutUpdate(c, z, t); tmp1Stack.addSlice(null, imp.getProcessor()); } ImagePlus tmp1 = new ImagePlus("tmp", tmp1Stack); tmp1.setCalibration(imp.getCalibration()); tmp1.setRoi(roi); ImagePlus tmp2 = reslice(tmp1); int frames2 = tmp2.getStackSize(); if (imp2==null) { imp2 = tmp2.createHyperStack("Reslice of "+imp.getTitle(), channels, 1, frames2, tmp2.getBitDepth()); stack2 = imp2.getStack(); } ImageStack tmp2Stack = tmp2.getStack(); for (int t=1; t<=frames2; t++) { imp.setPositionWithoutUpdate(c, z, t); int n2 = imp2.getStackIndex(c, z, t); stack2.setPixels(tmp2Stack.getPixels(z), n2); } } imp.setPosition(c1, 1, t1); if (channels>1 && imp.isComposite()) { imp2 = new CompositeImage(imp2, ((CompositeImage)imp).getMode()); ((CompositeImage)imp2).copyLuts(imp); } return imp2; } boolean showDialog(ImagePlus imp) { Calibration cal = imp.getCalibration(); if (cal.pixelDepth<0.0) cal.pixelDepth = -cal.pixelDepth; String units = cal.getUnits(); if (cal.pixelWidth==0.0) cal.pixelWidth = 1.0; inputZSpacing = cal.pixelDepth; double outputSpacing = cal.pixelDepth; Roi roi = imp.getRoi(); boolean line = roi!=null && roi.getType()==Roi.LINE; if (line) saveLineInfo(roi); String macroOptions = Macro.getOptions(); boolean macroRunning = macroOptions!=null; if (macroRunning) { if (macroOptions.indexOf("input=")!=-1) macroOptions = macroOptions.replaceAll("slice=", "slice_count="); macroOptions = macroOptions.replaceAll("slice=", "output="); Macro.setOptions(macroOptions); nointerpolate = false; } else { startAt = startAtS; rotate = rotateS; flip = flipS; sliceCount = sliceCountS; } GenericDialog gd = new GenericDialog("Reslice"); gd.addNumericField("Output spacing ("+units+"):", outputSpacing, 3); if (line) { if (!IJ.isMacro()) outputSlices=sliceCount; gd.addNumericField("Slice_count:", outputSlices, 0); } else gd.addChoice("Start at:", starts, startAt); gd.addCheckbox("Flip vertically", flip); gd.addCheckbox("Rotate 90 degrees", rotate); gd.addCheckbox("Avoid interpolation", nointerpolate); gd.setInsets(0, 32, 0); gd.addMessage("(use 1 pixel spacing)"); gd.setInsets(15, 0, 0); gd.addMessage("Voxel size: "+d2s(cal.pixelWidth)+"x"+d2s(cal.pixelHeight) +"x"+d2s(cal.pixelDepth)+" "+cal.getUnit()); gd.setInsets(5, 0, 0); gd.addMessage("Output size: "+getSize(cal.pixelDepth,outputSpacing,outputSlices)+" "); fields = gd.getNumericFields(); if (!macroRunning) { for (int i=0; iProperties correct?."); return null; } boolean virtualStack = imp.getStack().isVirtual(); String status = null; ImagePlus imp2 = null; ImageStack stack2 = null; boolean isStack = imp.getStackSize()>1; IJ.resetEscape(); boolean macro = IJ.isMacro(); for (int i=0; i1?(i+1)+"/"+outputSlices+", ":""; ImageProcessor ip = getSlice(imp, x1, y1, x2, y2, status); if (macro) IJ.showProgress(i,outputSlices-1); else drawLine(x1, y1, x2, y2, imp); if (stack2==null) { stack2 = createOutputStack(imp, ip); if (stack2==null || stack2.getSize()Movie (FFMPEG) bug imp.setSlice(flip?stackSize-i:i+1); ip = imp.getProcessor(); } if (roiType==Roi.POLYLINE || roiType==Roi.FREELINE) line = getIrregularProfile(roi, ip); else if (ortho) line = getOrthoLine(ip, (int)x1, (int)y1, (int)x2, (int)y2, line); else line = getLine(ip, x1, y1, x2, y2, line); if (rotate) { if (i==0) ip2 = ip.createProcessor(stackSize, line.length); putColumn(ip2, i, 0, line, line.length); } else { if (i==0) ip2 = ip.createProcessor(line.length, stackSize); putRow(ip2, 0, i, line, line.length); } if (status!=null) IJ.showStatus("Slicing: "+status +i+"/"+stackSize); } Calibration cal = imp.getCalibration(); double zSpacing = inputZSpacing/cal.pixelWidth; if (zSpacing!=1.0) { ip2.setInterpolate(true); if (rotate) ip2 = ip2.resize((int)(stackSize*zSpacing), line.length); else ip2 = ip2.resize(line.length, (int)(stackSize*zSpacing)); } return ip2; } public void putRow(ImageProcessor ip, int x, int y, float[] data, int length) { if (rgb) { for (int i=0; iw||y1<0||y1>h||x2<0||x2>w||y2<0||y2>h; int dx = x2-x1; int dy = y2-y1; int n = Math.max(Math.abs(dx), Math.abs(dy)); if (data==null) data = new float[n]; int xinc = dx/n; int yinc = dy/n; int width = ip.getWidth(); int height = ip.getHeight(); for (int i=0; i0&&x10&&y10) makePolygon(count, outSpacing); } String size = getSize(inputZSpacing, outSpacing, count); message.setText("Output Size: "+size); } String getSize(double inSpacing, double outSpacing, int count) { int size = getOutputStackSize(inSpacing, outSpacing, count); int mem = getAvailableMemory(); String available = mem!=-1?" ("+mem+"MB free)":""; if (message!=null) message.setForeground(mem!=-1&&size>mem?Color.red:Color.black); if (size>0) return size+"MB"+available; else return "<1MB"+available; } void makePolygon(int count, double outSpacing) { int[] x = new int[4]; int[] y = new int[4]; Calibration cal = imp.getCalibration(); double cx = cal.pixelWidth; //corrects preview for x calibration double cy = cal.pixelHeight; //corrects preview for y calibration x[0] = (int)gx1; y[0] = (int)gy1; x[1] = (int)gx2; y[1] = (int)gy2; double dx = gx2 - gx1; double dy = gy2 - gy1; double nrm = Math.sqrt(dx*dx + dy*dy)/outSpacing; double xInc = -(dy/(cx*nrm)); //cx scales the x increment double yInc = (dx/(cy*nrm)); //cy scales the y increment x[2] = x[1] + (int)(xInc*count); y[2] = y[1] + (int)(yInc*count); x[3] = x[0] + (int)(xInc*count); y[3] = y[0] + (int)(yInc*count); imp.setRoi(new PolygonRoi(x, y, 4, PolygonRoi.FREEROI)); } int getOutputStackSize(double inSpacing, double outSpacing, int count) { Roi roi = imp.getRoi(); int width = imp.getWidth(); int height = imp.getHeight(); if (roi!=null) { Rectangle r = roi.getBounds(); width = r.width; width = r.height; } int type = roi!=null?roi.getType():0; int stackSize = imp.getStackSize(); double size = 0.0; if (type==Roi.RECTANGLE) { size = width*height*stackSize; if (outSpacing>0&&!nointerpolate) size *= inSpacing/outSpacing; } else size = gLength*count*stackSize; int bits = imp.getBitDepth(); switch (bits) { case 16: size*=2; break; case 24: case 32: size*=4; break; } return (int)Math.round(size/1048576.0); } int getAvailableMemory() { long max = IJ.maxMemory(); if (max==0) return -1; long inUse = IJ.currentMemory(); long available = max - inUse; return (int)((available+524288L)/1048576L); } }