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)
688 * intersect the given Displayable (which is itself included if present in this very Layer). */
689 synchronized public <T
extends Displayable
> Collection
<T
> getIntersecting(final Displayable d
, final Class
<T
> target
) {
691 final Area area
= new Area(d
.getPerimeter());
692 if (root
.isBetter(area
.getBounds(), this)) {
693 return (Collection
<T
>) root
.find(target
, area
, this, false, true);
696 final ArrayList
<T
> al
= new ArrayList
<T
>();
697 for (int i
= al_displayables
.size() -1; i
>-1; i
--) {
698 final Object ob
= al_displayables
.get(i
);
699 if (target
.isAssignableFrom(ob
.getClass())) continue;
700 final Displayable da
= (Displayable
)ob
;
701 if (d
.intersects(da
)) {
708 /** Returns -1 if not found. */
709 public final int indexOf(final Displayable d
) {
710 return al_displayables
.indexOf(d
);
713 /** Within its own class only.
714 * '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). */
715 public void moveUp(final Displayable d
) {
716 final int i
= al_displayables
.indexOf(d
);
717 if (null == d
|| -1 == i
|| al_displayables
.size() -1 == i
) return;
718 if (al_displayables
.get(i
+1).getClass() == d
.getClass()) {
720 al_displayables
.remove(d
);
721 al_displayables
.add(i
+1, d
);
723 updateInDatabase("stack_index");
724 Display
.updatePanelIndex(d
.getLayer(), d
);
725 if (null != root
) root
.updateRange(this, d
, i
, i
+1);
728 /** Within its own class only. */
729 public void moveDown(final Displayable d
) {
730 final int i
= al_displayables
.indexOf(d
);
731 if (null == d
|| -1 == i
|| 0 == i
) return;
732 if (al_displayables
.get(i
-1).getClass() == d
.getClass()) {
734 final Displayable o
= al_displayables
.remove(i
-1);
735 al_displayables
.add(i
, o
);
737 updateInDatabase("stack_index");
738 Display
.updatePanelIndex(d
.getLayer(), d
);
739 if (null != root
) root
.updateRange(this, d
, i
-1, i
);
742 /** Within its own class only. */
743 public void moveTop(final Displayable d
) { // yes I could have made several lists and make my life easier. Whatever
744 final int i
= al_displayables
.indexOf(d
);
745 final int size
= al_displayables
.size();
746 if (null == d
|| -1 == i
|| size
-1 == i
) return;
747 final Class
<?
> c
= d
.getClass();
748 boolean done
= false;
750 for (; j
<size
; j
++) {
751 if (al_displayables
.get(j
).getClass() == c
) continue;
753 al_displayables
.remove(d
);
754 al_displayables
.add(--j
, d
); // j-1
759 // solves case of no other class present
762 al_displayables
.remove(d
);
763 al_displayables
.add(d
);
766 updateInDatabase("stack_index");
767 Display
.updatePanelIndex(d
.getLayer(), d
);
768 if (null != root
) root
.updateRange(this, d
, i
, j
);
771 /** Within its own class only. */
772 public void moveBottom(final Displayable d
) {
773 final int i
= al_displayables
.indexOf(d
);
774 if (null == d
|| -1 == i
|| 0 == i
) return;
775 final Class
<?
> c
= d
.getClass();
776 boolean done
= false;
778 for (; j
> -1; j
--) {
779 if (al_displayables
.get(j
).getClass() == c
) continue;
781 al_displayables
.remove(d
);
782 al_displayables
.add(++j
, d
); // j+1
787 // solve case of no other class present
789 al_displayables
.remove(d
);
790 al_displayables
.add(0, d
);
793 updateInDatabase("stack_index");
794 Display
.updatePanelIndex(d
.getLayer(), d
);
795 if (null != root
) root
.updateRange(this, d
, j
, i
);
798 /** Within its own class only. */
799 public boolean isTop(final Displayable d
) {
800 final int i
= al_displayables
.indexOf(d
);
801 final int size
= al_displayables
.size();
802 if (size
-1 == i
) return true;
803 if (al_displayables
.get(i
+1).getClass() == d
.getClass()) return false;
805 } // these two methods will throw an Exception if the Displayable is not found (-1 == i) (the null.getClass() *should* throw it)
806 /** Within its own class only. */
807 public boolean isBottom(final Displayable d
) {
808 final int i
= al_displayables
.indexOf(d
);
809 if (0 == i
) return true;
810 if (al_displayables
.get(i
-1).getClass() == d
.getClass()) return false;
814 /** 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.*/
815 public int relativeIndexOf(final Displayable d
) {
816 final int k
= al_displayables
.indexOf(d
);
817 if (-1 == k
) return -1;
818 final Class
<?
> c
= d
.getClass();
819 final int size
= al_displayables
.size();
821 for (; i
<size
; i
++) {
822 if (al_displayables
.get(i
).getClass() == c
) continue;
830 Utils
.log2("relativeIndexOf: return 0");
834 /** Note: Not recursive into embedded LayerSet objects. Returns the hash set of objects whose visibility has changed. */
835 public HashSet
<Displayable
> setVisible(String type
, final boolean visible
, final boolean repaint
) {
836 type
= type
.toLowerCase();
837 if (type
.equals("image")) type
= "patch";
838 final HashSet
<Displayable
> hs
= new HashSet
<Displayable
>();
839 for (final Displayable d
: al_displayables
) {
840 if (visible
!= d
.isVisible() && d
.getClass().getName().toLowerCase().endsWith(type
)) {
841 d
.setVisible(visible
, false); // don't repaint
846 Display
.repaint(this);
848 Display
.updateCheckboxes(hs
, DisplayablePanel
.VISIBILITY_STATE
, visible
);
851 /** Returns the collection of Displayable whose visibility state has changed. */
852 public Collection
<Displayable
> setAllVisible(final boolean repaint
) {
853 final Collection
<Displayable
> col
= new ArrayList
<Displayable
>();
854 for (final Displayable d
: al_displayables
) {
855 if (!d
.isVisible()) {
856 d
.setVisible(true, repaint
);
863 /** Hide all except those whose type is in 'type' list, whose visibility flag is left unchanged. Returns the list of displayables made hidden. */
864 public HashSet
<Displayable
> hideExcept(final ArrayList
<Class
<?
>> type
, final boolean repaint
) {
865 final HashSet
<Displayable
> hs
= new HashSet
<Displayable
>();
866 for (final Displayable d
: al_displayables
) {
867 if (!type
.contains(d
.getClass()) && d
.isVisible()) {
868 d
.setVisible(false, repaint
);
876 public void exportXML(final StringBuilder sb_body
, final String indent
, final XMLOptions options
) {
877 final String in
= indent
+ "\t";
879 sb_body
.append(indent
).append("<t2_layer oid=\"").append(id
).append("\"\n")
880 .append(in
).append(" thickness=\"").append(thickness
).append("\"\n")
881 .append(in
).append(" z=\"").append(z
).append("\"\n")
883 // TODO this search is linear!
884 final LayerThing lt
= project
.findLayerThing(this);
886 if (null == lt
) title
= null;
887 else title
= lt
.getTitle();
888 if (null == title
) title
= "";
889 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.
890 sb_body
.append(indent
).append(">\n");
891 // 2 - export children
892 if (null != al_displayables
) {
893 for (final Displayable d
: al_displayables
) {
894 d
.exportXML(sb_body
, in
, options
);
898 sb_body
.append(indent
).append("</t2_layer>\n");
901 /** Includes all Displayable objects in the list of possible children. */
902 static public void exportDTD(final StringBuilder sb_header
, final HashSet
<String
> hs
, final String indent
) {
903 final String type
= "t2_layer";
904 if (hs
.contains(type
)) return;
906 sb_header
.append(indent
).append("<!ELEMENT t2_layer (t2_patch,t2_label,t2_layer_set,t2_profile)>\n")
907 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" oid").append(Displayable
.TAG_ATTR2
)
908 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" thickness").append(Displayable
.TAG_ATTR2
)
909 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" z").append(Displayable
.TAG_ATTR2
)
913 protected String
getLayerThingTitle() {
914 final LayerThing lt
= project
.findLayerThing(this);
915 if (null == lt
|| null == lt
.getTitle() || 0 == lt
.getTitle().trim().length()) return "";
916 return lt
.getTitle();
920 public String
getTitle() {
921 final LayerThing lt
= project
.findLayerThing(this);
922 if (null == lt
|| null == lt
.getTitle() || 0 == lt
.getTitle().trim().length()) return this.toString();
923 return lt
.getTitle();
926 public void destroy() {
927 for (final Displayable d
: al_displayables
) {
932 /** Returns null if no Displayable objects of class c exist. */
933 public Rectangle
getMinimalBoundingBox(final Class
<?
> c
) {
934 return getMinimalBoundingBox(c
, true);
937 /** Returns null if no Displayable objects of class c exist (or are visible if {@param visible_only} is true). */
938 public Rectangle
getMinimalBoundingBox(final Class
<?
> c
, final boolean visible_only
) {
939 Rectangle box
= null;
940 Rectangle tmp
= new Rectangle();
941 for (final Displayable d
: getDisplayables(c
, visible_only
)) {
942 tmp
= d
.getBoundingBox(tmp
);
944 box
= (Rectangle
)tmp
.clone();
952 /** Returns an Area in world coordinates that represents the inside of all Patches. */
953 public Area
getPatchArea(final boolean visible_only
) {
954 final Area area
= new Area(); // with width,height zero
955 for (final Patch p
: getAll(Patch
.class)) {
956 if (visible_only
&& p
.isVisible()) {
957 area
.add(p
.getArea());
963 /** Preconcatenate the given AffineTransform to all Displayable objects of class c, without respecting their links. */
964 public void apply(final Class
<?
> c
, final AffineTransform at
) {
965 final boolean all
= Displayable
.class == c
;
966 for (final Displayable d
: al_displayables
) {
967 if (all
|| d
.getClass() == c
) {
968 d
.at
.preConcatenate(at
);
974 /** 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. */
975 public Layer
clone(final Project pr
, final LayerSet ls
, final Rectangle roi
, final boolean copy_id
, final boolean ignore_hidden_patches
) {
976 final long nid
= copy_id ?
this.id
: pr
.getLoader().getNextId();
977 final Layer copy
= new Layer(pr
, nid
, z
, thickness
);
979 for (final Displayable d
: find(roi
)) {
980 if (ignore_hidden_patches
&& !d
.isVisible() && d
.getClass() == Patch
.class) continue;
981 copy
.addSilently(d
.clone(pr
, copy_id
));
983 final AffineTransform transform
= new AffineTransform();
984 transform
.translate(-roi
.x
, -roi
.y
);
985 copy
.apply(Displayable
.class, transform
);
989 static public final int IMAGEPROCESSOR
= 0;
990 static public final int PIXELARRAY
= 1;
991 static public final int IMAGE
= 2;
992 static public final int IMAGEPLUS
= 3;
994 /** Returns the region defined by the rectangle as an image in the type and format specified.
995 * The type is either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB.
996 * The format is either Layer.IMAGEPROCESSOR, Layer.IMAGEPLUS, Layer.PIXELARRAY or Layer.IMAGE.
998 public Object
grab(final Rectangle r
, final double scale
, final Class
<?
> c
, final int c_alphas
, final int format
, final int type
) {
999 //Ensure some memory is free
1000 project
.getLoader().releaseToFit(r
.width
, r
.height
, type
, 1.1f
);
1001 if (IMAGE
== format
) {
1002 return project
.getLoader().getFlatAWTImage(this, r
, scale
, c_alphas
, type
, c
, null, true, Color
.black
);
1004 final ImagePlus imp
= project
.getLoader().getFlatImage(this, r
, scale
, c_alphas
, type
, c
, null, true);
1008 case IMAGEPROCESSOR
:
1009 return imp
.getProcessor();
1011 return imp
.getProcessor().getPixels();
1017 public DBObject
findById(final long id
) {
1018 if (this.id
== id
) return this;
1019 for (final Displayable d
: al_displayables
) {
1020 if (d
.getId() == id
) return d
;
1025 // private to the package
1026 void linkPatchesR() {
1027 for (final Displayable d
: al_displayables
) {
1028 if (d
.getClass() == LayerSet
.class) ((LayerSet
)d
).linkPatchesR();
1029 d
.linkPatches(); // Patch.class does nothing
1033 /** Recursive into nested LayerSet objects.*/
1034 public void updateLayerTree() {
1035 project
.getLayerTree().addLayer(parent
, this);
1036 for (final Displayable d
: getDisplayables(LayerSet
.class)) {
1037 ((LayerSet
)d
).updateLayerTree();
1041 /** Don't use this for fast pixel grabbing; this is intended for the dropper tool and status bar reporting by mouse motion. */
1042 public int[] getPixel(final int x
, final int y
, final double mag
) {
1043 // find Patch under cursor
1044 final Collection
<Displayable
> under
= find(Patch
.class, x
, y
);
1045 if (null == under
|| under
.isEmpty()) return new int[3]; // zeros
1046 final Patch pa
= (Patch
)under
.iterator().next();// get(0) // the top one, since they are ordered like html divs
1047 // TODO: edit here when adding layer mipmaps
1048 return pa
.getPixel(x
, y
, mag
);
1051 synchronized public void recreateBuckets() {
1052 this.root
= new Bucket(0, 0, (int)(0.00005 + getLayerWidth()), (int)(0.00005 + getLayerHeight()), Bucket
.getBucketSide(this, this));
1053 this.db_map
= new HashMap
<Displayable
,HashSet
<Bucket
>>();
1054 this.root
.populate(this, this, db_map
);
1058 /** Update buckets of a position change for the given Displayable. */
1060 public void updateBucket(final Displayable d
, final Layer layer
) { // ignore layer
1061 if (null != root
) root
.updatePosition(d
, this, db_map
);
1064 public void checkBuckets() {
1065 if (use_buckets
&& (null == root
|| null == db_map
)) recreateBuckets();
1068 private boolean use_buckets
= true;
1070 public void setBucketsEnabled(final boolean b
) {
1071 this.use_buckets
= b
;
1072 if (!use_buckets
) this.root
= null;
1075 static class DoEditLayer
implements DoStep
{
1076 final double z
, thickness
;
1078 DoEditLayer(final Layer layer
) {
1081 this.thickness
= layer
.thickness
;
1084 public Displayable
getD() { return null; }
1086 public boolean isEmpty() { return false; }
1088 public boolean isIdenticalTo(final Object ob
) {
1089 if (!(ob
instanceof Layer
)) return false;
1090 final Layer layer
= (Layer
) ob
;
1091 return this.la
.id
== layer
.id
&& this.z
== layer
.z
&& this.thickness
== layer
.thickness
;
1094 public boolean apply(final int action
) {
1096 la
.thickness
= this.thickness
;
1097 la
.getProject().getLayerTree().updateUILater();
1098 Display
.update(la
.getParent());
1103 static class DoEditLayers
implements DoStep
{
1104 final ArrayList
<DoEditLayer
> all
= new ArrayList
<DoEditLayer
>();
1105 DoEditLayers(final List
<Layer
> all
) {
1106 for (final Layer la
: all
) {
1107 this.all
.add(new DoEditLayer(la
));
1111 public Displayable
getD() { return null; }
1113 public boolean isEmpty() { return all
.isEmpty(); }
1115 public boolean isIdenticalTo(final Object ob
) {
1116 if (!(ob
instanceof DoEditLayers
)) return false;
1117 final DoEditLayers other
= (DoEditLayers
) ob
;
1118 if (all
.size() != other
.all
.size()) return false;
1120 final Iterator
<DoEditLayer
> it1
= all
.iterator();
1121 final Iterator
<DoEditLayer
> it2
= other
.all
.iterator();
1122 for (; it1
.hasNext() && it2
.hasNext(); ) {
1123 if (!it1
.next().isIdenticalTo(it2
.next())) return false;
1128 public boolean apply(final int action
) {
1129 boolean failed
= false;
1130 for (final DoEditLayer one
: all
) {
1131 if (!one
.apply(action
)) {
1139 /** Add an object that is Layer bound, such as Profile, Patch, DLabel and LayerSet. */
1140 static class DoContentChange
implements DoStep
{
1142 final ArrayList
<Displayable
> al
;
1143 DoContentChange(final Layer la
) {
1145 this.al
= la
.getDisplayables(); // a copy
1148 public Displayable
getD() { return null; }
1150 public boolean isEmpty() { return false; }
1151 /** Check that the Displayable objects of this layer are the same and in the same quantity and order. */
1153 public boolean isIdenticalTo(final Object ob
) {
1154 if (!(ob
instanceof DoContentChange
)) return false;
1155 final DoContentChange dad
= (DoContentChange
) ob
;
1156 if (la
!= dad
.la
|| al
.size() != dad
.al
.size()) return false;
1158 final Iterator
<Displayable
> it1
= al
.iterator();
1159 final Iterator
<Displayable
> it2
= dad
.al
.iterator();
1160 for (; it1
.hasNext() && it2
.hasNext(); ) {
1161 if (it1
.next() != it2
.next()) return false;
1166 public boolean apply(final int action
) {
1167 // find the subset in la.al_displayables that is not in this.al
1168 final HashSet
<Displayable
> sub1
= new HashSet
<Displayable
>(la
.al_displayables
);
1169 sub1
.removeAll(this.al
);
1170 // find the subset in this.al that is not in la.al_displayables
1171 final HashSet
<Displayable
> sub2
= new HashSet
<Displayable
>(this.al
);
1172 sub2
.removeAll(la
.al_displayables
);
1174 HashSet
<Displayable
> subA
=null, subB
=null;
1176 if (action
== DoStep
.UNDO
) {
1179 } else if (action
== DoStep
.REDO
) {
1183 if (null != subA
&& null != subB
) {
1184 // Mark Patch for mipmap file removal
1185 for (final Displayable d
: subA
) {
1186 if (d
.getClass() == Patch
.class) {
1187 d
.getProject().getLoader().queueForMipmapRemoval((Patch
)d
, true);
1191 for (final Displayable d
: subB
) {
1192 if (d
.getClass() == Patch
.class) {
1193 d
.getProject().getLoader().queueForMipmapRemoval((Patch
)d
, false);
1198 la
.al_displayables
.clear();
1199 la
.al_displayables
.addAll(this.al
);
1200 la
.recreateBuckets();
1201 Display
.updateVisibleTabs();
1202 Display
.clearSelection();
1208 static protected class DoMoveDisplayable
implements DoStep
{
1209 final ArrayList
<Displayable
> al_displayables
;
1211 HashSet
<DoStep
> dependents
= null;
1212 DoMoveDisplayable(final Layer layer
) {
1214 this.al_displayables
= new ArrayList
<Displayable
>(layer
.al_displayables
);
1217 public boolean apply(final int action
) {
1218 // Replace all ZDisplayable
1219 layer
.al_displayables
.clear();
1220 layer
.al_displayables
.addAll(this.al_displayables
);
1221 Display
.update(layer
);
1225 public boolean isEmpty() {
1229 public Displayable
getD() {
1233 public boolean isIdenticalTo(final Object ob
) {
1234 if (!(ob
instanceof DoMoveDisplayable
)) return false;
1235 final DoMoveDisplayable dm
= (DoMoveDisplayable
)ob
;
1236 if (dm
.layer
!= this.layer
) return false;
1237 if (dm
.al_displayables
.size() != this.al_displayables
.size()) return false;
1238 for (int i
=0; i
<this.al_displayables
.size(); ++i
) {
1239 if (dm
.al_displayables
.get(i
) != this.al_displayables
.get(i
)) return false;
1247 private Overlay overlay
= null;
1249 /** Return the current Overlay or a new one if none yet. */
1250 synchronized public Overlay
getOverlay() {
1251 if (null == overlay
) overlay
= new Overlay();
1254 // Used by DisplayCanvas to paint
1255 Overlay
getOverlay2() {
1258 /** Set to null to remove the Overlay.
1259 * @return the previous Overlay, if any. */
1260 synchronized public Overlay
setOverlay(final Overlay o
) {
1261 final Overlay old
= this.overlay
;
1267 public int compareTo(final Layer layer
) {
1268 final double diff
= this.z
- layer
.z
;
1269 if (diff
< 0) return -1;
1270 if (diff
> 0) return 1;
1274 /** Transfer the world coordinate specified by {@param world_x},{@param world_y}
1275 * in pixels, to the local coordinate of the {@link Patch} immediately present under it.
1276 * @return null if no {@link Patch} is under the coordinate, else the {@link Coordinate} with the x, y, {@link Layer} and the {@link Patch}.
1277 * @throws NoninvertibleModelException
1278 * @throws NoninvertibleTransformException
1280 public Coordinate
<Patch
> toPatchCoordinate(final double world_x
, final double world_y
) throws NoninvertibleTransformException
, NoninvertibleModelException
{
1281 final Collection
<Displayable
> ps
= find(Patch
.class, world_x
, world_y
, true, false);
1284 // No Patch under the point. Find the nearest Patch instead
1285 final Collection
<Patch
> patches
= getAll(Patch
.class);
1286 if (patches
.isEmpty()) return null;
1287 double minSqDist
= Double
.MAX_VALUE
;
1288 for (final Patch p
: patches
) {
1289 // Check if any of the 4 corners of the bounding box are beyond minSqDist
1290 final Rectangle b
= p
.getBoundingBox();
1291 final double d1
= Math
.pow(b
.x
- world_x
, 2) + Math
.pow(b
.y
- world_y
, 2),
1292 d2
= Math
.pow(b
.x
+ b
.width
- world_x
, 2) + Math
.pow(b
.y
- world_y
, 2),
1293 d3
= Math
.pow(b
.x
- world_x
, 2) + Math
.pow(b
.y
+ b
.height
- world_y
, 2),
1294 d4
= Math
.pow(b
.x
+ b
.width
- world_x
, 2) + Math
.pow(b
.y
+ b
.height
- world_y
, 2),
1295 d
= Math
.min(d1
, Math
.min(d2
, Math
.min(d3
, d4
)));
1296 if (d
< minSqDist
) {
1300 // If the Patch has a CoordinateTransform, find the closest perimeter point
1301 if (p
.hasCoordinateTransform()) {
1302 for (final Polygon pol
: M
.getPolygons(p
.getArea())) { // Area in world coordinates
1303 for (int i
=0; i
<pol
.npoints
; ++i
) {
1304 final double sqDist
= Math
.pow(pol
.xpoints
[0] - world_x
, 2) + Math
.pow(pol
.ypoints
[1] - world_y
, 2);
1305 if (sqDist
< minSqDist
) {
1314 patch
= (Patch
) ps
.iterator().next();
1317 final double[] point
= patch
.toPixelCoordinate(world_x
, world_y
);
1318 return new Coordinate
<Patch
>(point
[0], point
[1], patch
.getLayer(), patch
);