3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.display
;
25 import ij
.gui
.GenericDialog
;
26 import ij
.measure
.Calibration
;
27 import ij
.measure
.ResultsTable
;
28 import ini
.trakem2
.Project
;
29 import ini
.trakem2
.persistence
.XMLOptions
;
30 import ini
.trakem2
.utils
.IJError
;
31 import ini
.trakem2
.utils
.M
;
32 import ini
.trakem2
.utils
.ProjectToolbar
;
33 import ini
.trakem2
.utils
.Utils
;
35 import java
.awt
.AlphaComposite
;
36 import java
.awt
.Color
;
37 import java
.awt
.Composite
;
38 import java
.awt
.Graphics2D
;
39 import java
.awt
.Polygon
;
40 import java
.awt
.Rectangle
;
41 import java
.awt
.Stroke
;
42 import java
.awt
.event
.KeyEvent
;
43 import java
.awt
.event
.MouseEvent
;
44 import java
.awt
.geom
.AffineTransform
;
45 import java
.awt
.geom
.Area
;
46 import java
.awt
.geom
.Ellipse2D
;
47 import java
.awt
.geom
.NoninvertibleTransformException
;
48 import java
.awt
.geom
.Point2D
;
49 import java
.util
.ArrayList
;
50 import java
.util
.Collection
;
51 import java
.util
.HashMap
;
52 import java
.util
.HashSet
;
53 import java
.util
.Iterator
;
54 import java
.util
.List
;
57 import javax
.vecmath
.Point3f
;
59 public class Ball
extends ZDisplayable
implements VectorData
{
61 /**The number of points.*/
62 protected int n_points
;
63 /**The array of clicked points.*/
64 protected double[][] p
;
65 /**The array of Layers over which each point lives */
66 protected long[] p_layer
;
67 /**The width of each point. */
68 protected double[] p_width
;
70 /** Every new Ball will have, for its first point, the last user-adjusted radius value or the radius of the last user-selected point. */
71 static private double last_radius
= -1;
73 /** Paint as outlines (false) or as solid areas (true; default, with a default alpha of 0.4f).*/
74 private boolean fill_paint
= true;
76 public Ball(Project project
, String title
, double x
, double y
) {
77 super(project
, title
, x
, y
);
80 p_layer
= new long[5]; // the ids of the layers in which each point lays
81 p_width
= new double[5];
85 /** Construct an unloaded Ball from the database. Points will be loaded later, when needed. */
86 public Ball(Project project
, long id
, String title
, float width
, float height
, float alpha
, boolean visible
, Color color
, boolean locked
, AffineTransform at
) {
87 super(project
, id
, title
, locked
, at
, width
, height
);
88 this.visible
= visible
;
91 this.n_points
= -1; //used as a flag to signal "I have points, but unloaded"
94 /** Construct a Ball from an XML entry. */
95 public Ball(Project project
, long id
, HashMap
<String
,String
> ht
, HashMap
<Displayable
,String
> ht_links
) {
96 super(project
, id
, ht
, ht_links
);
97 // individual balls will be added as soon as parsed
99 this.p
= new double[2][5];
100 this.p_layer
= new long[5];
101 this.p_width
= new double[5];
103 Object ob_data
= ht
.get("fill");
105 if (null != ob_data
) this.fill_paint
= "true".equals(((String
)ob_data
).trim().toLowerCase()); // fails: //Boolean.getBoolean((String)ob_data);
106 } catch (Exception e
) {
107 Utils
.log("Ball: could not read fill_paint value from XML:" + e
);
111 /** Used to add individual ball objects when parsing. */
112 public void addBall(double x
, double y
, double r
, long layer_id
) {
113 if (p
[0].length
== n_points
) enlargeArrays();
116 p_width
[n_points
] = r
;
117 p_layer
[n_points
] = layer_id
;
121 /**Increase the size of the arrays by 5.*/
122 private void enlargeArrays() {
124 int length
= p
[0].length
;
126 double[][] p_copy
= new double[2][length
+ 5];
127 long[] p_layer_copy
= new long[length
+ 5];
128 double[] p_width_copy
= new double[length
+ 5];
130 System
.arraycopy(p
[0], 0, p_copy
[0], 0, length
);
131 System
.arraycopy(p
[1], 0, p_copy
[1], 0, length
);
132 System
.arraycopy(p_layer
, 0, p_layer_copy
, 0, length
);
133 System
.arraycopy(p_width
, 0, p_width_copy
, 0, length
);
136 this.p_layer
= p_layer_copy
;
137 this.p_width
= p_width_copy
;
140 /**Find a point in an array, with a precision dependent on the magnification.*/
141 protected int findPoint(double[][] a
, int x_p
, int y_p
, double magnification
, final long lid
) {
143 double d
= (10.0D
/ magnification
);
145 for (int i
=0; i
<n_points
; i
++) {
146 if (p_layer
[i
] != lid
) continue;
147 if ((Math
.abs(x_p
- a
[0][i
]) + Math
.abs(y_p
- a
[1][i
])) <= p_width
[i
]) {
154 protected void removePoint(int index
) {
155 // check preconditions:
158 } else if (n_points
- 1 == index
) {
162 //one point out (but not the last)
165 // shift all points after 'index' one position to the left:
166 for (int i
=index
; i
<n_points
; i
++) {
167 p
[0][i
] = p
[0][i
+1]; //the +1 doesn't fail ever because the n_points has been adjusted above, but the arrays are still the same size. The case of deleting the last point is taken care above.
169 p_layer
[i
] = p_layer
[i
+1];
170 p_width
[i
] = p_width
[i
+1];
174 //later! Otherwise can't repaint properly//calculateBoundingBox(true);
177 updateInDatabase("points");
180 /**Move backbone point by the given deltas.*/
181 private void dragPoint(int index
, int dx
, int dy
) {
186 static private double getFirstWidth() {
187 if (null == Display
.getFront()) return 10;
188 if (-1 != last_radius
) return last_radius
;
189 return 10 / Display
.getFront().getCanvas().getMagnification(); // 10 pixels in the screen
192 /**Add a point either at the end or between two existing points, with accuracy depending on magnification. The width of the new point is that of the closest point after which it is inserted.*/
193 protected int addPoint(double x_p
, double y_p
, long layer_id
, double radius
) {
194 if (-1 == n_points
) setupForDisplay(); //reload
196 if (p
[0].length
== n_points
) {
200 p
[0][n_points
] = x_p
;
201 p
[1][n_points
] = y_p
;
202 p_layer
[n_points
] = layer_id
;
203 p_width
[n_points
] = radius
;
207 updateInDatabase(new StringBuilder("INSERT INTO ab_ball_points (ball_id, x, y, width, layer_id) VALUES (").append(id
).append(",").append(x_p
).append(",").append(y_p
).append(",").append(p_width
[index
]).append(",").append(layer_id
).append(")").toString());
212 public void paint(final Graphics2D g
, final Rectangle srcRect
, final double magnification
, final boolean active
, final int channels
, final Layer active_layer
, final List
<Layer
> layers
) {
213 if (0 == n_points
) return;
214 if (-1 == n_points
) {
215 // load points from the database
218 //arrange transparency
219 final Composite original_composite
= g
.getComposite(),
220 perimeter_composite
= alpha
== 1.0f ? original_composite
: AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, alpha
),
221 area_composite
= fill_paint ?
(alpha
> 0.4f ? AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, 0.4f
) : perimeter_composite
)
224 // Clear transform and stroke
225 final AffineTransform gt
= g
.getTransform();
226 g
.setTransform(DisplayCanvas
.DEFAULT_AFFINE
);
227 final Stroke stroke
= g
.getStroke();
228 g
.setStroke(DisplayCanvas
.DEFAULT_STROKE
);
230 // local pointers, since they may be transformed
231 double[][] p
= this.p
;
232 double[] p_width
= this.p_width
;
234 if (!this.at
.isIdentity()) {
235 final Object
[] ob
= getTransformedData();
236 p
= (double[][])ob
[0];
237 p_width
= (double[])ob
[1];
240 final boolean color_cues
= layer_set
.color_cues
;
241 final int n_layers_color_cue
= layer_set
.n_layers_color_cue
;
242 final Color below
, above
;
243 if (layer_set
.use_color_cue_colors
) {
251 // Paint a sliced sphere
252 final double current_layer_z
= active_layer
.getZ();
253 final int current_layer_index
= layer_set
.indexOf(active_layer
);
254 final long active_lid
= active_layer
.getId();
255 for (int j
=0; j
<n_points
; j
++) {
256 if (active_lid
== p_layer
[j
]) {
257 g
.setColor(this.color
);
258 final int radius
= (int)p_width
[j
];
259 final int x
= (int)((p
[0][j
] -radius
-srcRect
.x
) * magnification
),
260 y
= (int)((p
[1][j
] -radius
-srcRect
.y
) * magnification
),
261 w
= (int)(2 * radius
* magnification
);
263 g
.setComposite(area_composite
);
264 g
.fillOval(x
, y
, w
, w
);
266 g
.setComposite(perimeter_composite
);
267 g
.drawOval(x
, y
, w
, w
);
268 } else if (color_cues
) {
269 boolean can_paint
= -1 == n_layers_color_cue
;
270 final Layer layer
= layer_set
.getLayer(p_layer
[j
]); // fast: map lookup
272 can_paint
= Math
.abs(current_layer_index
- layer_set
.indexOf(layer
)) <= n_layers_color_cue
; // fast: map lookup
274 // Check if p_layer[j] is within the range of layers to color cue:
275 //Utils.logMany2("current_layer_index: ", current_layer_index, "layer index:", layer_set.indexOf(layer), "n_layers_color_cue", n_layers_color_cue);
277 // does the point intersect with the layer?
278 final double z
= layer
.getZ();
279 final double depth
= Math
.abs(current_layer_z
- z
);
280 if (depth
< this.p_width
[j
]) { // compare with untransformed data, in pixels!
282 if (z
< current_layer_z
) g
.setColor(below
);
283 else g
.setColor(above
);
284 // h^2 = sin^2 + cos^2 ---> p_width[j] is h, and sin*h is depth
285 final int slice_radius
= (int)(p_width
[j
] * Math
.sqrt(1 - Math
.pow(depth
/p_width
[j
], 2)));
286 final int x
= (int)((p
[0][j
] -slice_radius
-srcRect
.x
) * magnification
),
287 y
= (int)((p
[1][j
] -slice_radius
-srcRect
.y
) * magnification
),
288 w
= (int)(2 * slice_radius
* magnification
);
290 g
.setComposite(area_composite
);
291 g
.fillOval(x
, y
, w
, w
);
293 g
.setComposite(perimeter_composite
);
294 g
.drawOval(x
, y
, w
, w
);
301 final long layer_id
= active_layer
.getId();
302 for (int j
=0; j
<n_points
; j
++) {
303 if (layer_id
!= p_layer
[j
]) continue;
304 DisplayCanvas
.drawScreenHandle(g
, (int)((p
[0][j
] -srcRect
.x
) * magnification
),
305 (int)((p
[1][j
] -srcRect
.y
) * magnification
));
309 //Transparency: fix alpha composite back to original.
310 g
.setComposite(original_composite
);
317 public void keyPressed(KeyEvent ke
) {
321 /**Helper vars for mouse events. Safe as static since only one Ball will be edited at a time.*/
322 static int index
= -1;
324 public void mousePressed(MouseEvent me
, Layer layer
, int x_p
, int y_p
, double mag
) {
325 // transform the x_p, y_p to the local coordinates
326 if (!this.at
.isIdentity()) {
327 final Point2D
.Double po
= inverseTransformPoint(x_p
, y_p
);
332 final int tool
= ProjectToolbar
.getToolId();
334 if (ProjectToolbar
.PEN
== tool
) {
335 long layer_id
= layer
.getId();
336 if (Utils
.isControlDown(me
) && me
.isShiftDown()) {
337 index
= findNearestPoint(p
, p_layer
, n_points
, x_p
, y_p
, layer
.getId()); // should go to an AbstractProfile or something
339 index
= findPoint(p
, x_p
, y_p
, mag
, layer
.getId());
342 if (layer_id
== p_layer
[index
]) {
343 if (Utils
.isControlDown(me
) && me
.isShiftDown() && p_layer
[index
] == Display
.getFrontLayer().getId()) {
345 index
= -1; // to prevent saving in the database twice
346 repaint(false, layer
);
349 } else index
= -1; // disable if not in the front layer (so a new point will be added)
351 // Make the radius for newly added balls that of the last selected
352 last_radius
= p_width
[index
];
356 index
= addPoint(x_p
, y_p
, layer_id
, (0 == n_points ? Ball
.getFirstWidth() : p_width
[n_points
-1])); // either 10 screen pixels or the same as the last point
357 repaint(false, layer
);
362 public void mouseDragged(MouseEvent me
, Layer layer
, int x_p
, int y_p
, int x_d
, int y_d
, int x_d_old
, int y_d_old
) {
363 // transform to the local coordinates
364 if (!this.at
.isIdentity()) {
365 final Point2D
.Double p
= inverseTransformPoint(x_p
, y_p
);
368 final Point2D
.Double pd
= inverseTransformPoint(x_d
, y_d
);
371 final Point2D
.Double pdo
= inverseTransformPoint(x_d_old
, y_d_old
);
372 x_d_old
= (int)pdo
.x
;
373 y_d_old
= (int)pdo
.y
;
376 final int tool
= ProjectToolbar
.getToolId();
378 if (ProjectToolbar
.PEN
== tool
) {
380 if (me
.isShiftDown()) {
381 p_width
[index
] = Math
.sqrt((x_d
- p
[0][index
])*(x_d
- p
[0][index
]) + (y_d
- p
[1][index
])*(y_d
- p
[1][index
]));
382 last_radius
= p_width
[index
];
383 Utils
.showStatus("radius: " + p_width
[index
], false);
385 dragPoint(index
, x_d
- x_d_old
, y_d
- y_d_old
);
387 repaint(false, layer
);
392 public void mouseReleased(MouseEvent me
, Layer layer
, int x_p
, int y_p
, int x_d
, int y_d
, int x_r
, int y_r
) {
394 //update points in database if there was any change
395 if (-1 != index
&& index
!= n_points
) { // don't do it when the last point is removed
396 // NEEDS to be able to identify each point separately!! Needs an id, or an index as in pipe!! //updateInDatabase(getUpdatePointForSQL(index));
397 updateInDatabase("points"); // delete and add all again. TEMPORARY
400 //later!//calculateBoundingBox(true);
401 updateInDatabase("transform+dimensions");
406 repaint(true, layer
);
410 protected boolean calculateBoundingBox(Layer la
) {
411 calculateBoundingBox(true, la
);
415 /** Uses the @param layer to update a specific Bucket for that layer. */
416 private void calculateBoundingBox(boolean adjust_position
, Layer la
) {
417 double min_x
= Double
.MAX_VALUE
;
418 double min_y
= Double
.MAX_VALUE
;
422 this.width
= this.height
= 0;
427 for (int i
=0; i
<n_points
; i
++) {
428 if (p
[0][i
] - p_width
[i
] < min_x
) min_x
= p
[0][i
] - p_width
[i
];
429 if (p
[1][i
] - p_width
[i
] < min_y
) min_y
= p
[1][i
] - p_width
[i
];
430 if (p
[0][i
] + p_width
[i
] > max_x
) max_x
= p
[0][i
] + p_width
[i
];
431 if (p
[1][i
] + p_width
[i
] > max_y
) max_y
= p
[1][i
] + p_width
[i
];
434 this.width
= (float)(max_x
- min_x
);
435 this.height
= (float)(max_y
- min_y
);
437 if (adjust_position
) {
438 // now readjust points to make min_x,min_y be the x,y
439 for (int i
=0; i
<n_points
; i
++) {
440 p
[0][i
] -= min_x
; p
[1][i
] -= min_y
;
442 this.at
.translate(min_x
, min_y
); // not using super.translate(...) because a preConcatenation is not needed; here we deal with the data.
443 updateInDatabase("transform+dimensions");
445 updateInDatabase("dimensions");
451 /**Release all memory resources taken by this object.*/
452 public void destroy() {
459 /**Repaints in the given ImageCanvas only the area corresponding to the bounding box of this Profile. */
460 public void repaint(boolean repaint_navigator
, Layer layer
) {
461 //TODO: this could be further optimized to repaint the bounding box of the last modified segments, i.e. the previous and next set of interpolated points of any given backbone point. This would be trivial if each segment of the Bezier curve was an object.
462 Rectangle box
= getBoundingBox(null);
463 calculateBoundingBox(true, layer
);
464 box
.add(getBoundingBox(null));
465 Display
.repaint(layer_set
, this, box
, 5, repaint_navigator
);
468 /**Make this object ready to be painted.*/
469 private void setupForDisplay() {
472 ArrayList
<?
> al
= project
.getLoader().fetchBallPoints(id
);
473 n_points
= al
.size();
474 p
= new double[2][n_points
];
475 p_layer
= new long[n_points
];
476 p_width
= new double[n_points
];
477 Iterator
<?
> it
= al
.iterator();
479 while (it
.hasNext()) {
480 Object
[] ob
= (Object
[])it
.next();
481 p
[0][i
] = ((Double
)ob
[0]).doubleValue();
482 p
[1][i
] = ((Double
)ob
[1]).doubleValue();
483 p_width
[i
] = ((Double
)ob
[2]).doubleValue();
484 p_layer
[i
] = ((Long
)ob
[3]).longValue();
489 /**Release memory resources used by this object: namely the arrays of points, which can be reloaded with a call to setupForDisplay()*/
490 public void flush() {
494 n_points
= -1; // flag that points exist
497 /** The exact perimeter of this Ball, in integer precision. */
498 public Polygon
getPerimeter() {
499 if (-1 == n_points
) setupForDisplay();
501 // local pointers, since they may be transformed
502 double[][] p
= this.p
;
503 double[] p_width
= this.p_width
;
505 if (!this.at
.isIdentity()) {
506 final Object
[] ob
= getTransformedData();
507 p
= (double[][])ob
[0];
508 p_width
= (double[])ob
[1];
511 // the box of the selected point
512 return new Polygon(new int[]{(int)(p
[0][index
] - p_width
[index
]), (int)(p
[0][index
] + p_width
[index
]), (int)(p
[0][index
] + p_width
[index
]), (int)(p
[0][index
] - p_width
[index
])}, new int[]{(int)(p
[1][index
] - p_width
[index
]), (int)(p
[1][index
] + p_width
[index
]), (int)(p
[1][index
] + p_width
[index
]), (int)(p
[1][index
] - p_width
[index
])}, 4);
515 return super.getPerimeter();
518 /** Writes the data of this object as a Ball object in the .shapes file represented by the 'data' StringBuffer. */
519 public void toShapesFile(StringBuffer data
, String group
, String color
, double z_scale
) {
520 if (-1 == n_points
) setupForDisplay();
521 // TEMPORARY FIX: sort balls by layer_id (by Z, which is roughly the same)
522 final HashMap
<Long
,StringBuffer
> ht
= new HashMap
<Long
,StringBuffer
>();
524 // local pointers, since they may be transformed
525 double[][] p
= this.p
;
526 double[] p_width
= this.p_width
;
527 if (!this.at
.isIdentity()) {
528 final Object
[] ob
= getTransformedData();
529 p
= (double[][])ob
[0];
530 p_width
= (double[])ob
[1];
532 StringBuffer sb
= new StringBuffer();
533 sb
.append("type=ball").append(l
)
534 .append("name=").append(project
.getMeaningfulTitle(this)).append(l
)
535 .append("group=").append(group
).append(l
)
536 .append("color=").append(color
).append(l
)
537 .append("supergroup=").append("null").append(l
)
538 .append("supercolor=").append("null").append(l
)
541 StringBuffer tmp
= null;
542 for (int i
=0; i
<n_points
; i
++) {
543 Long layer_id
= new Long(p_layer
[i
]);
544 // Doesn't work ??//if (ht.contains(layer_id)) tmp = (StringBuffer)ht.get(layer_id);
545 for (Map
.Entry
<Long
,StringBuffer
> e
: ht
.entrySet()) {
546 if (e
.getKey().longValue() == p_layer
[i
]) {
552 tmp
= new StringBuffer(sb
.toString()); // can't clone ?!?
553 tmp
.append(layer
.getParent().getLayer(p_layer
[i
]).getZ() * z_scale
).append(l
);
554 ht
.put(layer_id
, tmp
);
556 tmp
.append("x").append(p
[0][i
]).append(l
)
557 .append("y").append(p
[1][i
]).append(l
)
558 .append("r").append(p_width
[i
]).append(l
)
562 for (StringBuffer s
: ht
.values()) {
563 data
.append(s
).append(l
);
565 Utils
.log("s : " + s
.toString());
569 /** Return the list of query statements needed to insert all the points in the database. */
570 public String
[] getPointsForSQL() {
571 String
[] sql
= new String
[n_points
];
572 for (int i
=0; i
<n_points
; i
++) {
573 StringBuilder sb
= new StringBuilder("INSERT INTO ab_ball_points (ball_id, x, y, width, layer_id) VALUES (");
574 sb
.append(this.id
).append(",")
575 .append(p
[0][i
]).append(",")
576 .append(p
[1][i
]).append(",")
577 .append(p_width
[i
]).append(",")
581 sql
[i
] = sb
.toString();
586 public boolean isDeletable() {
587 return 0 == n_points
;
590 /** Test whether the Ball contains the given point at the given layer. What it does: and tests whether the point is contained in any of the balls present in the given layer. */
592 public boolean contains(Layer layer
, double x
, double y
) {
593 if (-1 == n_points
) setupForDisplay(); // reload points
594 if (0 == n_points
) return false;
596 final Point2D
.Double po
= inverseTransformPoint(x
, y
);
600 final long layer_id
= layer
.getId();
601 for (int i
=0; i
<n_points
; i
++) {
602 if (layer_id
!= p_layer
[i
]) continue;
603 if (x
>= p
[0][i
] - p_width
[i
] && x
<= p
[0][i
] + p_width
[i
] && y
>= p
[1][i
] - p_width
[i
] && y
<= p
[1][i
] + p_width
[i
]) return true;
608 /** Get the perimeter of all parts that show in the given layer (as defined by its Z), but representing each ball as a square in a Rectangle object. Returns null if none found. */
609 private Rectangle
[] getSubPerimeters(final Layer layer
) {
610 final ArrayList
<Rectangle
> al
= new ArrayList
<Rectangle
>();
611 final long layer_id
= layer
.getId();
612 double[][] p
= this.p
;
613 double[] p_width
= this.p_width
;
614 if (!this.at
.isIdentity()) {
615 final Object
[] ob
= getTransformedData();
616 p
= (double[][])ob
[0];
617 p_width
= (double[])ob
[1];
619 for (int i
=0; i
<n_points
; i
++) {
620 if (layer_id
!= p_layer
[i
]) continue;
621 al
.add(new Rectangle((int)(p
[0][i
] - p_width
[i
]), (int)(p
[1][i
] - p_width
[i
]), (int)Math
.ceil(p_width
[i
] + p_width
[i
]), (int)Math
.ceil(p_width
[i
] + p_width
[i
]))); // transformRectangle returns a copy of the Rectangle
623 if (al
.isEmpty()) return null;
625 final Rectangle
[] rects
= new Rectangle
[al
.size()];
631 public boolean linkPatches() {
632 // find the patches that don't lay under other profiles of this profile's linking group, and make sure they are unlinked. This will unlink any Patch objects under this Profile:
633 unlinkAll(Patch
.class);
635 // scan the Display and link Patch objects that lay under this Profile's bounding box:
637 // catch all displayables of the current Layer
638 final ArrayList
<Displayable
> al
= layer
.getDisplayables(Patch
.class);
640 // this bounding box as in the present layer
641 final Rectangle
[] perimeters
= getSubPerimeters(layer
); // transformed
642 if (null == perimeters
) return false;
644 boolean must_lock
= false;
646 // for each Patch, check if it underlays this profile's bounding box
647 final Rectangle box
= new Rectangle(); // as tmp
648 for (final Displayable displ
: al
) {
649 // stupid java, Polygon cannot test for intersection with another Polygon !! //if (perimeter.intersects(displ.getPerimeter())) // TODO do it yourself: check if a Displayable intersects another Displayable
650 for (int i
=0; i
<perimeters
.length
; i
++) {
651 if (perimeters
[i
].intersects(displ
.getBoundingBox(box
))) {
654 if (displ
.locked
) must_lock
= true;
660 // set the locked flag to this and all linked ones
661 if (must_lock
&& !locked
) {
669 /** Returns the layer of lowest Z coordinate where this ZDisplayable has a point in, or the creation layer if no points yet. */
670 public Layer
getFirstLayer() {
671 if (0 == n_points
) return this.layer
;
672 if (-1 == n_points
) setupForDisplay(); //reload
673 Layer la
= this.layer
;
674 double z
= Double
.MAX_VALUE
;
675 for (int i
=0; i
<n_points
; i
++) {
676 Layer layer
= layer_set
.getLayer(p_layer
[i
]);
677 if (layer
.getZ() < z
) la
= layer
;
682 /** Returns the raw data for the balls, sorted by Layer ID versus double[]{z,y,r} . */
683 public Map
<Layer
,double[]> getRawBalls() {
684 if (-1 == n_points
) setupForDisplay(); // reload
685 HashMap
<Layer
,double[]> m
= new HashMap
<Layer
,double[]>();
686 for (int i
=0; i
<n_points
; i
++) {
687 m
.put(layer_set
.getLayer(p_layer
[i
]), new double[]{p
[0][i
], p
[1][i
], p_width
[i
]});
692 /** Returns a [n_points][4] array, with x,y,z,radius on the second part; not transformed, but local!
693 * To obtain balls in world coordinates, calibrated, use getWorldBalls().
695 public double[][] getBalls() {
696 if (-1 == n_points
) setupForDisplay(); // reload
697 final double[][] b
= new double[n_points
][4];
698 for (int i
=0; i
<n_points
; i
++) {
701 b
[i
][2] = layer_set
.getLayer(p_layer
[i
]).getZ();
702 b
[i
][3] = p_width
[i
];
707 /** Returns a [n_points][4] array, with x,y,z,radius on the second part, in world coordinates (that is, transformed with this AffineTransform and calibrated with the containing LayerSet's calibration). */
708 public double[][] getWorldBalls() {
709 if (-1 == n_points
) setupForDisplay(); // reload
710 final double[][] b
= new double[n_points
][4];
711 final Calibration cal
= getLayerSet().getCalibrationCopy();
712 final int sign
= cal
.pixelDepth
< 0 ?
-1 : 1;
713 for (int i
=0; i
<n_points
; i
++) {
714 final Point2D
.Double po
= transformPoint(p
[0][i
], p
[1][i
]); // bring to world coordinates
715 b
[i
][0] = po
.x
* cal
.pixelWidth
;
716 b
[i
][1] = po
.y
* cal
.pixelHeight
;
717 b
[i
][2] = layer_set
.getLayer(p_layer
[i
]).getZ() * cal
.pixelWidth
* sign
;
718 b
[i
][3] = p_width
[i
] * cal
.pixelWidth
;
723 /** Returns a Point3f for every x,y,z ball, in calibrated world space. */
724 public List
<Point3f
> asWorldPoints() {
725 final ArrayList
<Point3f
> ps
= new ArrayList
<Point3f
>();
726 for (final double[] d
: getWorldBalls()) {
727 ps
.add(new Point3f((float)d
[0], (float)d
[1], (float)d
[2]));
732 public void exportSVG(StringBuffer data
, double z_scale
, String indent
) {
733 if (-1 == n_points
) setupForDisplay(); // reload
734 if (0 == n_points
) return;
735 String in
= indent
+ "\t";
736 String
[] RGB
= Utils
.getHexRGBColor(color
);
737 final double[] a
= new double[6];
739 data
.append(indent
).append("<ball_ob\n>")
740 .append(in
).append("id=\"").append(id
).append("\"")
741 .append(in
).append("transform=\"matrix(").append(a
[0]).append(',')
742 .append(a
[1]).append(',')
743 .append(a
[2]).append(',')
744 .append(a
[3]).append(',')
745 .append(a
[4]).append(',')
746 .append(a
[5]).append(")\"\n")
747 .append(in
).append("style=\"fill:none;stroke-opacity:").append(alpha
).append(";stroke:#").append(RGB
[0]).append(RGB
[1]).append(RGB
[2]).append(";stroke-width:1.0px;stroke-opacity:1.0\"\n")
748 .append(in
).append("links=\"")
750 if (null != hs_linked
&& 0 != hs_linked
.size()) {
752 int len
= hs_linked
.size();
753 for (final Displayable d
: hs_linked
) {
754 data
.append(d
.getId());
755 if (ii
!= len
-1) data
.append(",");
760 .append(indent
).append(">\n");
761 for (int i
=0; i
<n_points
; i
++) {
762 data
.append(in
).append("<ball x=\"").append(p
[0][i
]).append("\" y=\"").append(p
[1][0]).append("\" z=\"").append(layer_set
.getLayer(p_layer
[i
]).getZ() * z_scale
).append("\" r=\"").append(p_width
[i
]).append("\" />\n");
764 data
.append(indent
).append("</ball_ob>\n");
767 /** Similar to exportSVG but the layer_id is saved instead of the z. The convention is my own, a ball_ob that contains ball objects and links. */
769 public void exportXML(final StringBuilder sb_body
, final String indent
, final XMLOptions options
) {
770 if (-1 == n_points
) setupForDisplay(); // reload
771 //if (0 == n_points) return;
772 final String in
= indent
+ "\t";
773 final String
[] RGB
= Utils
.getHexRGBColor(color
);
774 sb_body
.append(indent
).append("<t2_ball\n");
775 super.exportXML(sb_body
, in
, options
);
776 if (!fill_paint
) sb_body
.append(in
).append("fill=\"").append(fill_paint
).append("\"\n"); // otherwise no need
777 sb_body
.append(in
).append("style=\"fill:none;stroke-opacity:").append(alpha
).append(";stroke:#").append(RGB
[0]).append(RGB
[1]).append(RGB
[2]).append(";stroke-width:1.0px;\"\n")
779 sb_body
.append(indent
).append(">\n");
780 for (int i
=0; i
<n_points
; i
++) {
781 sb_body
.append(in
).append("<t2_ball_ob x=\"").append(p
[0][i
]).append("\" y=\"").append(p
[1][i
]).append("\" layer_id=\"").append(p_layer
[i
]).append("\" r=\"").append(p_width
[i
]).append("\" />\n");
783 super.restXML(sb_body
, in
, options
);
784 sb_body
.append(indent
).append("</t2_ball>\n");
787 static public void exportDTD(final StringBuilder sb_header
, final HashSet
<String
> hs
, final String indent
) {
788 final String type
= "t2_ball";
789 if (hs
.contains(type
)) return;
791 sb_header
.append(indent
).append("<!ELEMENT t2_ball (").append(Displayable
.commonDTDChildren()).append(",t2_ball_ob)>\n");
792 Displayable
.exportDTD(type
, sb_header
, hs
, indent
);
793 sb_header
.append(indent
).append("<!ATTLIST t2_ball fill NMTOKEN #REQUIRED>\n")
794 .append(indent
).append("<!ELEMENT t2_ball_ob EMPTY>\n")
795 .append(indent
).append("<!ATTLIST t2_ball_ob x NMTOKEN #REQUIRED>\n")
796 .append(indent
).append("<!ATTLIST t2_ball_ob y NMTOKEN #REQUIRED>\n")
797 .append(indent
).append("<!ATTLIST t2_ball_ob r NMTOKEN #REQUIRED>\n")
798 .append(indent
).append("<!ATTLIST t2_ball_ob layer_id NMTOKEN #REQUIRED>\n")
802 /** */ // this may be inaccurate
804 public boolean paintsAt(Layer layer
) {
805 if (!super.paintsAt(layer
)) return false;
806 // find previous and next
807 final long lid_previous
= layer_set
.previous(layer
).getId(); // never null, may be the same though
808 final long lid_next
= layer_set
.next(layer
).getId(); // idem
809 final long lid
= layer
.getId();
810 for (int i
=0; i
<p_layer
.length
; i
++) {
811 if (lid
== p_layer
[i
] || lid_previous
== p_layer
[i
] || lid_next
== p_layer
[i
]) return true;
816 /** Returns information on the number of ball objects per layer. */
817 public String
getInfo() {
818 // group balls by layer
819 final HashMap
<Long
,ArrayList
<Integer
>> ht
= new HashMap
<Long
,ArrayList
<Integer
>>();
820 for (int i
=0; i
<n_points
; i
++) {
821 ArrayList
<Integer
> al
= ht
.get(new Long(p_layer
[i
]));
823 al
= new ArrayList
<Integer
>();
824 ht
.put(p_layer
[i
], al
);
829 final StringBuilder sb1
= new StringBuilder("Ball id: ").append(this.id
).append('\n');
830 final StringBuilder sb
= new StringBuilder();
831 for (final Map
.Entry
<Long
,ArrayList
<Integer
>> entry
: ht
.entrySet()) {
832 final long lid
= entry
.getKey().longValue();
833 final ArrayList
<Integer
> al
= entry
.getValue();
834 sb
.append("\tLayer ").append(this.layer_set
.getLayer(lid
).toString()).append(":\n");
835 sb
.append("\t\tcount : ").append(al
.size()).append('\n');
838 for (final Integer i
: al
) {
839 average
+= p_width
[i
.intValue()];
841 sb
.append("\t\taverage radius: ").append(average
/ al
.size()).append('\n');
843 return sb1
.append("Total count: ").append(total
).append('\n').append(sb
).toString();
846 /** Performs a deep copy of this object, without the links. */
847 public Displayable
clone(final Project pr
, final boolean copy_id
) {
848 final long nid
= copy_id ?
this.id
: pr
.getLoader().getNextId();
849 final Ball copy
= new Ball(pr
, nid
, null != title ? title
.toString() : null, width
, height
, alpha
, this.visible
, new Color(color
.getRed(), color
.getGreen(), color
.getBlue()), this.locked
, (AffineTransform
)this.at
.clone());
850 // links are left null
852 if (-1 == n_points
) setupForDisplay(); // load data
853 copy
.n_points
= n_points
;
854 copy
.p
= new double[][]{(double[])this.p
[0].clone(), (double[])this.p
[1].clone()};
855 copy
.p_layer
= (long[])this.p_layer
.clone();
856 copy
.p_width
= (double[])this.p_width
.clone();
857 copy
.addToDatabase();
862 /** Generate a globe of radius 1.0 that can be used for any Ball. First dimension is Z, then comes a double array x,y. Minimal accepted meridians and parallels is 3.*/
863 static public double[][][] generateGlobe(int meridians
, int parallels
) {
864 if (meridians
< 3) meridians
= 3;
865 if (parallels
< 3) parallels
= 3;
867 -first loop makes horizontal circle using meridian points.
868 -second loop scales it appropiately and makes parallels.
869 Both loops are common for all balls and so should be done just once.
870 Then this globe can be properly translocated and resized for each ball.
872 // a circle of radius 1
873 double angle_increase
= 2*Math
.PI
/ meridians
;
874 double temp_angle
= 0;
875 final double[][] xy_points
= new double[meridians
+1][2]; //plus 1 to repeat last point
876 xy_points
[0][0] = 1; // first point
878 for (int m
=1; m
<meridians
; m
++) {
879 temp_angle
= angle_increase
*m
;
880 xy_points
[m
][0] = Math
.cos(temp_angle
);
881 xy_points
[m
][1] = Math
.sin(temp_angle
);
883 xy_points
[xy_points
.length
-1][0] = 1; // last point
884 xy_points
[xy_points
.length
-1][1] = 0;
886 // Build parallels from circle
887 angle_increase
= Math
.PI
/ parallels
; // = 180 / parallels in radians
888 //final double angle90 = Math.toRadians(90);
889 final double[][][] xyz
= new double[parallels
+1][xy_points
.length
][3];
890 for (int p
=1; p
<xyz
.length
-1; p
++) {
891 double radius
= Math
.sin(angle_increase
*p
);
892 double Z
= Math
.cos(angle_increase
*p
);
893 for (int mm
=0; mm
<xyz
[0].length
-1; mm
++) {
894 //scaling circle to appropriate radius, and positioning the Z
895 xyz
[p
][mm
][0] = xy_points
[mm
][0] * radius
;
896 xyz
[p
][mm
][1] = xy_points
[mm
][1] * radius
;
899 xyz
[p
][xyz
[0].length
-1][0] = xyz
[p
][0][0]; //last one equals first one
900 xyz
[p
][xyz
[0].length
-1][1] = xyz
[p
][0][1];
901 xyz
[p
][xyz
[0].length
-1][2] = xyz
[p
][0][2];
904 // south and north poles
905 for (int ns
=0; ns
<xyz
[0].length
; ns
++) {
906 xyz
[0][ns
][0] = 0; //south pole
909 xyz
[xyz
.length
-1][ns
][0] = 0; //north pole
910 xyz
[xyz
.length
-1][ns
][1] = 0;
911 xyz
[xyz
.length
-1][ns
][2] = -1;
918 /** Put all balls as a single 'mesh'; the returned list contains all faces as three consecutive Point3f. The mesh is also translated by x,y,z of this Displayable.*/
919 public List
<Point3f
> generateTriangles(final double scale
, final double[][][] globe
) {
921 Class
.forName("javax.vecmath.Point3f");
922 } catch (ClassNotFoundException cnfe
) {
923 Utils
.log("Java3D is not installed.");
926 final Calibration cal
= layer_set
.getCalibrationCopy();
927 // modify the globe to fit each ball's radius and x,y,z position
928 final ArrayList
<Point3f
> list
= new ArrayList
<Point3f
>();
930 // local pointers, since they may be transformed
931 double[][] p
= this.p
;
932 double[] p_width
= this.p_width
;
933 if (!this.at
.isIdentity()) {
934 final Object
[] ob
= getTransformedData();
935 p
= (double[][])ob
[0];
936 p_width
= (double[])ob
[1];
938 final int sign
= cal
.pixelDepth
< 0 ?
-1 : 1;
940 for (int i
=0; i
<n_points
; i
++) {
941 // create local globe for the ball, and translate it to z,y,z
942 final double[][][] ball
= new double[globe
.length
][globe
[0].length
][3];
943 for (int z
=0; z
<ball
.length
; z
++) {
944 for (int k
=0; k
<ball
[0].length
; k
++) {
945 // the line below says: to each globe point, multiply it by the radius of the particular ball, then translate to the ball location, then translate to this Displayable's location, then scale to the Display3D scale.
946 ball
[z
][k
][0] = (globe
[z
][k
][0] * p_width
[i
] + p
[0][i
]) * scale
* cal
.pixelWidth
;
947 ball
[z
][k
][1] = (globe
[z
][k
][1] * p_width
[i
] + p
[1][i
]) * scale
* cal
.pixelHeight
;
948 ball
[z
][k
][2] = (globe
[z
][k
][2] * p_width
[i
] + layer_set
.getLayer(p_layer
[i
]).getZ()) * scale
* cal
.pixelWidth
* sign
; // not pixelDepth, see day notes 20080227. Because pixelDepth is in microns/px, not in px/microns, and the z coord here is taken from the z of the layer, which is in pixels.
951 // create triangular faces and add them to the list
952 for (int z
=0; z
<ball
.length
-1; z
++) { // the parallels
953 for (int k
=0; k
<ball
[0].length
-1; k
++) { // meridian points
954 // half quadrant (a triangle)
955 list
.add(new Point3f((float)ball
[z
][k
][0], (float)ball
[z
][k
][1], (float)ball
[z
][k
][2]));
956 list
.add(new Point3f((float)ball
[z
+1][k
+1][0], (float)ball
[z
+1][k
+1][1], (float)ball
[z
+1][k
+1][2]));
957 list
.add(new Point3f((float)ball
[z
+1][k
][0], (float)ball
[z
+1][k
][1], (float)ball
[z
+1][k
][2]));
958 // the other half quadrant
959 list
.add(new Point3f((float)ball
[z
][k
][0], (float)ball
[z
][k
][1], (float)ball
[z
][k
][2]));
960 list
.add(new Point3f((float)ball
[z
][k
+1][0], (float)ball
[z
][k
+1][1], (float)ball
[z
][k
+1][2]));
961 list
.add(new Point3f((float)ball
[z
+1][k
+1][0], (float)ball
[z
+1][k
+1][1], (float)ball
[z
+1][k
+1][2]));
963 // the Point3f could be initialized through reflection, by getting the Construntor from the Class and calling new Instance(new Object[]{new Double(x), new Double(y), new Double(z)), so it would compile even in the absence of java3d
970 private final Object
[] getTransformedData() {
971 return getTransformedData(null);
974 /** Apply the AffineTransform to a copy of the points and return the arrays. */
975 private final Object
[] getTransformedData(final AffineTransform additional
) {
977 final double[][] p
= transformPoints(this.p
, additional
);
978 // create points to represent the point where the radius ends. Since these are abstract spheres, there's no need to consider a second point that would provide the shear. To capture both the X and Y axis deformations, I use a diagonal point which sits at (x,y) => (p[0][i] + p_width[i], p[1][i] + p_width[i])
979 double[][] pw
= new double[2][n_points
];
980 for (int i
=0; i
<n_points
; i
++) {
981 pw
[0][i
] = this.p
[0][i
] + p_width
[i
]; //built relative to the untransformed points!
982 pw
[1][i
] = this.p
[1][i
] + p_width
[i
];
984 pw
= transformPoints(pw
, additional
);
985 final double[] p_width
= new double[n_points
];
986 for (int i
=0; i
<n_points
; i
++) {
987 // plain average of differences in X and Y axis, relative to the transformed points.
988 p_width
[i
] = (Math
.abs(pw
[0][i
] - p
[0][i
]) + Math
.abs(pw
[1][i
] - p
[1][i
])) / 2;
990 return new Object
[]{p
, p_width
};
993 /** @param area is expected in world coordinates. */
994 public boolean intersects(final Area area
, final double z_first
, final double z_last
) {
995 // find lowest and highest Z
996 double min_z
= Double
.MAX_VALUE
;
998 for (int i
=0; i
<n_points
; i
++) {
999 double laz
=layer_set
.getLayer(p_layer
[i
]).getZ();
1000 if (laz
< min_z
) min_z
= laz
;
1001 if (laz
> max_z
) max_z
= laz
;
1003 if (z_last
< min_z
|| z_first
> max_z
) return false;
1005 for (int i
=0; i
<n_points
; i
++) {
1006 final Rectangle
[] rec
= getSubPerimeters(layer_set
.getLayer(p_layer
[i
]));
1007 for (int k
=0; k
<rec
.length
; k
++) {
1008 Area a
= new Area(rec
[k
]); // subperimeters already in world coords
1010 Rectangle r
= a
.getBounds();
1011 if (0 != r
.width
&& 0 != r
.height
) return true;
1018 synchronized public Area
getAreaAt(final Layer layer
) {
1019 final Area a
= new Area();
1020 for (int i
=0; i
<n_points
; i
++) {
1021 if (p_layer
[i
] != layer
.getId()) continue;
1022 a
.add(new Area(new Ellipse2D
.Float((float)(p
[0][i
] - p_width
[i
]/2), (float)(p
[1][i
] - p_width
[i
]/2), (float)p_width
[i
], (float)p_width
[i
])));
1024 a
.transform(this.at
);
1029 protected boolean isRoughlyInside(final Layer layer
, final Rectangle r
) {
1030 if (0 == n_points
) return false;
1032 final Rectangle box
= this.at
.createInverse().createTransformedShape(r
).getBounds();
1033 for (int i
=0; i
<n_points
; i
++) {
1034 if (box
.contains(p
[0][i
], p
[1][i
])) return true;
1036 } catch (NoninvertibleTransformException nite
) {
1037 IJError
.print(nite
);
1042 /** Returns a listing of all balls contained here, one per row with index, x, y, z, and radius, all calibrated.
1043 * 'name-id' is a column that displays the title of this Ball object only when such title is purely a number.
1046 public ResultsTable
measure(ResultsTable rt
) {
1047 if (-1 == n_points
) setupForDisplay(); //reload
1048 if (0 == n_points
) return rt
;
1049 if (null == rt
) rt
= Utils
.createResultsTable("Ball results", new String
[]{"id", "index", "x", "y", "z", "radius", "name-id"});
1050 final Object
[] ob
= getTransformedData();
1051 double[][] p
= (double[][])ob
[0];
1052 double[] p_width
= (double[])ob
[1];
1053 final Calibration cal
= layer_set
.getCalibration();
1054 for (int i
=0; i
<n_points
; i
++) {
1055 rt
.incrementCounter();
1056 rt
.addLabel("units", cal
.getUnit());
1057 rt
.addValue(0, this.id
);
1058 rt
.addValue(1, i
+1);
1059 rt
.addValue(2, p
[0][i
] * cal
.pixelWidth
);
1060 rt
.addValue(3, p
[1][i
] * cal
.pixelHeight
);
1061 rt
.addValue(4, layer_set
.getLayer(p_layer
[i
]).getZ() * cal
.pixelWidth
);
1062 rt
.addValue(5, p_width
[i
] * cal
.pixelWidth
);
1063 rt
.addValue(6, getNameId());
1069 Class
<?
> getInternalDataPackageClass() {
1070 return DPBall
.class;
1074 Object
getDataPackage() {
1075 return new DPBall(this);
1078 static private final class DPBall
extends Displayable
.DataPackage
{
1080 final double[] p_width
;
1081 final long[] p_layer
;
1083 DPBall(final Ball ball
) {
1085 // store copies of all arrays
1086 this.p
= new double[][]{Utils
.copy(ball
.p
[0], ball
.n_points
), Utils
.copy(ball
.p
[1], ball
.n_points
)};
1087 this.p_width
= Utils
.copy(ball
.p_width
, ball
.n_points
);
1088 this.p_layer
= new long[ball
.n_points
]; System
.arraycopy(ball
.p_layer
, 0, this.p_layer
, 0, ball
.n_points
);
1090 final boolean to2(final Displayable d
) {
1092 final Ball ball
= (Ball
)d
;
1093 final int len
= p
[0].length
; // == n_points, since it was cropped on copy
1094 ball
.p
= new double[][]{Utils
.copy(p
[0], len
), Utils
.copy(p
[1], len
)};
1095 ball
.n_points
= p
[0].length
;
1096 ball
.p_layer
= new long[len
]; System
.arraycopy(p_layer
, 0, ball
.p_layer
, 0, len
);
1097 ball
.p_width
= Utils
.copy(p_width
, len
);
1102 /** Retain the data within the layer range, and throw out all the rest. */
1103 synchronized public boolean crop(List
<Layer
> range
) {
1104 if (-1 == n_points
) setupForDisplay();
1105 HashSet
<Long
> lids
= new HashSet
<Long
>();
1106 for (Layer l
: range
) {
1107 lids
.add(l
.getId());
1109 for (int i
=0; i
<n_points
; i
++) {
1110 if (!lids
.contains(p_layer
[i
])) {
1115 calculateBoundingBox(true, null);
1119 synchronized protected boolean layerRemoved(Layer la
) {
1120 super.layerRemoved(la
);
1121 for (int i
=0; i
<p_layer
.length
; i
++) {
1122 if (la
.getId() == p_layer
[i
]) {
1130 synchronized public boolean apply(final Layer la
, final Area roi
, final mpicbg
.models
.CoordinateTransform ict
) throws Exception
{
1132 mpicbg
.models
.CoordinateTransform chain
= null;
1133 Area localroi
= null;
1134 AffineTransform inverse
= null;
1135 for (int i
=0; i
<n_points
; i
++) {
1136 if (p_layer
[i
] == la
.getId()) {
1137 if (null == localroi
) {
1138 inverse
= this.at
.createInverse();
1139 localroi
= roi
.createTransformedArea(inverse
);
1141 if (localroi
.contains(p
[0][i
], p
[1][i
])) {
1142 if (null == chain
) {
1143 chain
= M
.wrap(this.at
, ict
, inverse
);
1147 double ox
= p
[0][i
],
1149 // Transform the point
1150 M
.apply(chain
, p
, i
, fp
);
1151 // For radius, assume it's a point to the right of the center point
1152 fp
[0] = (float)(ox
+ p_width
[i
]);
1154 chain
.applyInPlace(fp
);
1155 p_width
[i
] = Math
.abs(fp
[0] - p
[0][i
]);
1159 if (null != chain
) calculateBoundingBox(true, la
); // may be called way too many times, but avoids lots of headaches.
1162 public boolean apply(final VectorDataTransform vdt
) throws Exception
{
1163 final float[] fp
= new float[2];
1164 final VectorDataTransform vlocal
= vdt
.makeLocalTo(this);
1165 for (int i
=0; i
<n_points
; i
++) {
1166 if (vlocal
.layer
.getId() == p_layer
[i
]) {
1167 for (final VectorDataTransform
.ROITransform rt
: vlocal
.transforms
) {
1168 if (rt
.roi
.contains(p
[0][i
], p
[1][i
])) {
1170 double ox
= p
[0][i
],
1172 // Transform the point
1173 M
.apply(rt
.ct
, p
, i
, fp
);
1174 // For radius, assume it's a point to the right of the center point
1175 fp
[0] = (float)(ox
+ p_width
[i
]);
1177 rt
.ct
.applyInPlace(fp
);
1178 p_width
[i
] = Math
.sqrt(Math
.pow(fp
[0] - p
[0][i
], 2) + Math
.pow(fp
[1] - p
[1][i
], 2));
1184 calculateBoundingBox(true, vlocal
.layer
);
1189 synchronized public Collection
<Long
> getLayerIds() {
1190 return Utils
.asList(p_layer
, 0, n_points
);
1194 public void adjustProperties() {
1195 GenericDialog gd
= makeAdjustPropertiesDialog(); // in superclass
1196 gd
.addCheckbox("Paint as outlines", !fill_paint
);
1197 gd
.addCheckbox("Apply paint mode to all Ball instances", false);
1199 if (gd
.wasCanceled()) return;
1200 // superclass processing
1201 final Displayable
.DoEdit prev
= processAdjustPropertiesDialog(gd
);
1203 final boolean fp
= !gd
.getNextBoolean();
1204 final boolean to_all
= gd
.getNextBoolean();
1206 for (final ZDisplayable zd
: layer_set
.getZDisplayables()) {
1207 if (zd
.getClass() == Ball
.class) {
1210 b
.updateInDatabase("fill_paint");
1213 Display
.repaint(layer_set
);
1214 } else if (fill_paint
!= fp
) {
1215 prev
.add("fill_paint", fp
);
1216 this.fill_paint
= fp
; // change it after storing state in DoEdit
1217 updateInDatabase("fill_paint");
1220 // Add current step, with the same modified keys
1221 DoEdit current
= new DoEdit(this).init(prev
);
1222 if (isLinked()) current
.add(new Displayable
.DoTransforms().addAll(getLinkedGroup(null)));
1223 getLayerSet().addEditStep(current
);
1226 /** Set the x,y,radius raw pixel values for the ball at index i.
1227 * When done setting values, call repaint(true, null).
1228 * @throws IndexOutOfBoundsException if i < 0 or i >= the number of points. */
1229 public void set(final int i
, final double x
, final double y
, final Layer la
, final double radius
) {
1230 if (i
< 0 || i
> n_points
) throw new IndexOutOfBoundsException("i must be 0<=i<n_points, but it is " + i
);
1233 p_layer
[i
] = la
.getId();
1234 p_width
[i
] = radius
;
1237 /** Return the number of balls. */
1238 public int getCount() {
1242 /** Set the radius (raw pixel value) for the ball at index i.
1243 * When done setting values, call repaint(true, null).
1244 * @throws IndexOutOfBoundsException if i < 0 or i >= the number of points. */
1245 public void setRadius(final int i
, final double radius
) {
1246 if (i
< 0 || i
> n_points
) throw new IndexOutOfBoundsException("i must be 0<=i<n_points, but it is " + i
);
1247 p_width
[i
] = radius
;