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
;
27 import ij
.gui
.GenericDialog
;
28 import ini
.trakem2
.ControlWindow
;
29 import ini
.trakem2
.Project
;
30 import ini
.trakem2
.persistence
.DBObject
;
31 import ini
.trakem2
.persistence
.XMLOptions
;
32 import ini
.trakem2
.tree
.LayerThing
;
33 import ini
.trakem2
.utils
.IJError
;
34 import ini
.trakem2
.utils
.M
;
35 import ini
.trakem2
.utils
.Utils
;
37 import java
.awt
.Color
;
38 import java
.awt
.Polygon
;
39 import java
.awt
.Rectangle
;
40 import java
.awt
.geom
.AffineTransform
;
41 import java
.awt
.geom
.Area
;
42 import java
.awt
.geom
.NoninvertibleTransformException
;
43 import java
.util
.ArrayList
;
44 import java
.util
.Collection
;
45 import java
.util
.Comparator
;
46 import java
.util
.HashMap
;
47 import java
.util
.HashSet
;
48 import java
.util
.Iterator
;
49 import java
.util
.List
;
52 import mpicbg
.models
.NoninvertibleModelException
;
54 public final class Layer
extends DBObject
implements Bucketable
, Comparable
<Layer
> {
56 private final ArrayList
<Displayable
> al_displayables
= new ArrayList
<Displayable
>();
57 /** For fast search. */
59 private HashMap
<Displayable
,HashSet
<Bucket
>> db_map
= null;
62 private double thickness
= 0;
64 private LayerSet parent
;
66 /** Compare layers by Z. */
67 static public final Comparator
<Layer
> COMPARATOR
= new Comparator
<Layer
>() {
69 public final int compare(final Layer l1
, final Layer l2
) {
70 if (l1
== l2
) return 0; // the same layer
71 if (l1
.getZ() < l2
.getZ()) return -1;
72 return 1; // even if same Z, prefer the second
75 public final boolean equals(final Object ob
) { return this == ob
; }
78 public Layer(final Project project
, final double z
, final double thickness
, final LayerSet parent
) {
81 this.thickness
= thickness
;
86 /** Reconstruct from database*/
87 public Layer(final Project project
, final long id
, final double z
, final double thickness
) {
90 this.thickness
= thickness
;
94 /** Reconstruct from XML file. */
95 public Layer(final Project project
, final long id
, final HashMap
<String
,String
> ht_attributes
) {
100 if (null != (data
= ht_attributes
.get("z"))) this.z
= Double
.parseDouble(data
);
101 else Displayable
.xmlError(this, "z", this.z
);
102 if (null != (data
= ht_attributes
.get("thickness"))) this.thickness
= Double
.parseDouble(data
);
103 else Displayable
.xmlError(this, "thickness", this.thickness
);
106 /** Creates a new Layer asking for z and thickness, and adds it to the parent and returns it. Returns null if the dialog was canceled.*/
107 static public Layer
create(final Project project
, final LayerSet parent
) {
108 if (null == parent
) return null;
109 final GenericDialog gd
= ControlWindow
.makeGenericDialog("New Layer");
110 gd
.addMessage("In pixels:"); // TODO set elsewhere the units!
111 gd
.addNumericField("z coordinate: ", 0.0D
, 3);
112 gd
.addNumericField("thickness: ", 1.0D
, 3);
114 if (gd
.wasCanceled()) return null;
116 final double z
= gd
.getNextNumber();
117 final double thickness
= gd
.getNextNumber();
118 if (Double
.isNaN(z
) || Double
.isNaN(thickness
)) return null;
119 final Layer layer
= new Layer(project
, z
, thickness
, parent
);
121 parent
.recreateBuckets(layer
, true);
123 } catch (final Exception e
) {}
127 /** Pops up a dialog to choose the first Z coord, the thickness, the number of layers,
128 * and whether to skip the creation of any layers whose Z and thickness match
129 * that of existing layers.
130 * @return The newly created layers. */
131 static public List
<Layer
> createMany(final Project project
, final LayerSet parent
) {
132 if (null == parent
) return null;
133 final GenericDialog gd
= ControlWindow
.makeGenericDialog("Many new layers");
134 gd
.addNumericField("First Z coord: ", 0, 3);
135 gd
.addNumericField("thickness: ", 1.0, 3);
136 gd
.addNumericField("Number of layers: ", 1, 0);
137 gd
.addCheckbox("Skip existing layers", true);
139 if (gd
.wasCanceled()) return null;
140 // start iteration to add layers
141 double z
= gd
.getNextNumber();
142 final double thickness
= gd
.getNextNumber();
143 final int n_layers
= (int)gd
.getNextNumber();
144 final boolean skip
= gd
.getNextBoolean();
146 Utils
.log("Can't create layers with negative thickness");
150 Utils
.log("Invalid number of layers");
153 final List
<Layer
> layers
= new ArrayList
<Layer
>(n_layers
);
154 for (int i
=0; i
<n_layers
; i
++) {
157 // Check if layer exists
158 la
= parent
.getLayer(z
);
159 if (null == la
) la
= new Layer(project
, z
, thickness
, parent
);
161 } else la
= new Layer(project
, z
, thickness
, parent
);
163 parent
.addSilently(la
);
168 parent
.recreateBuckets(layers
, true); // all empty
169 // update the scroller of currently open Displays
170 Display
.updateLayerScroller(parent
);
174 /** Returns a title such as 018__4-K4_2__z1.67 , which is [layer_set index]__[title]__[z coord] . The ordinal number starts at 1 and finishes at parent's length, inclusive. */
175 public String
getPrintableTitle() {
176 final LayerThing lt
= project
.findLayerThing(this);
177 if (null == lt
) return toString();
178 String title
= lt
.getTitle();
179 if (null == title
) title
= "";
180 else title
= title
.replace(' ', '_');
181 final StringBuilder sb
= new StringBuilder().append(parent
.indexOf(this) + 1);
182 final int s_size
= Integer
.toString(parent
.size()).length();
183 while (sb
.length() < s_size
) {
186 sb
.append('_').append('_').append(title
).append('_').append('_').append('z').append(Utils
.cutNumber(this.z
, 3, true));
187 return sb
.toString();
191 public String
toString() {
192 if (null == parent
) return new StringBuilder("z=").append(Utils
.cutNumber(z
, 4)).toString();
193 //return "z=" + Utils.cutNumber(z / parent.getCalibration().pixelDepth * z !!!?? I don't have the actual depth to correct with.
194 //return "z=" + Utils.cutNumber(z, 4);
196 final String unit
= parent
.getCalibration().getUnit();
197 if (unit
.equals("pixel")) {
198 return "z=" + Utils
.cutNumber(z
, 4);
200 return "z=" + (z
* parent
.getCalibration().pixelWidth
) + " " + unit
201 + " (" + Utils
.cutNumber(z
, 4) + " px)";
204 /** Add a displayable and update all Display instances showing this Layer. */
205 public void add(final Displayable displ
) { add(displ
, true); }
207 public void add(final Displayable displ
, final boolean update_displays
) {
208 add(displ
, update_displays
, true);
211 public void add(final Displayable displ
, final boolean update_displays
, final boolean update_db
) {
212 if (null == displ
|| -1 != al_displayables
.indexOf(displ
)) return;
213 if (displ
.getProject() != this.project
)
214 throw new IllegalArgumentException("Layer rejected a Displayable: belongs to a different project.");
217 final Displayable
[] d
= new Displayable
[al_displayables
.size()];
218 al_displayables
.toArray(d
);
221 if (displ
instanceof Patch
) {
222 // find last Patch (which start at 0)
223 for (i
=0; i
<d
.length
; i
++) {
224 if (d
[i
] instanceof Patch
) { j
= i
;}
230 al_displayables
.add(displ
); // at the end
231 stack_index
= d
.length
;
233 al_displayables
.add(j
, displ
);
238 al_displayables
.add(0, displ
); // at the very beggining
241 } else if (displ
instanceof Profile
) {
242 // find first LayerSet or if none, first DLabel, add before it
243 for (i
=d
.length
-1; i
>-1; i
--) {
244 if (! (d
[i
] instanceof DLabel
|| d
[i
] instanceof LayerSet
)) { j
= i
; break; }
248 if (j
>= d
.length
) { al_displayables
.add(displ
); stack_index
= d
.length
; }
249 else { al_displayables
.add(j
, displ
); stack_index
= j
; }
251 // no labels or LayerSets
252 al_displayables
.add(displ
); // at the end
253 stack_index
= d
.length
;
255 } else if (displ
instanceof LayerSet
) {
256 // find first DLabel, add before it
257 for (i
=d
.length
-1; i
>-1; i
--) {
258 if (! (d
[i
] instanceof DLabel
)) { j
= i
; break; }
261 j
++; // add it after the non-label one, displacing the label one position
262 if (j
>= d
.length
) { al_displayables
.add(displ
); stack_index
= d
.length
; } // at the end
263 else { al_displayables
.add(j
, displ
); stack_index
= j
; }
266 al_displayables
.add(displ
); // at the end
267 stack_index
= d
.length
;
271 al_displayables
.add(displ
); // at the end
272 stack_index
= d
.length
;
276 updateInDatabase("stack_index"); // of the displayables ...
277 displ
.setLayer(this);
279 displ
.setLayer(this, false);
282 // insert into bucket
284 if (d
.length
== stack_index
) {
286 root
.put(stack_index
, displ
, this, db_map
);
288 // add as last first, then update
289 root
.put(d
.length
, displ
, this, db_map
);
290 // find and update the range of affected Displayable objects
291 root
.updateRange(this, displ
, stack_index
, d
.length
); // first to last indices affected
295 if (update_displays
) {
296 Display
.add(this, displ
);
301 public HashMap
<Displayable
, HashSet
<Bucket
>> getBucketMap(final Layer layer
) { // ignore layer
305 /** Used for reconstruction purposes. Assumes the displ are given in the proper order! */
306 public void addSilently(final DBObject displ
) { // why DBObject and not Displayable ?? TODO
307 if (null == displ
|| -1 != al_displayables
.indexOf(displ
)) return;
309 ((Displayable
)displ
).setLayer(this, false);
310 al_displayables
.add((Displayable
)displ
);
311 } catch (final Exception e
) {
312 Utils
.log("Layer.addSilently: Not a Displayable/LayerSet, not adding DBObject id=" + displ
.getId());
317 /** Will recreate the buckets; if you intend to remove many, use "removeAll" instead,
318 * so that the expensive operation of recreating the buckets is done only once. */
319 public synchronized boolean remove(final Displayable displ
) {
320 if (null == displ
|| null == al_displayables
) {
321 Utils
.log2("Layer can't remove Displayable " + displ
.getId());
324 final int old_stack_index
= al_displayables
.indexOf(displ
);
325 if (-1 == old_stack_index
) {
326 Utils
.log2("Layer.remove: not found: " + displ
);
329 al_displayables
.remove(old_stack_index
);
330 if (null != root
) recreateBuckets();
331 parent
.removeFromOffscreens(this);
332 Display
.remove(this, displ
);
336 /** Remove a set of children. Does not destroy the children nor remove them from the database, only from the Layer and the Display. */
337 public synchronized boolean removeAll(final Set
<Displayable
> ds
) {
338 if (null == ds
|| null == al_displayables
) return false;
339 // Ensure list is iterated only once: don't ask for index every time!
340 for (final Iterator
<Displayable
> it
= al_displayables
.iterator(); it
.hasNext(); ) {
341 final Displayable d
= it
.next();
342 if (ds
.contains(d
)) {
344 parent
.removeFromOffscreens(this);
345 Display
.remove(this, d
);
348 if (null != root
) recreateBuckets();
349 Display
.updateVisibleTabs(this.project
);
353 /** Used for reconstruction purposes. */
354 public void setParentSilently(final LayerSet layer_set
) {
355 if (layer_set
== this.parent
) return;
356 this.parent
= layer_set
;
357 //Utils.log("Layer " +id + ": I have as new parent the LayerSet " + layer_set.getId());
360 public void setParent(final LayerSet layer_set
) { // can be null
361 if (layer_set
== this.parent
) return;
362 this.parent
= layer_set
;
363 updateInDatabase("layer_set_id");
366 public LayerSet
getParent() {
370 public double getZ() { return z
; }
371 public double getThickness() { return thickness
; }
373 public double getCalibratedZ() {
374 return z
* parent
.getCalibration().pixelWidth
; // not pixelDepth ...
376 public double getCalibratedThickness() {
377 return thickness
* parent
.getCalibration().pixelWidth
; // not pixelDepth ...
380 /** Remove this layer and all its contents from the project. */
382 public boolean remove(final boolean check
) {
384 if (check
&& !Utils
.check("Really delete " + this.toString() + " and all its children?")) return false;
385 // destroy the Display objects that show this layer
386 Display
.remove(this);
387 // proceed to remove all the children
388 final Displayable
[] displ
= new Displayable
[al_displayables
.size()]; // to avoid concurrent modifications
389 al_displayables
.toArray(displ
);
390 for (int i
=0; i
<displ
.length
; i
++) {
391 if (!displ
[i
].remove2(false)) { // will call back Layer.remove(Displayable)
392 Utils
.log("Could not delete " + displ
[i
]);
396 al_displayables
.clear();
397 // remove from the parent
398 /*can't ever be null//if (null != parent) */
400 Display
.updateLayerScroller(parent
);
401 removeFromDatabase();
402 } catch (final Exception e
) { IJError
.print(e
); return false; }
406 public void setZ(final double z
) {
407 if (Double
.isNaN(z
) || z
== this.z
) return;
409 if (null != parent
) {
410 parent
.reposition(this);
411 // fix ordering in the trees (must be done after repositioning in the parent)
412 final LayerThing lt
= project
.findLayerThing(this);
414 final LayerThing p
= (LayerThing
)lt
.getParent();
416 p
.removeChild(lt
); // does not affect the database
417 p
.addChild(lt
); // idem
421 updateInDatabase("z");
424 public void setThickness(final double thickness
) {
425 if (Double
.isNaN(thickness
) || thickness
== this.thickness
) return;
426 this.thickness
= thickness
;
427 updateInDatabase("thickness");
430 public boolean contains(final int x
, final int y
, int inset
) {
431 if (inset
< 0) inset
= -inset
;
432 return x
>= inset
&& y
>= inset
&& x
<= parent
.getLayerWidth() - inset
&& y
<= parent
.getLayerHeight() - inset
;
435 public boolean contains(final Displayable displ
) {
436 return -1 != al_displayables
.indexOf(displ
);
439 /** Returns true if any of the Displayable objects are of the given class. */
440 public boolean contains(final Class
<?
> c
) {
441 for (final Object ob
: al_displayables
) {
442 if (ob
.getClass() == c
) return true;
447 /** Returns true if any of the Displayable objects are of the given class; if {@param visible_only} is true,
448 * will return true only if at least one of the matched objects is visible. */
449 public boolean contains(final Class
<?
> c
, final boolean visible_only
) {
450 for (final Displayable d
: al_displayables
) {
451 if (visible_only
&& !d
.isVisible()) continue;
452 if (d
.getClass() == c
) return true;
457 /** Count instances of the given Class. */
458 public int count(final Class
<?
> c
) {
460 for (final Object ob
: al_displayables
) {
461 if (ob
.getClass() == c
) n
++;
466 /** Checks if there are any Displayable or if any ZDisplayable paints in this layer. */
467 public boolean isEmpty() {
468 return 0 == al_displayables
.size() && parent
.isEmptyAt(this); // check for ZDisplayable painting here as well
471 /** Returns a copy of the list of Displayable objects.*/
472 synchronized public ArrayList
<Displayable
> getDisplayables() {
473 return new ArrayList
<Displayable
>(al_displayables
);
476 /** Returns the real list of displayables, not a copy. If you modify this list, Thor may ground you with His lightning. */
478 public final ArrayList
<Displayable
> getDisplayableList() {
479 return al_displayables
;
482 synchronized public int getNDisplayables() {
483 return al_displayables
.size();
486 /** Returns a list of Displayable of class c only.*/
487 synchronized public<T
extends Displayable
> ArrayList
<T
> getAll(final Class
<T
> c
) {
488 // So yes, it can be done to return a typed list of any kind: this WORKS:
489 final ArrayList
<T
> al
= new ArrayList
<T
>();
490 if (null == c
) return al
;
491 if (Displayable
.class == c
) {
492 al
.addAll((Collection
<T
>)al_displayables
); // T is Displayable
495 for (final Displayable d
: al_displayables
) {
496 if (d
.getClass() == c
) al
.add((T
)d
);
501 /** Returns a list of Displayable of class c only.*/
502 synchronized public ArrayList
<Displayable
> getDisplayables(final Class
<?
> c
) {
503 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
504 if (null == c
) return al
;
505 if (Displayable
.class == c
) {
506 al
.addAll(al_displayables
);
509 for (final Displayable d
: al_displayables
) {
510 if (d
.getClass() == c
) al
.add(d
);
515 synchronized public ArrayList
<Displayable
> getDisplayables(final Class
<?
> c
, final boolean visible_only
, final boolean instance_of
) {
516 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
517 if (null == c
) return al
;
519 for (final Displayable d
: al_displayables
) {
520 if (visible_only
&& !d
.isVisible()) continue;
521 if (c
.isAssignableFrom(d
.getClass())) al
.add(d
);
524 for (final Displayable d
: al_displayables
) {
525 if (visible_only
&& !d
.isVisible()) continue;
526 if (d
.getClass() == c
) al
.add(d
);
533 /** Returns a list of all Displayable of class c that intersect the given rectangle. */
534 public Collection
<Displayable
> getDisplayables(final Class
<?
> c
, final Rectangle roi
) {
535 return getDisplayables(c
, new Area(roi
), true, false);
538 /** Returns a list of all Displayable of class c that intersect the given area. */
539 synchronized public Collection
<Displayable
> getDisplayables(final Class
<?
> c
, final Area aroi
, final boolean visible_only
) {
540 return getDisplayables(c
, aroi
, visible_only
, false);
543 /** Check class identity by instanceof instead of equality. */
544 synchronized public Collection
<Displayable
> getDisplayables(final Class
<?
> c
, final Area aroi
, final boolean visible_only
, final boolean instance_of
) {
545 if (null != root
) return root
.find(c
, aroi
, this, visible_only
, instance_of
);
546 // Else, the slow way
547 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
548 if (Displayable
.class == c
) {
549 for (final Displayable d
: al_displayables
) {
550 if (visible_only
&& !d
.isVisible()) continue;
551 final Area area
= d
.getArea();
552 area
.intersect(aroi
);
553 final Rectangle b
= area
.getBounds();
554 if (!(0 == b
.width
|| 0 == b
.height
)) al
.add(d
);
559 for (final Displayable d
: al_displayables
) {
560 if (visible_only
&& !d
.isVisible()) continue;
561 if (c
.isAssignableFrom(d
.getClass())) {
562 final Area area
= d
.getArea();
563 area
.intersect(aroi
);
564 final Rectangle b
= area
.getBounds();
565 if (!(0 == b
.width
|| 0 == b
.height
)) al
.add(d
);
569 for (final Displayable d
: al_displayables
) {
570 if (visible_only
&& !d
.isVisible()) continue;
571 if (d
.getClass() == c
) {
572 final Area area
= d
.getArea();
573 area
.intersect(aroi
);
574 final Rectangle b
= area
.getBounds();
575 if (!(0 == b
.width
|| 0 == b
.height
)) al
.add(d
);
582 /** Check class identity with equality, so no superclasses or interfaces are possible. */
583 synchronized public ArrayList
<Displayable
> getDisplayables(final Class
<?
> c
, final boolean visible_only
) {
584 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
585 for (final Displayable d
: al_displayables
) {
586 if (d
.getClass() == c
) {
587 if (visible_only
&& !d
.isVisible()) continue;
594 public Displayable
get(final long id
) {
595 for (final Displayable d
: al_displayables
) {
596 if (d
.getId() == id
) return d
;
602 public float getLayerWidth() {
603 return parent
.getLayerWidth();
606 public float getLayerHeight() {
607 return parent
.getLayerHeight();
610 public Collection
<Displayable
> find(final double x
, final double y
) {
611 return find(x
, y
, false);
614 /** Find the Displayable objects that contain the point. */
615 synchronized public Collection
<Displayable
> find(final double x
, final double y
, final boolean visible_only
) {
616 if (null != root
) return root
.find(x
, y
, this, visible_only
);
617 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
618 for (int i
= al_displayables
.size() -1; i
>-1; i
--) {
619 final Displayable d
= (Displayable
)al_displayables
.get(i
);
620 if (visible_only
&& !d
.isVisible()) continue;
621 if (d
.contains(x
, y
)) {
628 public Collection
<Displayable
> find(final Class
<?
> c
, final double x
, final double y
) {
629 return find(c
, x
, y
, false, false);
632 /** Find the Displayable objects of Class c that contain the point, with class equality. */
633 synchronized public Collection
<Displayable
> find(final Class
<?
> c
, final double x
, final double y
, final boolean visible_only
) {
634 return find(c
, x
, y
, visible_only
, false);
636 /** Find the Displayable objects of Class c that contain the point, with instanceof if instance_of is true. */
637 synchronized public Collection
<Displayable
> find(final Class
<?
> c
, final double x
, final double y
, final boolean visible_only
, final boolean instance_of
) {
638 if (null != root
) return root
.find(c
, x
, y
, this, visible_only
, instance_of
);
639 if (Displayable
.class == c
) return find(x
, y
, visible_only
); // search among all
640 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
641 for (int i
= al_displayables
.size() -1; i
>-1; i
--) {
642 final Displayable d
= al_displayables
.get(i
);
643 if (visible_only
&& !d
.isVisible()) continue;
644 if (d
.getClass() == c
&& d
.contains(x
, y
)) {
651 public Collection
<Displayable
> find(final Rectangle r
) {
652 return find(r
, false);
655 /** Find the Displayable objects whose bounding box intersects with the given rectangle. */
656 synchronized public Collection
<Displayable
> find(final Rectangle r
, final boolean visible_only
) {
657 if (null != root
&& root
.isBetter(r
, this)) return root
.find(r
, this, visible_only
);
658 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
659 for (final Displayable d
: al_displayables
) {
660 if (visible_only
&& !d
.isVisible()) continue;
661 if (d
.getBoundingBox().intersects(r
)) {
668 synchronized public Collection
<Displayable
> find(final Class
<?
> c
, final Rectangle r
, final boolean visible_only
) {
669 return find(c
, r
, visible_only
, false);
672 /** Find the Displayable objects whose bounding box intersects with the given rectangle. */
673 synchronized public Collection
<Displayable
> find(final Class
<?
> c
, final Rectangle r
, final boolean visible_only
, final boolean instance_of
) {
674 if (Displayable
.class == c
) return find(r
, visible_only
);
675 if (null != root
&& root
.isBetter(r
, this)) return root
.find(c
, r
, this, visible_only
, instance_of
);
676 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
677 for (final Displayable d
: al_displayables
) {
678 if (visible_only
&& !d
.isVisible()) continue;
679 if (d
.getClass() != c
) continue;
680 if (d
.getBoundingBox().intersects(r
)) {
687 /** Find the Displayable objects of class 'target' whose perimeter (not just the bounding box) intersect the given Displayable (which is itself included if present in this very Layer). */
688 synchronized public Collection
<Displayable
> getIntersecting(final Displayable d
, final Class
<?
> target
) {
690 final Area area
= new Area(d
.getPerimeter());
691 if (root
.isBetter(area
.getBounds(), this)) return root
.find(area
, this, false);
693 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
694 for (int i
= al_displayables
.size() -1; i
>-1; i
--) {
695 final Object ob
= al_displayables
.get(i
);
696 if (ob
.getClass() != target
) continue;
697 final Displayable da
= (Displayable
)ob
;
698 if (d
.intersects(da
)) {
702 // remove the calling one
703 if (al
.contains(d
)) al
.remove(d
);
707 /** Returns -1 if not found. */
708 public final int indexOf(final Displayable d
) {
709 return al_displayables
.indexOf(d
);
712 /** Within its own class only.
713 * 'up' is at the last element of the ArrayList (since when painting, the first one gets painted first, and thus gets buried the most while the last paints last, on top). */
714 public void moveUp(final Displayable d
) {
715 final int i
= al_displayables
.indexOf(d
);
716 if (null == d
|| -1 == i
|| al_displayables
.size() -1 == i
) return;
717 if (al_displayables
.get(i
+1).getClass() == d
.getClass()) {
719 al_displayables
.remove(d
);
720 al_displayables
.add(i
+1, d
);
722 updateInDatabase("stack_index");
723 Display
.updatePanelIndex(d
.getLayer(), d
);
724 if (null != root
) root
.updateRange(this, d
, i
, i
+1);
727 /** Within its own class only. */
728 public void moveDown(final Displayable d
) {
729 final int i
= al_displayables
.indexOf(d
);
730 if (null == d
|| -1 == i
|| 0 == i
) return;
731 if (al_displayables
.get(i
-1).getClass() == d
.getClass()) {
733 final Displayable o
= al_displayables
.remove(i
-1);
734 al_displayables
.add(i
, o
);
736 updateInDatabase("stack_index");
737 Display
.updatePanelIndex(d
.getLayer(), d
);
738 if (null != root
) root
.updateRange(this, d
, i
-1, i
);
741 /** Within its own class only. */
742 public void moveTop(final Displayable d
) { // yes I could have made several lists and make my life easier. Whatever
743 final int i
= al_displayables
.indexOf(d
);
744 final int size
= al_displayables
.size();
745 if (null == d
|| -1 == i
|| size
-1 == i
) return;
746 final Class
<?
> c
= d
.getClass();
747 boolean done
= false;
749 for (; j
<size
; j
++) {
750 if (al_displayables
.get(j
).getClass() == c
) continue;
752 al_displayables
.remove(d
);
753 al_displayables
.add(--j
, d
); // j-1
758 // solves case of no other class present
761 al_displayables
.remove(d
);
762 al_displayables
.add(d
);
765 updateInDatabase("stack_index");
766 Display
.updatePanelIndex(d
.getLayer(), d
);
767 if (null != root
) root
.updateRange(this, d
, i
, j
);
770 /** Within its own class only. */
771 public void moveBottom(final Displayable d
) {
772 final int i
= al_displayables
.indexOf(d
);
773 if (null == d
|| -1 == i
|| 0 == i
) return;
774 final Class
<?
> c
= d
.getClass();
775 boolean done
= false;
777 for (; j
> -1; j
--) {
778 if (al_displayables
.get(j
).getClass() == c
) continue;
780 al_displayables
.remove(d
);
781 al_displayables
.add(++j
, d
); // j+1
786 // solve case of no other class present
788 al_displayables
.remove(d
);
789 al_displayables
.add(0, d
);
792 updateInDatabase("stack_index");
793 Display
.updatePanelIndex(d
.getLayer(), d
);
794 if (null != root
) root
.updateRange(this, d
, j
, i
);
797 /** Within its own class only. */
798 public boolean isTop(final Displayable d
) {
799 final int i
= al_displayables
.indexOf(d
);
800 final int size
= al_displayables
.size();
801 if (size
-1 == i
) return true;
802 if (al_displayables
.get(i
+1).getClass() == d
.getClass()) return false;
804 } // these two methods will throw an Exception if the Displayable is not found (-1 == i) (the null.getClass() *should* throw it)
805 /** Within its own class only. */
806 public boolean isBottom(final Displayable d
) {
807 final int i
= al_displayables
.indexOf(d
);
808 if (0 == i
) return true;
809 if (al_displayables
.get(i
-1).getClass() == d
.getClass()) return false;
813 /** Get the index of the given Displayable relative to the rest of its class. Beware that the order of the al_displayables is bottom at zero, top at last, but the relative index returned here is inverted: top at zero, bottom at last -to match the tabs' vertical orientation in a Display.*/
814 public int relativeIndexOf(final Displayable d
) {
815 final int k
= al_displayables
.indexOf(d
);
816 if (-1 == k
) return -1;
817 final Class
<?
> c
= d
.getClass();
818 final int size
= al_displayables
.size();
820 for (; i
<size
; i
++) {
821 if (al_displayables
.get(i
).getClass() == c
) continue;
829 Utils
.log2("relativeIndexOf: return 0");
833 /** Note: Not recursive into embedded LayerSet objects. Returns the hash set of objects whose visibility has changed. */
834 public HashSet
<Displayable
> setVisible(String type
, final boolean visible
, final boolean repaint
) {
835 type
= type
.toLowerCase();
836 if (type
.equals("image")) type
= "patch";
837 final HashSet
<Displayable
> hs
= new HashSet
<Displayable
>();
838 for (final Displayable d
: al_displayables
) {
839 if (visible
!= d
.isVisible() && d
.getClass().getName().toLowerCase().endsWith(type
)) {
840 d
.setVisible(visible
, false); // don't repaint
845 Display
.repaint(this);
847 Display
.updateCheckboxes(hs
, DisplayablePanel
.VISIBILITY_STATE
, visible
);
850 /** Returns the collection of Displayable whose visibility state has changed. */
851 public Collection
<Displayable
> setAllVisible(final boolean repaint
) {
852 final Collection
<Displayable
> col
= new ArrayList
<Displayable
>();
853 for (final Displayable d
: al_displayables
) {
854 if (!d
.isVisible()) {
855 d
.setVisible(true, repaint
);
862 /** Hide all except those whose type is in 'type' list, whose visibility flag is left unchanged. Returns the list of displayables made hidden. */
863 public HashSet
<Displayable
> hideExcept(final ArrayList
<Class
<?
>> type
, final boolean repaint
) {
864 final HashSet
<Displayable
> hs
= new HashSet
<Displayable
>();
865 for (final Displayable d
: al_displayables
) {
866 if (!type
.contains(d
.getClass()) && d
.isVisible()) {
867 d
.setVisible(false, repaint
);
875 public void exportXML(final StringBuilder sb_body
, final String indent
, final XMLOptions options
) {
876 final String in
= indent
+ "\t";
878 sb_body
.append(indent
).append("<t2_layer oid=\"").append(id
).append("\"\n")
879 .append(in
).append(" thickness=\"").append(thickness
).append("\"\n")
880 .append(in
).append(" z=\"").append(z
).append("\"\n")
882 // TODO this search is linear!
883 final LayerThing lt
= project
.findLayerThing(this);
885 if (null == lt
) title
= null;
886 else title
= lt
.getTitle();
887 if (null == title
) title
= "";
888 sb_body
.append(in
).append(" title=\"").append(title
).append("\"\n"); // TODO 'title' should be a property of the Layer, not the LayerThing. Also, the LayerThing should not exist: LayerSet and Layer should be directly presentable in a tree. They are not Things as in "objects of the sample", but rather, structural necessities such as Patch.
889 sb_body
.append(indent
).append(">\n");
890 // 2 - export children
891 if (null != al_displayables
) {
892 for (final Displayable d
: al_displayables
) {
893 d
.exportXML(sb_body
, in
, options
);
897 sb_body
.append(indent
).append("</t2_layer>\n");
900 /** Includes all Displayable objects in the list of possible children. */
901 static public void exportDTD(final StringBuilder sb_header
, final HashSet
<String
> hs
, final String indent
) {
902 final String type
= "t2_layer";
903 if (hs
.contains(type
)) return;
905 sb_header
.append(indent
).append("<!ELEMENT t2_layer (t2_patch,t2_label,t2_layer_set,t2_profile)>\n")
906 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" oid").append(Displayable
.TAG_ATTR2
)
907 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" thickness").append(Displayable
.TAG_ATTR2
)
908 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" z").append(Displayable
.TAG_ATTR2
)
912 protected String
getLayerThingTitle() {
913 final LayerThing lt
= project
.findLayerThing(this);
914 if (null == lt
|| null == lt
.getTitle() || 0 == lt
.getTitle().trim().length()) return "";
915 return lt
.getTitle();
919 public String
getTitle() {
920 final LayerThing lt
= project
.findLayerThing(this);
921 if (null == lt
|| null == lt
.getTitle() || 0 == lt
.getTitle().trim().length()) return this.toString();
922 return lt
.getTitle();
925 public void destroy() {
926 for (final Displayable d
: al_displayables
) {
931 /** Returns null if no Displayable objects of class c exist. */
932 public Rectangle
getMinimalBoundingBox(final Class
<?
> c
) {
933 return getMinimalBoundingBox(c
, true);
936 /** Returns null if no Displayable objects of class c exist (or are visible if {@param visible_only} is true). */
937 public Rectangle
getMinimalBoundingBox(final Class
<?
> c
, final boolean visible_only
) {
938 Rectangle box
= null;
939 Rectangle tmp
= new Rectangle();
940 for (final Displayable d
: getDisplayables(c
, visible_only
)) {
941 tmp
= d
.getBoundingBox(tmp
);
943 box
= (Rectangle
)tmp
.clone();
951 /** Returns an Area in world coordinates that represents the inside of all Patches. */
952 public Area
getPatchArea(final boolean visible_only
) {
953 final Area area
= new Area(); // with width,height zero
954 for (final Patch p
: getAll(Patch
.class)) {
955 if (visible_only
&& p
.isVisible()) {
956 area
.add(p
.getArea());
962 /** Preconcatenate the given AffineTransform to all Displayable objects of class c, without respecting their links. */
963 public void apply(final Class
<?
> c
, final AffineTransform at
) {
964 final boolean all
= Displayable
.class == c
;
965 for (final Displayable d
: al_displayables
) {
966 if (all
|| d
.getClass() == c
) {
967 d
.at
.preConcatenate(at
);
973 /** Make a copy of this layer into the given LayerSet, enclosing only Displayable objects within the roi, and translating them for that roi x,y. */
974 public Layer
clone(final Project pr
, final LayerSet ls
, final Rectangle roi
, final boolean copy_id
, final boolean ignore_hidden_patches
) {
975 final long nid
= copy_id ?
this.id
: pr
.getLoader().getNextId();
976 final Layer copy
= new Layer(pr
, nid
, z
, thickness
);
978 for (final Displayable d
: find(roi
)) {
979 if (ignore_hidden_patches
&& !d
.isVisible() && d
.getClass() == Patch
.class) continue;
980 copy
.addSilently(d
.clone(pr
, copy_id
));
982 final AffineTransform transform
= new AffineTransform();
983 transform
.translate(-roi
.x
, -roi
.y
);
984 copy
.apply(Displayable
.class, transform
);
988 static public final int IMAGEPROCESSOR
= 0;
989 static public final int PIXELARRAY
= 1;
990 static public final int IMAGE
= 2;
991 static public final int IMAGEPLUS
= 3;
993 /** Returns the region defined by the rectangle as an image in the type and format specified.
994 * The type is either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB.
995 * The format is either Layer.IMAGEPROCESSOR, Layer.IMAGEPLUS, Layer.PIXELARRAY or Layer.IMAGE.
997 public Object
grab(final Rectangle r
, final double scale
, final Class
<?
> c
, final int c_alphas
, final int format
, final int type
) {
998 //Ensure some memory is free
999 project
.getLoader().releaseToFit(r
.width
, r
.height
, type
, 1.1f
);
1000 if (IMAGE
== format
) {
1001 return project
.getLoader().getFlatAWTImage(this, r
, scale
, c_alphas
, type
, c
, null, true, Color
.black
);
1003 final ImagePlus imp
= project
.getLoader().getFlatImage(this, r
, scale
, c_alphas
, type
, c
, null, true);
1007 case IMAGEPROCESSOR
:
1008 return imp
.getProcessor();
1010 return imp
.getProcessor().getPixels();
1016 public DBObject
findById(final long id
) {
1017 if (this.id
== id
) return this;
1018 for (final Displayable d
: al_displayables
) {
1019 if (d
.getId() == id
) return d
;
1024 // private to the package
1025 void linkPatchesR() {
1026 for (final Displayable d
: al_displayables
) {
1027 if (d
.getClass() == LayerSet
.class) ((LayerSet
)d
).linkPatchesR();
1028 d
.linkPatches(); // Patch.class does nothing
1032 /** Recursive into nested LayerSet objects.*/
1033 public void updateLayerTree() {
1034 project
.getLayerTree().addLayer(parent
, this);
1035 for (final Displayable d
: getDisplayables(LayerSet
.class)) {
1036 ((LayerSet
)d
).updateLayerTree();
1040 /** Don't use this for fast pixel grabbing; this is intended for the dropper tool and status bar reporting by mouse motion. */
1041 public int[] getPixel(final int x
, final int y
, final double mag
) {
1042 // find Patch under cursor
1043 final Collection
<Displayable
> under
= find(Patch
.class, x
, y
);
1044 if (null == under
|| under
.isEmpty()) return new int[3]; // zeros
1045 final Patch pa
= (Patch
)under
.iterator().next();// get(0) // the top one, since they are ordered like html divs
1046 // TODO: edit here when adding layer mipmaps
1047 return pa
.getPixel(x
, y
, mag
);
1050 synchronized public void recreateBuckets() {
1051 this.root
= new Bucket(0, 0, (int)(0.00005 + getLayerWidth()), (int)(0.00005 + getLayerHeight()), Bucket
.getBucketSide(this, this));
1052 this.db_map
= new HashMap
<Displayable
,HashSet
<Bucket
>>();
1053 this.root
.populate(this, this, db_map
);
1057 /** Update buckets of a position change for the given Displayable. */
1059 public void updateBucket(final Displayable d
, final Layer layer
) { // ignore layer
1060 if (null != root
) root
.updatePosition(d
, this, db_map
);
1063 public void checkBuckets() {
1064 if (use_buckets
&& (null == root
|| null == db_map
)) recreateBuckets();
1067 private boolean use_buckets
= true;
1069 public void setBucketsEnabled(final boolean b
) {
1070 this.use_buckets
= b
;
1071 if (!use_buckets
) this.root
= null;
1074 static class DoEditLayer
implements DoStep
{
1075 final double z
, thickness
;
1077 DoEditLayer(final Layer layer
) {
1080 this.thickness
= layer
.thickness
;
1083 public Displayable
getD() { return null; }
1085 public boolean isEmpty() { return false; }
1087 public boolean isIdenticalTo(final Object ob
) {
1088 if (!(ob
instanceof Layer
)) return false;
1089 final Layer layer
= (Layer
) ob
;
1090 return this.la
.id
== layer
.id
&& this.z
== layer
.z
&& this.thickness
== layer
.thickness
;
1093 public boolean apply(final int action
) {
1095 la
.thickness
= this.thickness
;
1096 la
.getProject().getLayerTree().updateUILater();
1097 Display
.update(la
.getParent());
1102 static class DoEditLayers
implements DoStep
{
1103 final ArrayList
<DoEditLayer
> all
= new ArrayList
<DoEditLayer
>();
1104 DoEditLayers(final List
<Layer
> all
) {
1105 for (final Layer la
: all
) {
1106 this.all
.add(new DoEditLayer(la
));
1110 public Displayable
getD() { return null; }
1112 public boolean isEmpty() { return all
.isEmpty(); }
1114 public boolean isIdenticalTo(final Object ob
) {
1115 if (!(ob
instanceof DoEditLayers
)) return false;
1116 final DoEditLayers other
= (DoEditLayers
) ob
;
1117 if (all
.size() != other
.all
.size()) return false;
1119 final Iterator
<DoEditLayer
> it1
= all
.iterator();
1120 final Iterator
<DoEditLayer
> it2
= other
.all
.iterator();
1121 for (; it1
.hasNext() && it2
.hasNext(); ) {
1122 if (!it1
.next().isIdenticalTo(it2
.next())) return false;
1127 public boolean apply(final int action
) {
1128 boolean failed
= false;
1129 for (final DoEditLayer one
: all
) {
1130 if (!one
.apply(action
)) {
1138 /** Add an object that is Layer bound, such as Profile, Patch, DLabel and LayerSet. */
1139 static class DoContentChange
implements DoStep
{
1141 final ArrayList
<Displayable
> al
;
1142 DoContentChange(final Layer la
) {
1144 this.al
= la
.getDisplayables(); // a copy
1147 public Displayable
getD() { return null; }
1149 public boolean isEmpty() { return false; }
1150 /** Check that the Displayable objects of this layer are the same and in the same quantity and order. */
1152 public boolean isIdenticalTo(final Object ob
) {
1153 if (!(ob
instanceof DoContentChange
)) return false;
1154 final DoContentChange dad
= (DoContentChange
) ob
;
1155 if (la
!= dad
.la
|| al
.size() != dad
.al
.size()) return false;
1157 final Iterator
<Displayable
> it1
= al
.iterator();
1158 final Iterator
<Displayable
> it2
= dad
.al
.iterator();
1159 for (; it1
.hasNext() && it2
.hasNext(); ) {
1160 if (it1
.next() != it2
.next()) return false;
1165 public boolean apply(final int action
) {
1166 // find the subset in la.al_displayables that is not in this.al
1167 final HashSet
<Displayable
> sub1
= new HashSet
<Displayable
>(la
.al_displayables
);
1168 sub1
.removeAll(this.al
);
1169 // find the subset in this.al that is not in la.al_displayables
1170 final HashSet
<Displayable
> sub2
= new HashSet
<Displayable
>(this.al
);
1171 sub2
.removeAll(la
.al_displayables
);
1173 HashSet
<Displayable
> subA
=null, subB
=null;
1175 if (action
== DoStep
.UNDO
) {
1178 } else if (action
== DoStep
.REDO
) {
1182 if (null != subA
&& null != subB
) {
1183 // Mark Patch for mipmap file removal
1184 for (final Displayable d
: subA
) {
1185 if (d
.getClass() == Patch
.class) {
1186 d
.getProject().getLoader().queueForMipmapRemoval((Patch
)d
, true);
1190 for (final Displayable d
: subB
) {
1191 if (d
.getClass() == Patch
.class) {
1192 d
.getProject().getLoader().queueForMipmapRemoval((Patch
)d
, false);
1197 la
.al_displayables
.clear();
1198 la
.al_displayables
.addAll(this.al
);
1199 la
.recreateBuckets();
1200 Display
.updateVisibleTabs();
1201 Display
.clearSelection();
1207 static protected class DoMoveDisplayable
implements DoStep
{
1208 final ArrayList
<Displayable
> al_displayables
;
1210 HashSet
<DoStep
> dependents
= null;
1211 DoMoveDisplayable(final Layer layer
) {
1213 this.al_displayables
= new ArrayList
<Displayable
>(layer
.al_displayables
);
1216 public boolean apply(final int action
) {
1217 // Replace all ZDisplayable
1218 layer
.al_displayables
.clear();
1219 layer
.al_displayables
.addAll(this.al_displayables
);
1220 Display
.update(layer
);
1224 public boolean isEmpty() {
1228 public Displayable
getD() {
1232 public boolean isIdenticalTo(final Object ob
) {
1233 if (!(ob
instanceof DoMoveDisplayable
)) return false;
1234 final DoMoveDisplayable dm
= (DoMoveDisplayable
)ob
;
1235 if (dm
.layer
!= this.layer
) return false;
1236 if (dm
.al_displayables
.size() != this.al_displayables
.size()) return false;
1237 for (int i
=0; i
<this.al_displayables
.size(); ++i
) {
1238 if (dm
.al_displayables
.get(i
) != this.al_displayables
.get(i
)) return false;
1246 private Overlay overlay
= null;
1248 /** Return the current Overlay or a new one if none yet. */
1249 synchronized public Overlay
getOverlay() {
1250 if (null == overlay
) overlay
= new Overlay();
1253 // Used by DisplayCanvas to paint
1254 Overlay
getOverlay2() {
1257 /** Set to null to remove the Overlay.
1258 * @return the previous Overlay, if any. */
1259 synchronized public Overlay
setOverlay(final Overlay o
) {
1260 final Overlay old
= this.overlay
;
1266 public int compareTo(final Layer layer
) {
1267 final double diff
= this.z
- layer
.z
;
1268 if (diff
< 0) return -1;
1269 if (diff
> 0) return 1;
1273 /** Transfer the world coordinate specified by {@param world_x},{@param world_y}
1274 * in pixels, to the local coordinate of the {@link Patch} immediately present under it.
1275 * @return null if no {@link Patch} is under the coordinate, else the {@link Coordinate} with the x, y, {@link Layer} and the {@link Patch}.
1276 * @throws NoninvertibleModelException
1277 * @throws NoninvertibleTransformException
1279 public Coordinate
<Patch
> toPatchCoordinate(final double world_x
, final double world_y
) throws NoninvertibleTransformException
, NoninvertibleModelException
{
1280 final Collection
<Displayable
> ps
= find(Patch
.class, world_x
, world_y
, true, false);
1283 // No Patch under the point. Find the nearest Patch instead
1284 final Collection
<Patch
> patches
= getAll(Patch
.class);
1285 if (patches
.isEmpty()) return null;
1286 double minSqDist
= Double
.MAX_VALUE
;
1287 for (final Patch p
: patches
) {
1288 // Check if any of the 4 corners of the bounding box are beyond minSqDist
1289 final Rectangle b
= p
.getBoundingBox();
1290 final double d1
= Math
.pow(b
.x
- world_x
, 2) + Math
.pow(b
.y
- world_y
, 2),
1291 d2
= Math
.pow(b
.x
+ b
.width
- world_x
, 2) + Math
.pow(b
.y
- world_y
, 2),
1292 d3
= Math
.pow(b
.x
- world_x
, 2) + Math
.pow(b
.y
+ b
.height
- world_y
, 2),
1293 d4
= Math
.pow(b
.x
+ b
.width
- world_x
, 2) + Math
.pow(b
.y
+ b
.height
- world_y
, 2),
1294 d
= Math
.min(d1
, Math
.min(d2
, Math
.min(d3
, d4
)));
1295 if (d
< minSqDist
) {
1299 // If the Patch has a CoordinateTransform, find the closest perimeter point
1300 if (p
.hasCoordinateTransform()) {
1301 for (final Polygon pol
: M
.getPolygons(p
.getArea())) { // Area in world coordinates
1302 for (int i
=0; i
<pol
.npoints
; ++i
) {
1303 final double sqDist
= Math
.pow(pol
.xpoints
[0] - world_x
, 2) + Math
.pow(pol
.ypoints
[1] - world_y
, 2);
1304 if (sqDist
< minSqDist
) {
1313 patch
= (Patch
) ps
.iterator().next();
1316 final double[] point
= patch
.toPixelCoordinate(world_x
, world_y
);
1317 return new Coordinate
<Patch
>(point
[0], point
[1], patch
.getLayer(), patch
);