package ij.gui; import ij.*; import ij.process.*; import ij.measure.*; import ij.plugin.Colors; import ij.plugin.PointToolOptions; import ij.plugin.filter.Analyzer; import ij.plugin.frame.Recorder; import ij.util.Java2; import java.awt.*; import java.awt.image.*; import java.awt.event.KeyEvent; import java.util.*; import java.awt.geom.*; /** This class represents a collection of points that can be associated with counters. * @see PointProperties.js */ public class PointRoi extends PolygonRoi { public static final String[] sizes = {"Tiny", "Small", "Medium", "Large", "Extra Large"}; public static final String[] types = {"Hybrid", "Cross", "Dot", "Circle"}; private static final String TYPE_KEY = "point.type"; private static final String SIZE_KEY = "point.size"; private static final String CROSS_COLOR_KEY = "point.cross.color"; private static final int TINY=1, SMALL=3, MEDIUM=5, LARGE=7, EXTRA_LARGE=11; private static final int HYBRID=0, CROSS=1, CROSSHAIR=1, DOT=2, CIRCLE=3; private static final BasicStroke twoPixelsWide = new BasicStroke(2); private static final BasicStroke threePixelsWide = new BasicStroke(3); private static int defaultType = HYBRID; private static int defaultSize = SMALL; private static Font font; private static Color defaultCrossColor = Color.white; private static int fontSize = 9; public static final int MAX_COUNTERS = 100; private static String[] counterChoices; private static Color[] colors; private boolean showLabels; private int type = HYBRID; private int size = SMALL; private static int defaultCounter; private int counter; private int nCounters = 1; private short[] counters; private short[] positions; private int[] counts = new int[MAX_COUNTERS]; private ResultsTable rt; private long lastPointTime; private int[] counterInfo; private boolean promptBeforeDeleting; private boolean promptBeforeDeletingCalled; private int nMarkers; private boolean addToOverlay; static { setDefaultType((int)Prefs.get(TYPE_KEY, HYBRID)); setDefaultSize((int)Prefs.get(SIZE_KEY, 1)); } public PointRoi() { this(0.0, 0.0); deletePoint(0); } /** Creates a new PointRoi using the specified int arrays of offscreen coordinates. */ public PointRoi(int[] ox, int[] oy, int points) { super(itof(ox), itof(oy), points, POINT); width+=1; height+=1; } /** Creates a new PointRoi using the specified float arrays of offscreen coordinates. */ public PointRoi(float[] ox, float[] oy, int points) { super(ox, oy, points, POINT); width+=1; height+=1; } /** Creates a new PointRoi using the specified float arrays of offscreen coordinates. */ public PointRoi(float[] ox, float[] oy) { this(ox, oy, ox.length); } /** Creates a new PointRoi using the specified coordinate arrays and options. */ public PointRoi(float[] ox, float[] oy, String options) { this(ox, oy, ox.length); setOptions(options); } /** Creates a new PointRoi from a FloatPolygon. */ public PointRoi(FloatPolygon poly) { this(poly.xpoints, poly.ypoints, poly.npoints); } /** Creates a new PointRoi from a Polygon. */ public PointRoi(Polygon poly) { this(itof(poly.xpoints), itof(poly.ypoints), poly.npoints); } /** Creates a new PointRoi using the specified coordinates and options. */ public PointRoi(double ox, double oy, String options) { super(makeXArray(ox, null), makeYArray(oy, null), 1, POINT); width=1; height=1; incrementCounter(null); setOptions(options); } /** Creates a new PointRoi using the specified offscreen int coordinates. */ public PointRoi(int ox, int oy) { super(makeXArray(ox, null), makeYArray(oy, null), 1, POINT); width=1; height=1; incrementCounter(null); } /** Creates a new PointRoi using the specified offscreen double coordinates. */ public PointRoi(double ox, double oy) { super(makeXArray(ox, null), makeYArray(oy, null), 1, POINT); width=1; height=1; incrementCounter(null); } /** Creates a new PointRoi using the specified screen coordinates. */ public PointRoi(int sx, int sy, ImagePlus imp) { super(makeXArray(sx, imp), makeYArray(sy, imp), 1, POINT); defaultCounter = 0; setImage(imp); width=1; height=1; type = defaultType; size = defaultSize; showLabels = !Prefs.noPointLabels; if (imp!=null) { int r = 10; double mag = ic!=null?ic.getMagnification():1; if (mag<1) r = (int)(r/mag); imp.draw(x-r, y-r, 2*r, 2*r); } setCounter(Toolbar.getMultiPointMode()?defaultCounter:0); incrementCounter(imp); enlargeArrays(50); if (Recorder.record) { String add = Prefs.pointAddToOverlay?" add":""; String options = sizes[convertSizeToIndex(size)]+" "+Colors.colorToString(getColor())+" "+types[type]+add; options = options.toLowerCase(); if (Recorder.scriptMode()) Recorder.recordCall("imp.setRoi(new PointRoi("+x+","+y+",\""+options+"\"));"); else Recorder.record("makePoint", x, y, options); } } private void setOptions(String options) { if (options==null) return; if (options.contains("tiny")) size=TINY; else if (options.contains("medium")) size=MEDIUM; else if (options.contains("extra")) size=EXTRA_LARGE; else if (options.contains("large")) size=LARGE; if (options.contains("cross")) type=CROSS; else if (options.contains("dot")) type=DOT; else if (options.contains("circle")) type=CIRCLE; setStrokeColor(Colors.getColor(options,Roi.getColor())); addToOverlay = options.contains("add"); } static float[] itof(int[] arr) { if (arr==null) return null; int n = arr.length; float[] temp = new float[n]; for (int i=0; i1) { fontSize = 8; fontSize += convertSizeToIndex(size); if (fontSize>18) fontSize = 18; fontSize = (int)Math.round(fontSize); font = new Font("SansSerif", Font.PLAIN, fontSize); g.setFont(font); if (fontSize>9) Java2.setAntialiasedText(g, true); } int slice = imp!=null&&positions!=null&&imp.getStackSize()>1?imp.getCurrentSlice():0; ImageCanvas ic = imp!=null?imp.getCanvas():null; if (ic!=null && overlay && ic.getShowAllList()!=null && ic.getShowAllList().contains(this) && !Prefs.showAllSliceOnly) slice = 0; // draw point irrespective of currently selected slice if (Prefs.showAllPoints) slice = 0; //IJ.log("draw: "+positions+" "+imp.getCurrentSlice()); for (int i=0; i1.0) { saveXform = g2d.getTransform(); g2d.translate(x, y); g2d.scale(flattenScale, flattenScale); x = y = 0; } Color color = strokeColor!=null?strokeColor:ROIColor; if (!overlay && isActiveOverlayRoi()) { if (color==Color.cyan) color = Color.magenta; else color = Color.cyan; } if (nCounters>1 && counters!=null && n<=counters.length) color = getColor(counters[n-1]); if (type==HYBRID || type==CROSS) { if (type==HYBRID) g.setColor(Color.white); else { g.setColor(color); colorSet = true; } if (size>LARGE) g2d.setStroke(threePixelsWide); g.drawLine(x-(size+2), y, x+size+2, y); g.drawLine(x, y-(size+2), x, y+size+2); } if (type!=CROSS && size>SMALL) g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (type==HYBRID || type==DOT) { if (!colorSet) { g.setColor(color); colorSet = true; } if (size>LARGE) g2d.setStroke(onePixelWide); if (size>LARGE && type==DOT) g.fillOval(x-size2, y-size2, size, size); else if (size>LARGE && type==HYBRID) g.fillRect(x-(size2-2), y-(size2-2), size-4, size-4); else if (size>SMALL && type==HYBRID) g.fillRect(x-(size2-1), y-(size2-1), size-2, size-2); else g.fillRect(x-size2, y-size2, size, size); } if (showLabels && nPoints>1) { int offset = (int)Math.round(0.4); if (offset<1) offset=1; offset++; if (nCounters==1) { if (!colorSet) g.setColor(color); g.drawString(""+n, x+offset, y+offset+fontSize); } else if (counters!=null) { g.setColor(getColor(counters[n-1])); g.drawString(""+counters[n-1], x+offset, y+offset+fontSize); } } if ((size>TINY||type==DOT) && (type==HYBRID||type==DOT)) { g.setColor(Color.black); if (size>LARGE && type==HYBRID) g.drawOval(x-(size2-1), y-(size2-1), size-3, size-3); else if (size>SMALL && type==HYBRID) g.drawOval(x-size2, y-size2, size-1, size-1); else g.drawOval(x-(size2+1), y-(size2+1), size+1, size+1); } if (type==CIRCLE) { int scaledSize = (int)Math.round(size+1); g.setColor(color); if (size>LARGE) g2d.setStroke(twoPixelsWide); g.drawOval(x-scaledSize/2, y-scaledSize/2, scaledSize, scaledSize); } if (saveXform!=null) g2d.setTransform(saveXform); } public void drawPixels(ImageProcessor ip) { ip.setLineWidth(Analyzer.markWidth); for (int i=0; i=0 && index<=nPoints && counters!=null) { counts[counters[index]]--; for (int i=index; i1; if (counter!=0 || isStack || counters!=null) { if (counters==null) { counters = new short[nPoints*2]; positions = new short[nPoints*2]; } counters[nPoints-1] = (short)counter; if (imp!=null) positions[nPoints-1] = imp.getStackSize()>1?(short)imp.getCurrentSlice():0; //if (positions[nPoints-1]==0 || positions[nPoints-1]==1 || counters[nPoints-1]==0) // IJ.log("incrementCounter: "+nPoints+" "+" "+positions[nPoints-1]+" "+counters[nPoints-1]+" "+imp); if (nPoints+1==counters.length) { short[] temp = new short[counters.length*2]; System.arraycopy(counters, 0, temp, 0, counters.length); counters = temp; temp = new short[counters.length*2]; System.arraycopy(positions, 0, temp, 0, positions.length); positions = temp; } } if (rt!=null && WindowManager.getFrame(getCountsTitle())!=null) displayCounts(); } /** Returns the index of the current counter. */ public int getCounter() { return counter; } /** Returns the count associated with the specified counter index. * @see #getLastCounter * @see PointProperties.js */ public int getCount(int counter) { if (counter==0 && counters==null) return nPoints; else return counts[counter]; } /** Returns the index of the last counter. */ public int getLastCounter() { return nCounters - 1; } /** Returns the number of counters. */ public int getNCounters() { int n = 0; for (int counter=0; counter0) n++; } return n; } /** Returns the counter assocated with the specified point. */ public int getCounter(int index) { if (counters==null || index>=counters.length) return 0; else return counters[index]; } public void resetCounters() { for (int i=0; i=0 && type=0 && type=0 && index=0 && sizenCounters-1 && nCounters10 && imp!=null && imp.getWindow()!=null; } public void promptBeforeDeleting(Boolean prompt) { promptBeforeDeleting = prompt; promptBeforeDeletingCalled = true; } public static void setDefaultCounter(int counter) { defaultCounter = counter; } public int[] getCounters() { if (nPoints>65535) return null; int[] temp = new int[nPoints]; if (counters!=null) { for (int i=0; i>8)&0xffff; this.counters[i] = (short)counter; this.positions[i] = (short)position; if (counternCounters-1) nCounters = counter + 1; } } IJ.setTool("multi-point"); } } public int getPointPosition(int index) { if (positions!=null && index1 && positions!=null) { int nChannels = 1; int nSlices = 1; int nFrames = 1; boolean isHyperstack = false; if (imp.isComposite() || imp.isHyperStack()) { isHyperstack = true; nChannels = imp.getNChannels(); nSlices = imp.getNSlices(); nFrames = imp.getNFrames(); int nDimensions = 2; if (nChannels>1) nDimensions++; if (nSlices>1) nDimensions++; if (nFrames>1) nDimensions++; if (nDimensions==3) { isHyperstack = false; if (nChannels>1) firstColumnHdr = "Channel"; } else firstColumnHdr = "Image"; } int firstSlice = Integer.MAX_VALUE; for (int i=0; i0 && positions[i]0) { for (int i=0; ilastSlice) lastSlice = positions[i]; } } if (firstSlice>0) { for (int slice=firstSlice; slice<=lastSlice; slice++) { rt.setValue(firstColumnHdr, row, slice); if (isHyperstack) { int[] position = imp.convertIndexToPosition(slice); if (nChannels>1) rt.setValue("Channel", row, position[0]); if (nSlices>1) rt.setValue("Slice", row, position[1]); if (nFrames>1) rt.setValue("Frame", row, position[2]); } for (int counter=0; counter1?imp.getCurrentSlice():0; for (int i=0; i=sx2 && sx<=sx2+size && sy>=sy2 && sy<=sy2+size) { handle = i; break; } } return handle; } /** Returns the points as an array of Points. * Wilhelm Burger: modified to use FloatPolygon for correct point positions. */ public Point[] getContainedPoints() { FloatPolygon p = getFloatPolygon(); Point[] points = new Point[p.npoints]; for (int i=0; i iterator() { return new Iterator() { final Point[] pnts = getContainedPoints(); final int n = pnts.length; int next = (n == 0) ? 1 : 0; @Override public boolean hasNext() { return next < n; } @Override public Point next() { if (next >= n) { throw new NoSuchElementException(); } Point pnt = pnts[next]; next = next + 1; return pnt; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override protected int getClosestPoint(double x, double y, FloatPolygon points) { int index = -1; double distance = Double.MAX_VALUE; int slice = imp!=null&&positions!=null&&imp.getStackSize()>1?imp.getCurrentSlice():0; if (Prefs.showAllPoints) slice = 0; for (int i=0; i1) return ("Roi[Points, count="+nPoints+"]"); else return ("Roi[Point, x="+x+", y="+y+"]"); } /** @deprecated */ public void setHideLabels(boolean hideLabels) { this.showLabels = !hideLabels; } /** @deprecated */ public static void setDefaultMarkerSize(String size) { } /** @deprecated */ public static String getDefaultMarkerSize() { return sizes[defaultSize]; } /** Deprecated */ public static void setDefaultCrossColor(Color color) { } /** Deprecated */ public static Color getDefaultCrossColor() { return null; } }