File: PointRoi.java

package info (click to toggle)
imagej 1.54g-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,520 kB
  • sloc: java: 132,209; sh: 286; xml: 255; makefile: 6
file content (993 lines) | stat: -rw-r--r-- 29,527 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
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. Use the getPolygon() or getFloatPolygon() methods
 * to retrieve the coordinates of the points. 
 * @see <a href="http://wsr.imagej.net/macros/js/PointProperties.js">PointProperties.js</a>
*/
public class PointRoi extends PolygonRoi {
	public static final String[] sizes = {"Tiny", "Small", "Medium", "Large", "Extra Large", "XXL", "XXXL"};
	public static final String[] types = {"Hybrid", "Cross", "Dot", "Circle"};
	public static final int HYBRID=0, CROSS=1, CROSSHAIR=1, DOT=2, CIRCLE=3;
	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, XXL=17, XXXL=25;
	private static final BasicStroke twoPixelsWide = new BasicStroke(2);
	private static final BasicStroke threePixelsWide = new BasicStroke(3);
	private static final BasicStroke fivePixelsWide = new BasicStroke(5);
	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;   //for each point, 0-100 for counter (=category that can be defined by the user)
	private int[] positions;    //for each point, the stack slice, or 0 for 'show on all'
	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;
	public static PointRoi savedPoints;

	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);
		updateCounts();
	}

	/** 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);
		updateCounts();
	}

	/** 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(makeXorYArray(ox, null, false), makeXorYArray(oy, null, true), 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(makeXorYArray(ox, null, false), makeXorYArray(oy, null, true), 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(makeXorYArray(ox, null, false), makeXorYArray(oy, null, true), 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(makeXorYArray(sx, imp, false), makeXorYArray(sy, imp, true), 1, POINT);
		//defaultCounter = 0;
		setImage(imp);
		width=1; height=1;
		type = defaultType;
		size = defaultSize;
		showLabels = !Prefs.noPointLabels;
		if (imp!=null) {
			int r = 10 + size;
			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);
		}
	}

	public 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;
		else if (options.contains("xxxl")) size=XXXL;
		else if (options.contains("xxl")) size=XXL;
		if (options.contains("cross")) type=CROSS;
		else if (options.contains("dot")) type=DOT;
		else if (options.contains("circle")) type=CIRCLE;
		if (options.contains("nolabel")) setShowLabels(false);
		else if (options.contains("label")) setShowLabels(true);
		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; i<n; i++)
			temp[i] = arr[i];
		return temp;
	}

	/** Creates a one-element array with a coordinate; if 'imp' is non-null
	 *  converts from a screen coordinate to an image (offscreen) coordinate.
	 *  The array can be used for adding to an existing point selection */
	static float[] makeXorYArray(double value, ImagePlus imp, boolean isY) {
		if (imp != null) {
			ImageCanvas canvas = imp.getCanvas();
			if (canvas != null) {			//offset 0.5 converts from area to pixel center coordinates
				value = (isY ? canvas.offScreenYD((int)value) :canvas.offScreenXD((int)value)) - 0.5;
				if (!magnificationForSubPixel(canvas.getMagnification()))
					value = Math.round(value);
			}
		}
		return new float[] {(float)value};
	}

	void handleMouseMove(int ox, int oy) {
	}

	protected void handleMouseUp(int sx, int sy) {
		super.handleMouseUp(sx, sy);
		modifyRoi(); //adds this point to previous points if shift key down
	}

	/** Draws the points on the image. */
	public void draw(Graphics g) {
		updatePolygon();
		if (showLabels && nPoints>1) {
			fontSize = 8;
			double scale = size>=XXL?2:1.5;
			fontSize += scale*convertSizeToIndex(size);
			fontSize = (int)Math.round(fontSize);
			//IJ.log("fontSize: "+fontSize+" "+scale);
			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; i<nPoints; i++) {
			//IJ.log(i+" "+slice+" "+(positions!=null?positions[i]:-1)+"  "+getPosition());
			if (slice==0 || (positions!=null&&(slice==positions[i]||positions[i]==0)))
				drawPoint(g, xp2[i], yp2[i], i+1);
		}
		if (updateFullWindow) {
			updateFullWindow = false;
			imp.draw();
		}
		PointToolOptions.update();
		flattenScale = 1.0;
	}

	void drawPoint(Graphics g, int x, int y, int n) {
		int size2=size/2;
		boolean colorSet = false;
		Graphics2D g2d = (Graphics2D)g;
		AffineTransform saveXform = null;
		if (flattenScale>1.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>XXL)
				g2d.setStroke(fivePixelsWide);
			else 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 xoffset = 2;
			if (size==LARGE) xoffset=3;
			if (size==EXTRA_LARGE) xoffset=4;
			if (size==XXL) xoffset=5;
			if (size==XXXL) xoffset=7;
			int yoffset = xoffset;
			if (size>=LARGE) yoffset=yoffset-1;
			if (nCounters==1) {
				if (!colorSet)
					g.setColor(color);
				g.drawString(""+n, x+xoffset, y+yoffset+fontSize);
			} else if (counters!=null) {
				g.setColor(getColor(counters[n-1]));
				g.drawString(""+counters[n-1], x+xoffset, y+yoffset+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);
		double x0 = bounds == null ? x : bounds.x;
		double y0 = bounds == null ? y : bounds.y;
		for (int i=0; i<nPoints; i++) {
			ip.moveTo((int)Math.round(x0+xpf[i]), (int)Math.round(y0+ypf[i]));
			ip.lineTo((int)Math.round(x0+xpf[i]), (int)Math.round(y0+ypf[i]));
		}
	}

	/** Adds a point to this PointRoi. */
	public void addPoint(ImagePlus imp, double ox, double oy) {
		if (nPoints==xpf.length)
			enlargeArrays();
		addPoint2(imp, ox, oy);
		resetBoundingRect();
	}


	public void addUserPoint(ImagePlus imp, double ox, double oy) {
		addPoint(imp, ox, oy);
		nMarkers++;
	}

	private void addPoint2(ImagePlus imp, double ox, double oy) {
		double xbase = getXBase();
		double ybase = getYBase();
		xpf[nPoints] = (float)(ox-xbase);
		ypf[nPoints] = (float)(oy-ybase);
		xp2[nPoints] = (int)ox;
		yp2[nPoints] = (int)oy;
		nPoints++;
		incrementCounter(imp);
		lastPointTime = System.currentTimeMillis();
	}

	/** Adds a point to this PointRoi. */
	public PointRoi addPoint(double x, double y) {
		addPoint(null, x, y);
		return this;
	}

	/** Adds a point at the specified stack position. */
	public void addPoint(double x, double y, int position) {
		if (counters==null) {
			int size = nPoints*2;
			if (size<100) size=100;
			counters = new short[size];
			positions = new int[size];
		}
		addPoint(null, x, y);
		positions[nPoints-1] = position;	
	}

	protected void deletePoint(int index) {
		super.deletePoint(index);
		if (index>=0 && index<=nPoints && counters!=null) {
			counts[counters[index]]--;
			for (int i=index; i<nPoints; i++) {
				counters[i] = counters[i+1];
				positions[i] = positions[i+1];
			}
			if (rt!=null && WindowManager.getFrame(getCountsTitle())!=null)
				displayCounts();
		} else if (index==0 && nPoints==0)
			counts[0] = 0;
			
	}

	private synchronized void incrementCounter(ImagePlus imp) {
		//IJ.log("incrementCounter: "+nPoints+" "+counter+" "+(counters!=null?""+counters.length:"null"));
		counts[counter]++;
		boolean isStack = imp!=null && imp.getStackSize()>1;
		if (counter!=0 || isStack || counters!=null) {
			if (counters==null) {
				int size = nPoints*2;
				if (size<100) size=100;
				counters = new short[size];
				positions = new int[size];
			}
			if (nPoints>=counters.length) {
				short[] temp = new short[counters.length*2];
				System.arraycopy(counters, 0, temp, 0, counters.length);
				counters = temp;
				int[] temp1 = new int[counters.length*2];
				System.arraycopy(positions, 0, temp1, 0, positions.length);
				positions = temp1;
			}
			counters[nPoints-1] = (short)counter;
			if (imp!=null)
					positions[nPoints-1] = imp.getStackSize()>1 ? imp.getCurrentSlice() : 0;
			//if (positions[nPoints-1]==0 || positions[nPoints-1]==1 || counters[nPoints-1]==0)
			//IJ.log("incrementCounter: "+nPoints+" "+counters.length+" "+counters[nPoints-1]+"   "+positions[nPoints-1]+" "+imp);
		}
		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 <a href="http://wsr.imagej.net/macros/js/PointProperties.js">PointProperties.js</a>
	 */
	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; counter<nCounters; counter++) {
			if (getCount(counter)>0) 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<counts.length; i++)
			counts[i] = 0;
		counters = null;
		positions = null;
		PointToolOptions.update();
	}

	/** Returns the points of this Roi that are not contained in the specified area ROI.
	 *  Returns null if there are no resulting points or Roi is not an area roi. */
	public PointRoi subtractPoints(Roi roi) {
		return checkContained(roi, false);
	}

	/** Returns the points of this Roi that are contained in the specified area ROI.
	 *  Returns null if there are no resulting points or Roi is not an area roi. */
	public PointRoi containedPoints(Roi roi) {
		return checkContained(roi, true);
	}

	/** Returns the points contained (not contained) in <code>roi</code> if <code>keepContained</code> is true (false). */
	PointRoi checkContained(Roi roi, boolean keepContained) {
		if (!roi.isArea()) return null;
		FloatPolygon points = getFloatPolygon();
		FloatPolygon points2 = new FloatPolygon();
		for (int i=0; i<points.npoints; i++) {
			if (keepContained == roi.containsPoint(points.xpoints[i], points.ypoints[i]))
				points2.addPoint(points.xpoints[i], points.ypoints[i]);
		}
		if (points2.npoints==0)
			return null;
		else {
			PointRoi roi2 = new PointRoi(points2.xpoints, points2.ypoints, points2.npoints);
			roi2.copyAttributes(this);
			return roi2;
		}
	}

	public ImageProcessor getMask() {
		ImageProcessor mask = cachedMask;
		if (mask!=null && mask.getPixels()!=null)
			return mask;
		mask = new ByteProcessor(width, height);
		double deltaX = getXBase() - x;
		double deltaY = getYBase() - y;
		for (int i=0; i<nPoints; i++)
			mask.putPixel((int)Math.round(xpf[i] + deltaX), (int)Math.round(ypf[i] + deltaY), 255);
		cachedMask = mask;
		return mask;
	}

	/** Returns true if (x,y) is one of the points in this collection. */
	public boolean contains(int x, int y) {
		for (int i=0; i<nPoints; i++) {
			if (x==this.x+xpf[i] && y==this.y+ypf[i]) return true;
		}
		return false;
	}

	public void setShowLabels(boolean showLabels) {
		this.showLabels = showLabels;
	}

	public boolean getShowLabels() {
		return showLabels;
	}

	public static void setDefaultType(int type) {
		if (type>=0 && type<types.length) {
			defaultType = type;
			PointRoi instance = getPointRoiInstance();
			if (instance!=null)
				instance.setPointType(defaultType);
			Prefs.set(TYPE_KEY, type);
		}
	}

	public static int getDefaultType() {
		return defaultType;
	}

	/** Sets the point type (0=hybrid, 1=cross, 2=dot, 3=circle). */
	public void setPointType(int type) {
		if (type>=0 && type<types.length)
			this.type = type;
	}

	/** Returns the point type (0=hybrid, 1=cross, 2=dot, 3=circle). */
	public int getPointType() {
		return type;
	}


	/** Sets the default point size, where 'size' is 0-6 (Tiny-XXXL). */
	public static void setDefaultSize(int size) {
		int index = size;
		if (index>=0 && index<sizes.length) {
			defaultSize = convertIndexToSize(index);
			PointRoi instance = getPointRoiInstance();
			if (instance!=null)
				instance.setSize(index);
			Prefs.set(SIZE_KEY, index);
		}
	}

	/** Returns the default point size 0-6 (Tiny-XXXL). */
	public static int getDefaultSize() {
		return convertSizeToIndex(defaultSize);
	}

	/** Sets the point size, where 'size' is 0-6 (Tiny-XXXL). */
	public void setSize(int size) {
		if (size>=0 && size<sizes.length)
			this.size = convertIndexToSize(size);
	}

	/** Returns the current point size 0-6 (Tiny-XXXL). */
	public int getSize() {
		return convertSizeToIndex(size);
	}

	private static int convertSizeToIndex(int size) {
		switch (size) {
			case TINY: return 0;
			case SMALL: return 1;
			case MEDIUM: return 2;
			case LARGE: return 3;
			case EXTRA_LARGE: return 4;
			case XXL: return 5;
			case XXXL: return 6;
		}
		return 1;
	}

	private static int convertIndexToSize(int index) {
		switch (index) {
			case 0: return TINY;
			case 1: return SMALL;
			case 2: return MEDIUM;
			case 3: return LARGE;
			case 4: return EXTRA_LARGE;
			case 5: return XXL;
			case 6: return XXXL;
		}
		return SMALL;
	}

	/** Always returns true. */
	public boolean subPixelResolution() {
		return true;
	}

	private static PointRoi getPointRoiInstance() {
		ImagePlus imp = WindowManager.getCurrentImage();
		if (imp!=null) {
			Roi roi  = imp.getRoi();
			if (roi!=null) {
				if (roi instanceof PointRoi)
					return (PointRoi)roi;
			}
		}
		return null;
	}

	public void setCounter(int counter) {
		this.counter = counter;
		if (counter>nCounters-1 && nCounters<MAX_COUNTERS)
			nCounters = counter + 1;
	}

	public boolean promptBeforeDeleting() {
	    if (promptBeforeDeletingCalled && getNCounters()==1)
	    	return promptBeforeDeleting;
	    else
			return (nMarkers>8||getNCounters()>1) && imp!=null && imp.getWindow()!=null;
	}

	public void promptBeforeDeleting(Boolean prompt) {
		promptBeforeDeleting = prompt;
		promptBeforeDeletingCalled = true;
	}

	public static void setDefaultCounter(int counter) {
		defaultCounter = counter;
	}

	/** Returns an array containing for each point:
	 *  The counter number (0-100) in the lower 8 bits, and the slice number
	 *  (or 0, if the point appears on all slices) in the higher 24 bits.
	 *  Used when writing a Roi to file (RoiEncoder) */
	public int[] getCounters() {
		if (nPoints>65535)
			return null;
		int[] temp = new int[nPoints];
		if (counters!=null) {
			for (int i=0; i<nPoints; i++)
				temp[i] = (counters[i]&0xff) + (positions[i]<<8);
		}
		return temp;
	}

	/** Sets the counter number and slice number for each point from an
	 *  array, where the lower 8 bits are the counter number and the
	 *  higher 24 bits contain the slice position of each point.
	 *  Used when reading a roi fromfile (RoiDecoder).  */
	public void setCounters(int[] counters) {
		if (counters!=null) {
			int n = counters.length;
			this.counters = new short[n*2];
			this.positions = new int[n*2];
			for (int i=0; i<n; i++) {
				int counter = counters[i]&0xff;
				int position = counters[i]>>8;
				//IJ.log(i+" cnt="+counter+" slice="+position);
				this.counters[i] = (short)counter;
				this.positions[i] = position;
				if (counter<counts.length && counter>nCounters-1)
					nCounters = counter + 1;
			}
			updateCounts();
		}
	}

	/** Updates the counts for each category in 'counters' */
	public void updateCounts() {
		Arrays.fill(counts, 0);
		for (int i=0; i<nPoints; i++)
			counts[(counters==null || counters[i]>=counts.length) ? 0 : counters[i]] ++;
	}

	/** Returns the stack slice of the point with the given index, or 0 if no slice defined for this point */
	public int getPointPosition(int index) {
		if (positions!=null && index<nPoints)
			return positions[index];
		else
			return 0;
	}

	public void displayCounts() {
		ImagePlus imp = getImage();
		String firstColumnHdr = "Slice";
		rt = new ResultsTable();
		int row = 0;
		if (imp!=null && imp.getStackSize()>1 && positions!=null) {
			int nChannels = 1;
			int nSlices = 1;
			int nFrames = 1;
			boolean isHyperstack = true;
			if (imp.isComposite() || imp.isHyperStack()) {
				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; i<nPoints; i++) {
				if (positions[i]>0 && positions[i]<firstSlice)
					firstSlice = positions[i];
			}
			if (firstSlice==Integer.MAX_VALUE)
				firstSlice = 0;
			int lastSlice = 0;
			if (firstSlice>0) {
				for (int i=0; i<nPoints; i++) {
					if (positions[i]>lastSlice)
						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; counter<nCounters; counter++) {
						int count = 0;
						for (int i=0; i<nPoints; i++) {
							if (slice==positions[i] && counter==counters[i])
								count++;
						}
						rt.setValue("Ctr "+counter, row, count);
					}
					row++;
				}
			}
		}
		rt.setValue(firstColumnHdr, row, "Total");
		for (int i=0; i<nCounters; i++)
			rt.setValue("Ctr "+i, row, counts[i]);
		rt.show(getCountsTitle());
		if (IJ.debugMode) debug();
	}

	private void debug() {
		FloatPolygon p = getFloatPolygon();
		ResultsTable rt = new ResultsTable();
		for (int i=0; i<nPoints; i++) {
			if (counters!=null) {
				rt.setValue("Counter", i, counters[i]);
				rt.setValue("Position", i, positions[i]);
			}
			rt.setValue("X", i, p.xpoints[i]);
			rt.setValue("Y", i, p.ypoints[i]);
		}
		rt.show(getCountsTitle());
	}

	private String getCountsTitle() {
		return "Counts_"+(imp!=null?imp.getTitle():"");
	}

	public synchronized static String[] getCounterChoices() {
		if (counterChoices==null) {
			counterChoices = new String[MAX_COUNTERS];
			for (int i=0; i<MAX_COUNTERS; i++)
				counterChoices[i] = ""+i;
		}
		return counterChoices;
	}

	private static Color getColor(int index) {
		if (colors==null) {
			colors = new Color[MAX_COUNTERS];
			colors[0]=Color.yellow; colors[1]=Color.magenta; colors[2]=Color.cyan;
			colors[3]=Color.orange; colors[4]=Color.green; colors[5]=Color.blue;
			colors[6]=Color.white; colors[7]=Color.darkGray; colors[8]=Color.pink;
			colors[9]=Color.lightGray;
		}
		if (colors[index]!=null)
			return colors[index];
		else {
			Random ran = new Random();
			float r = (float)ran.nextDouble();
			float g = (float)ran.nextDouble();
			float b = (float)ran.nextDouble();
			Color c = new Color(r, g, b);
			colors[index] = c;
			return c;
		}
	}

	/** Returns a point index if it has been at least one second since
		the last point was added and the specified screen coordinates are
		inside or near a point, otherwise returns -1. */
	public int isHandle(int sx, int sy) {
		if ((System.currentTimeMillis()-lastPointTime)<1000L)
			return -1;
		int size = HANDLE_SIZE+this.size;
		int halfSize = size/2;
		int handle = -1;
		int sx2, sy2;
		int slice = !Prefs.showAllPoints&&positions!=null&&imp!=null&&imp.getStackSize()>1?imp.getCurrentSlice():0;
		for (int i=0; i<nPoints; i++) {
			if (slice!=0 && slice!=positions[i])
				continue;
			sx2 = xp2[i]-halfSize; sy2=yp2[i]-halfSize;
			if (sx>=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<p.npoints; i++)
			points[i] = new Point((int) Math.round(p.xpoints[i]), (int) Math.round(p.ypoints[i]));
		return points;
	}

	/** Returns the points as a FloatPolygon. */
	public FloatPolygon getContainedFloatPoints() {
		return getFloatPolygon();
	}

	/**
	 * Custom iterator for points contained in a {@link PointRoi}.
	 * Author: W. Burger
	*/
	public Iterator<Point> iterator() {
		return new Iterator<Point>() {
			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; i<points.npoints; i++) {
			double dx = points.xpoints[i] - x;
			double dy = points.ypoints[i] - y;
			double distance2 = dx*dx+dy*dy;
			if (distance2<distance && (slice==0||slice==positions[i])) {
				distance = distance2;
				index = i;
			}
		}
		return index;
	}

	/** Returns a copy of this PointRoi. */
	public synchronized Object clone() {
		PointRoi r = (PointRoi)super.clone();
		if (counters!=null) {
			r.counters = new short[counters.length];
			for (int i=0; i<counters.length; i++)
				r.counters[i] = counters[i];
		}
		if (positions!=null) {
			r.positions = new int[positions.length];
			for (int i=0; i<positions.length; i++)
				r.positions[i] = positions[i];
		}
		if (counts!=null) {
			r.counts = new int[counts.length];
			for (int i=0; i<counts.length; i++)
				r.counts[i] = counts[i];
		}
		return r;
	}

	/* Returns a version of this PointRoi that only contains points inside 'roi'. */
	public PointRoi crop(Roi roi) {
		PointRoi points = (PointRoi)clone();
		Polygon p = points.getPolygon();
		for (int i=points.size()-1; i>=0; i--) {
			if (!roi.contains(p.xpoints[i],p.ypoints[i])) {
				points.deletePoint(i);
			}
		}
		return points;
	}

	@Override
	public void copyAttributes(Roi roi2) {
		super.copyAttributes(roi2);
		if (roi2 instanceof PointRoi) {
			PointRoi p2 = (PointRoi)roi2;
			this.type = p2.type;
			this.size = p2.size;
			this.showLabels = p2.showLabels;
			this.fontSize = p2.fontSize;
		}
	}

	public void setCounterInfo(int[] info) {
		counterInfo = info;
	}

	public int[] getCounterInfo() {
		return counterInfo;
	}

	public boolean addToOverlay() {
		return addToOverlay;
	}

	public String toString() {
		if (nPoints>1)
			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;
	}

}