4 package mpicbg
.trakem2
.align
;
7 import java
.awt
.Rectangle
;
8 import java
.awt
.geom
.AffineTransform
;
9 import java
.awt
.geom
.NoninvertibleTransformException
;
10 import java
.awt
.geom
.Area
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Iterator
;
13 import java
.util
.Collection
;
14 import java
.util
.Collections
;
15 import java
.util
.HashMap
;
16 import java
.util
.HashSet
;
17 import java
.util
.List
;
20 import java
.util
.TreeMap
;
22 import mpicbg
.ij
.FeatureTransform
;
23 import mpicbg
.ij
.SIFT
;
24 import mpicbg
.imagefeatures
.Feature
;
25 import mpicbg
.imagefeatures
.FloatArray2DSIFT
;
26 import mpicbg
.models
.AbstractAffineModel2D
;
27 import mpicbg
.models
.AffineModel2D
;
28 import mpicbg
.models
.NotEnoughDataPointsException
;
29 import mpicbg
.models
.Point
;
30 import mpicbg
.models
.PointMatch
;
31 import mpicbg
.models
.SimilarityModel2D
;
32 import mpicbg
.models
.Tile
;
33 import mpicbg
.models
.Transforms
;
34 import mpicbg
.trakem2
.transform
.CoordinateTransform
;
35 import mpicbg
.trakem2
.transform
.CoordinateTransformList
;
36 import mpicbg
.trakem2
.transform
.MovingLeastSquaresTransform
;
37 import mpicbg
.trakem2
.transform
.RigidModel2D
;
38 import mpicbg
.trakem2
.transform
.TranslationModel2D
;
39 import mpicbg
.models
.NoninvertibleModelException
;
40 import mpicbg
.trakem2
.transform
.InvertibleCoordinateTransform
;
44 import ij
.gui
.GenericDialog
;
45 import ini
.trakem2
.display
.Display
;
46 import ini
.trakem2
.display
.Displayable
;
47 import ini
.trakem2
.display
.Layer
;
48 import ini
.trakem2
.display
.LayerSet
;
49 import ini
.trakem2
.display
.Patch
;
50 import ini
.trakem2
.display
.Selection
;
51 import ini
.trakem2
.display
.VectorData
;
52 import ini
.trakem2
.display
.VectorDataTransform
;
53 import ini
.trakem2
.persistence
.DBObject
;
54 import ini
.trakem2
.utils
.Worker
;
55 import ini
.trakem2
.utils
.Bureaucrat
;
56 import ini
.trakem2
.utils
.IJError
;
57 import ini
.trakem2
.utils
.M
;
58 import ini
.trakem2
.utils
.Utils
;
60 import java
.util
.concurrent
.ExecutorService
;
61 import java
.util
.concurrent
.Future
;
64 * Methods collection to be called from the GUI for alignment tasks.
67 final public class AlignTask
69 static protected int LINEAR
= 0, ELASTIC
= 1;
70 static protected int mode
= LINEAR
;
71 final static String
[] modeStrings
= new String
[]{
72 "least squares (linear feature correspondences)",
73 "elastic (non-linear block correspondences)" };
75 static protected boolean tilesAreInPlace
= false;
76 static protected boolean largestGraphOnly
= false;
77 static protected boolean hideDisconnectedTiles
= false;
78 static protected boolean deleteDisconnectedTiles
= false;
79 static protected boolean deform
= false;
81 final static public Bureaucrat
alignSelectionTask ( final Selection selection
)
83 Worker worker
= new Worker("Aligning selected images", false, true) {
87 alignSelection( selection
);
88 Display
.repaint(selection
.getLayer());
89 } catch (Throwable e
) {
95 public void cleanup() {
96 if (!selection
.isEmpty())
97 selection
.getLayer().getParent().undoOneStep();
100 return Bureaucrat
.createAndStart( worker
, selection
.getProject() );
104 final static public void alignSelection( final Selection selection
) throws Exception
106 List
< Patch
> patches
= new ArrayList
< Patch
>();
107 for ( Displayable d
: selection
.getSelected() )
108 if ( d
instanceof Patch
) patches
.add( ( Patch
)d
);
110 List
< Patch
> fixedPatches
= new ArrayList
< Patch
>();
112 // Add active Patch, if any, as the nail
113 Displayable active
= selection
.getActive();
114 if ( null != active
&& active
instanceof Patch
)
115 fixedPatches
.add( (Patch
)active
);
117 // Add all locked Patch instances to fixedPatches
118 for (final Patch patch
: patches
)
119 if ( patch
.isLocked() )
120 fixedPatches
.add( patch
);
122 alignPatches( patches
, fixedPatches
);
125 final static public Bureaucrat
alignPatchesTask ( final List
< Patch
> patches
, final List
< Patch
> fixedPatches
)
127 if ( 0 == patches
.size())
129 Utils
.log("Can't align zero patches.");
132 Worker worker
= new Worker("Aligning images", false, true) {
136 alignPatches( patches
, fixedPatches
);
138 } catch (Throwable e
) {
144 public void cleanup() {
145 patches
.get(0).getLayer().getParent().undoOneStep();
148 return Bureaucrat
.createAndStart( worker
, patches
.get(0).getProject() );
152 * @param patches: the list of Patch instances to align, all belonging to the same Layer.
153 * @param fixed: the list of Patch instances to keep locked in place, if any.
155 final static public void alignPatches( final List
< Patch
> patches
, final List
< Patch
> fixedPatches
) throws Exception
157 if ( patches
.size() < 2 )
159 Utils
.log("No images to align.");
163 for ( final Patch patch
: fixedPatches
)
165 if ( !patches
.contains( patch
) )
167 Utils
.log("The list of fixed patches contains at least one Patch not included in the list of patches to align!");
172 //final Align.ParamOptimize p = Align.paramOptimize;
174 final GenericDialog gdMode
= new GenericDialog( "Montage mode" );
175 gdMode
.addChoice( "mode :", modeStrings
, modeStrings
[ LINEAR
] );
177 if ( gdMode
.wasCanceled() )
180 mode
= gdMode
.getNextChoiceIndex();
182 if ( mode
== ELASTIC
)
183 new ElasticMontage().exec( patches
, fixedPatches
);
186 final GenericDialog gd
= new GenericDialog( "Montage" );
188 Align
.paramOptimize
.addFields( gd
);
190 gd
.addMessage( "Miscellaneous:" );
191 gd
.addCheckbox( "tiles are roughly in place", tilesAreInPlace
);
192 gd
.addCheckbox( "consider largest graph only", largestGraphOnly
);
193 gd
.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles
);
194 gd
.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles
);
197 if ( gd
.wasCanceled() ) return;
199 Align
.paramOptimize
.readFields( gd
);
200 tilesAreInPlace
= gd
.getNextBoolean();
201 largestGraphOnly
= gd
.getNextBoolean();
202 hideDisconnectedTiles
= gd
.getNextBoolean();
203 deleteDisconnectedTiles
= gd
.getNextBoolean();
205 final Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
206 alignPatches( p
, patches
, fixedPatches
, tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
);
210 /** Montage each layer independently, with SIFT.
211 * Does NOT register layers to each other.
212 * Considers visible Patches only. */
213 final static public Bureaucrat
montageLayersTask(final List
<Layer
> layers
) {
214 if (null == layers
|| layers
.isEmpty()) return null;
215 return Bureaucrat
.createAndStart(new Worker
.Task("Montaging layers", true) {
218 final GenericDialog gdMode
= new GenericDialog( "Montage mode" );
219 gdMode
.addChoice( "mode :", modeStrings
, modeStrings
[ LINEAR
] );
221 if ( gdMode
.wasCanceled() )
224 mode
= gdMode
.getNextChoiceIndex();
226 if ( mode
== ELASTIC
)
228 final ElasticMontage
.Param p
= ElasticMontage
.setup();
233 try { montageLayers( p
, layers
); }
234 catch ( Exception e
) { e
.printStackTrace(); Utils
.log( "Exception during montaging layers. Operation failed." ); }
239 //final Align.ParamOptimize p = Align.paramOptimize;
240 final GenericDialog gd
= new GenericDialog( "Montage Layers" );
241 Align
.paramOptimize
.addFields( gd
);
243 gd
.addMessage( "Miscellaneous:" );
244 gd
.addCheckbox( "tiles are roughly in place", tilesAreInPlace
);
245 gd
.addCheckbox( "consider largest graph only", largestGraphOnly
);
246 gd
.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles
);
247 gd
.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles
);
250 if ( gd
.wasCanceled() ) return;
252 Align
.paramOptimize
.readFields( gd
);
253 tilesAreInPlace
= gd
.getNextBoolean();
254 largestGraphOnly
= gd
.getNextBoolean();
255 hideDisconnectedTiles
= gd
.getNextBoolean();
256 deleteDisconnectedTiles
= gd
.getNextBoolean();
258 final Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
259 montageLayers(p
, layers
, tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
);
262 }, layers
.get(0).getProject());
265 final static public void montageLayers(
266 final Align
.ParamOptimize p
,
267 final List
<Layer
> layers
,
268 final boolean tilesAreInPlace
,
269 final boolean largestGraphOnly
,
270 final boolean hideDisconnectedTiles
,
271 final boolean deleteDisconnectedTiles
) {
273 for (final Layer layer
: layers
) {
274 if (Thread
.currentThread().isInterrupted()) return;
275 Collection
<Displayable
> patches
= layer
.getDisplayables(Patch
.class, true);
276 if (patches
.isEmpty()) continue;
277 for (final Displayable patch
: patches
) {
278 if (patch
.isLinked() && !patch
.isOnlyLinkedTo(Patch
.class)) {
279 Utils
.log("Cannot montage layer " + layer
+ "\nReason: at least one Patch is linked to non-image data: " + patch
);
283 Utils
.log("====\nMontaging layer " + layer
);
284 Utils
.showProgress(((double)i
)/layers
.size());
286 alignPatches(p
, new ArrayList
<Patch
>((Collection
<Patch
>)(Collection
)patches
), new ArrayList
<Patch
>(), tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
);
287 Display
.repaint(layer
);
292 final static public void montageLayers(
293 final ElasticMontage
.Param p
,
294 final List
< Layer
> layers
) throws Exception
297 for ( final Layer layer
: layers
)
299 if ( Thread
.currentThread().isInterrupted() ) return;
300 Collection
< Displayable
> patches
= layer
.getDisplayables( Patch
.class, true );
301 if ( patches
.isEmpty() ) continue;
302 final ArrayList
< Patch
> patchesList
= new ArrayList
< Patch
>();
303 for ( final Displayable d
: patches
)
304 if ( Patch
.class.isInstance( d
) )
305 patchesList
.add( ( Patch
)d
);
306 for (final Displayable patch
: patches
) {
307 if ( patch
.isLinked() && !patch
.isOnlyLinkedTo( Patch
.class ) )
309 Utils
.log( "Cannot montage layer " + layer
+ "\nReason: at least one Patch is linked to non-image data: " + patch
);
313 Utils
.log("====\nMontaging layer " + layer
);
314 Utils
.showProgress(((double)i
)/layers
.size());
316 new ElasticMontage().exec( p
, patchesList
, new ArrayList
< Patch
>() );
317 Display
.repaint( layer
);
321 final static private class InverseICT
implements mpicbg
.models
.InvertibleCoordinateTransform
{
322 final mpicbg
.models
.InvertibleCoordinateTransform ict
;
323 /** Sets this to the inverse of ict. */
324 InverseICT(final mpicbg
.models
.InvertibleCoordinateTransform ict
) {
327 public final float[] apply(final float[] p
) {
328 float[] q
= p
.clone();
332 public final float[] applyInverse(final float[] p
) {
333 float[] q
= p
.clone();
334 applyInverseInPlace(q
);
337 public final void applyInPlace(final float[] p
) {
339 ict
.applyInverseInPlace(p
);
340 } catch (NoninvertibleModelException e
) {
341 Utils
.log2("Point outside mesh: " + p
[0] + ", " + p
[1]);
344 public final void applyInverseInPlace(final float[] p
) {
347 public final InvertibleCoordinateTransform
createInverse() {
352 final static public void transformPatchesAndVectorData(final Layer layer
, final AffineTransform a
) {
353 AlignTask
.transformPatchesAndVectorData((Collection
<Patch
>)(Collection
)layer
.getDisplayables(Patch
.class),
354 new Runnable() { public void run() {
355 layer
.apply( Patch
.class, a
);
359 final static public void transformPatchesAndVectorData(final Collection
<Patch
> patches
, final AffineTransform a
) {
360 AlignTask
.transformPatchesAndVectorData(patches
,
361 new Runnable() { public void run() {
362 for (final Patch p
: patches
) {
363 p
.getAffineTransform().preConcatenate(a
);
369 final static public Map<Long,Patch.TransformProperties> createTransformPropertiesTable(final Collection<Patch> patches) {
370 final Map<Long,Patch.TransformProperties> tp = new HashMap<Long,Patch.TransformProperties>();
371 // Parallelize! This operation can be insanely expensive
372 final int nproc = Runtime.getRuntime().availableProcessors();
373 final ExecutorService exec = Utils.newFixedThreadPool(nproc, "AlignTask-createTransformPropertiesTable");
374 final LinkedList<Future> tasks = new LinkedList<Future>();
375 final Thread current = Thread.currentThread();
376 final AtomicInteger counter = new AtomicInteger(0);
377 Utils.log2("0/" + patches.size());
379 for (final Patch patch : patches) {
380 tasks.add(exec.submit(new Runnable() {
382 Patch.TransformProperties props = patch.getTransformPropertiesCopy();
384 tp.put(patch.getId(), props);
387 final int i = counter.incrementAndGet();
389 final String msg = new StringBuilder().append(i).append('/').append(patches.size()).toString();
391 Utils.showStatus(msg);
395 // When reaching 2*nproc, wait for nproc to complete
396 if (0 == tasks.size() % (nproc+nproc)) {
397 if (current.isInterrupted()) return tp;
400 try { tasks.removeFirst().get(); } catch (Exception e) { IJError.print(e); }
405 // Wait for remaining tasks
407 Utils.log2(patches.size() + "/" + patches.size() + " -- done!");
408 } catch (Throwable t) {
418 static public final class ReferenceData
{
419 /** Patch id vs transform */
420 final Map
<Long
,Patch
.TransformProperties
> tp
;
421 /** A map of Displayable vs a map of Layer id vs list of Patch ids in that Layer that lay under the Patch, sorted by stack index. */
422 final Map
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>> underlying
;
423 /** A list of the Layer ids form which at least one Patch was used to determine a transform of part of a VectorData instance. I.e. the visited layers. */
424 final Set
<Long
> src_layer_lids_used
;
425 ReferenceData(final Map
<Long
,Patch
.TransformProperties
> tp
, Map
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>> underlying
, Set
<Long
> src_layer_lids_used
) {
427 this.underlying
= underlying
;
428 this.src_layer_lids_used
= src_layer_lids_used
;
432 /** Creates a map only for visible patches that intersect vdata.
433 * @param src_vdata represents the VectorData instances in original form, of the original project and layer set.
434 * @param tgt_data if not null, it must have the same size as src_data and their elements correspond one-to-one (as in, tgt element a clone of src element at the same index).
435 * @param lids_to_operate The id of the layers on which any operation will be done
436 * tgt_data enables transformVectorData to apply the transforms to copies of the src_vdata in another project. */
437 final static public ReferenceData
createTransformPropertiesTable(final List
<Displayable
> src_vdata
, final List
<Displayable
> tgt_vdata
, final Set
<Long
> lids_to_operate
) {
438 if (src_vdata
.isEmpty()) return null;
439 final Map
<Long
,Patch
.TransformProperties
> tp
= new HashMap
<Long
,Patch
.TransformProperties
>();
440 // A map of Displayable vs a map of Layer id vs list of Patch ids in that Layer that lay under the Patch, sorted by stack index
441 final Map
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>> underlying
= new HashMap
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>>();
443 // The set of layers used
444 final Set
<Long
> src_layer_lids_used
= new HashSet
<Long
>();
446 // Parallelize! This operation can be insanely expensive
447 final int nproc
= Runtime
.getRuntime().availableProcessors();
448 final ExecutorService exec
= Utils
.newFixedThreadPool(nproc
, "AlignTask-createTransformPropertiesTable");
449 final List
<Future
<?
>> dtasks
= new ArrayList
<Future
<?
>>();
450 final List
<Future
<?
>> ltasks
= new ArrayList
<Future
<?
>>();
451 final Thread current
= Thread
.currentThread();
454 for (int i
=src_vdata
.size()-1; i
>-1; i
--) {
455 final Displayable src_d
= src_vdata
.get(i
);
456 if (!(src_d
instanceof VectorData
)) continue; // filter out
457 final Displayable tgt_d
= null == tgt_vdata ? src_d
: tgt_vdata
.get(i
); // use src_d if tgt_vdata is null
459 if (!(tgt_d
instanceof VectorData
)) {
460 Utils
.log("WARNING ignoring provided tgt_vdata " + tgt_d
+ " which is NOT a VectorData instance!");
463 if (src_d
.getClass() != tgt_d
.getClass()) {
464 Utils
.log("WARNING src_d and tgt_d are instances of different classes:\n src_d :: " + src_d
+ "\n tgt_d :: " + tgt_d
);
467 dtasks
.add(exec
.submit(new Runnable() {
469 final Map
<Long
,TreeMap
<Integer
,Long
>> under
= new HashMap
<Long
,TreeMap
<Integer
,Long
>>();
470 synchronized (underlying
) {
471 underlying
.put(tgt_d
, under
);
474 if (current
.isInterrupted()) return;
476 // Iterate the layers in which this VectorData has any data AND which have to be transformed
477 for (final Long olid
: src_d
.getLayerIds()) {
478 final long lid
= olid
.longValue();
480 if (!lids_to_operate
.contains(lid
)) continue; // layer with id 'lid' is not affected
482 final Layer la
= src_d
.getLayerSet().getLayer(lid
);
484 final Area a
= src_d
.getAreaAt(la
);
485 if (null == a
|| a
.isEmpty()) {
486 continue; // does not paint in the layer
489 // The list of patches that lay under VectorData d, sorted by their stack index in the layer
490 final TreeMap
<Integer
,Long
> stacked_patch_ids
= new TreeMap
<Integer
,Long
>();
491 synchronized (under
) {
492 under
.put(lid
, stacked_patch_ids
);
495 final boolean[] layer_visited
= new boolean[]{false};
497 // Iterate source patches
498 for (final Patch patch
: (Collection
<Patch
>)(Collection
)la
.getDisplayables(Patch
.class, a
, true)) { // pick visible patches only
499 if (current
.isInterrupted()) return;
502 ltasks
.add(exec
.submit(new Runnable() {
504 if (current
.isInterrupted()) return;
505 synchronized (patch
) {
506 Patch
.TransformProperties props
;
508 props
= tp
.get(patch
.getId());
511 props
= patch
.getTransformPropertiesCopy();
514 tp
.put(patch
.getId(), props
);
517 // Cache this patch as under the VectorData d
518 synchronized (stacked_patch_ids
) {
519 stacked_patch_ids
.put(la
.indexOf(patch
), patch
.getId()); // sorted by stack index
520 //Utils.log("Added patch for layer " + la + " with stack index " + la.indexOf(patch) + ", patch " + patch);
523 if (!layer_visited
[0]) {
524 // synch may fail to avoid adding it twice
525 // but it's ok since it's a Set anyway
526 layer_visited
[0] = true;
527 synchronized (src_layer_lids_used
) {
528 src_layer_lids_used
.add(la
.getId());
534 } catch (Throwable t
) {
546 } catch (Throwable t
) {
552 return new ReferenceData(tp
, underlying
, src_layer_lids_used
);
555 /** For registering within the same project instance. */
556 final static public void transformPatchesAndVectorData(final Collection
<Patch
> patches
, final Runnable alignment
) {
557 if (patches
.isEmpty()) {
558 Utils
.log("No patches to align!");
561 // 1 - Collect all VectorData to transform
562 final LayerSet ls
= patches
.iterator().next().getLayerSet();
563 final List
<Displayable
> vdata
= ls
.getDisplayables(); // from all layers
564 vdata
.addAll(ls
.getZDisplayables()); // no lazy seqs, no filter functions ... ole!
565 for (final Iterator
<Displayable
> it
= vdata
.iterator(); it
.hasNext(); ) {
566 if (it
.next() instanceof VectorData
) continue;
569 // 2 - Store current transformation of each Patch under any VectorData
570 final Set
<Long
> lids
= new HashSet
<Long
>();
571 for (final Patch p
: patches
) lids
.add(p
.getLayer().getId());
572 final ReferenceData rd
= createTransformPropertiesTable(vdata
, null, lids
);
575 // TODO check that alignTiles doesn't change the dimensions/origin of the LayerSet! That would invalidate the table of TransformProperties
576 // 4 - Transform VectorData instances to match the position of the Patch instances over which they were defined
577 if (null != rd
&& !vdata
.isEmpty()) transformVectorData(rd
, vdata
, ls
);
580 final static public void transformVectorData
581 (final ReferenceData rd
, /* The transformations of patches before alignment. */
582 final Collection
<Displayable
> vdata
, /* The VectorData instances to transform along with images. */
583 final LayerSet target_layerset
) /* The LayerSet in which the vdata and the transformed images exist. */
585 final ExecutorService exec
= Utils
.newFixedThreadPool("AlignTask-transformVectorData");
586 final Collection
<Future
<?
>> fus
= new ArrayList
<Future
<?
>>();
588 final HashMap
<Long
,Layer
> lidm
= new HashMap
<Long
,Layer
>();
589 for (final Long lid
: rd
.src_layer_lids_used
) {
590 Layer la
= target_layerset
.getLayer(lid
.longValue());
592 Utils
.log("ERROR layer with id " + lid
+ " NOT FOUND in target layerset!");
598 for (final Map
.Entry
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>> ed
: rd
.underlying
.entrySet()) {
599 final Displayable d
= ed
.getKey(); // The VectorData instance to transform
600 // Process Displayables concurrently:
601 fus
.add(exec
.submit(new Runnable() { public void run() {
602 for (final Map
.Entry
<Long
,TreeMap
<Integer
,Long
>> el
: ed
.getValue().entrySet()) {
603 // The entry has the id of the layer and the stack-index-ordered list of Patch that intersect VectorData d in that Layer
604 final Layer layer
= lidm
.get(el
.getKey());
606 Utils
.log("ERROR layer with id " + el
.getKey() + " NOT FOUND in target layerset!");
609 //Utils.log("Editing Displayable " + d + " at layer " + layer);
610 final ArrayList
<Long
> pids
= new ArrayList
<Long
>(el
.getValue().values()); // list of Patch ids affecting VectorData/Displayable d
611 Collections
.reverse(pids
); // so now Patch ids are sorted from top to bottom
612 // The area already processed in the layer
613 final Area used_area
= new Area();
614 // The map of areas vs transforms for each area to apply to the VectorData, to its data within the layer only
615 final VectorDataTransform vdt
= new VectorDataTransform(layer
);
616 // The list of transforms to apply to each VectorData
617 for (final long pid
: pids
) {
618 // Find the Patch with id 'pid' in Layer 'la' of the target LayerSet:
619 final DBObject ob
= layer
.findById(pid
);
620 if (null == ob
|| !(ob
instanceof Patch
)) {
621 Utils
.log("ERROR layer with id " + layer
.getId() + " DOES NOT CONTAIN a Patch with id " + pid
);
624 final Patch patch
= (Patch
)ob
;
625 final Patch
.TransformProperties props
= rd
.tp
.get(pid
); // no need to synch, read only from now on
627 Utils
.log("ERROR: could not find any Patch.TransformProperties for patch " + patch
);
630 final Area a
= new Area(props
.area
);
631 a
.subtract(used_area
);
633 continue; // skipping fully occluded Patch
636 used_area
.add(props
.area
);
638 // For the remaining area within this Layer, define a transform
639 // Generate a CoordinateTransformList that includes:
640 // 1 - an inverted transform from Patch coords to world coords
641 // 2 - the CoordinateTransform of the Patch, if any
642 // 3 - the AffineTransform of the Patch
644 // The idea is to first send the data from world to pixel space of the Patch, using the old transfroms,
645 // and then from pixel space of the Patch to world, using the new transforms.
648 final CoordinateTransformList tlist
= new CoordinateTransformList();
649 // 1. Inverse of the old affine: from world into the old patch mipmap
650 final mpicbg
.models
.AffineModel2D aff_inv
= new mpicbg
.models
.AffineModel2D();
652 aff_inv
.set(props
.at
.createInverse());
653 } catch (NoninvertibleTransformException nite
) {
654 Utils
.log("ERROR: could not invert the affine transform for Patch " + patch
);
660 // 2. Inverse of the old coordinate transform of the Patch: from old mipmap to pixels in original image
661 if (null != props
.ct
) {
662 // The props.ct is a CoordinateTransform, not necessarily an InvertibleCoordinateTransform
663 // So the mesh is necessary to ensure the invertibility
664 mpicbg
.trakem2
.transform
.TransformMesh mesh
= new mpicbg
.trakem2
.transform
.TransformMesh(props
.ct
, 32, props
.o_width
, props
.o_height
);
665 /* // Apparently not needed; the inverse affine in step 1 took care of it.
666 * // (the affine of step 1 includes the mesh translation)
667 Rectangle box = mesh.getBoundingBox();
668 AffineModel2D aff = new AffineModel2D();
669 aff.set(new AffineTransform(1, 0, 0, 1, box.x, box.y));
672 tlist
.add(new InverseICT(mesh
));
675 // 3. New coordinate transform of the Patch: from original image to new mipmap
676 final mpicbg
.trakem2
.transform
.CoordinateTransform ct
= patch
.getCoordinateTransform();
679 mpicbg
.trakem2
.transform
.TransformMesh mesh
= new mpicbg
.trakem2
.transform
.TransformMesh(ct
, 32, patch
.getOWidth(), patch
.getOHeight());
680 // correct for mesh bounds -- Necessary because it comes from the other side, and the removal of the translation here is re-added by the affine in step 4!
681 Rectangle box
= mesh
.getBoundingBox();
682 AffineModel2D aff
= new AffineModel2D();
683 aff
.set(new AffineTransform(1, 0, 0, 1, -box
.x
, -box
.y
));
687 // 4. New affine transform of the Patch: from mipmap to world
688 final mpicbg
.models
.AffineModel2D new_aff
= new mpicbg
.models
.AffineModel2D();
689 new_aff
.set(patch
.getAffineTransform());
693 // TODO Consider caching the tlist for each Patch, or for a few thousand of them maximum.
694 // But it could blow up memory astronomically.
697 final mpicbg.models.InvertibleCoordinateTransformList old = new mpicbg.models.InvertibleCoordinateTransformList();
698 if (null != props.ct) {
699 mpicbg.trakem2.transform.TransformMesh mesh = new mpicbg.trakem2.transform.TransformMesh(props.ct, 32, props.o_width, props.o_height);
702 final mpicbg.models.AffineModel2D old_aff = new mpicbg.models.AffineModel2D();
703 old_aff.set(props.at);
706 tlist.add(new InverseICT(old));
709 final mpicbg.models.AffineModel2D new_aff = new mpicbg.models.AffineModel2D();
710 new_aff.set(patch.getAffineTransform());
712 final mpicbg.trakem2.transform.CoordinateTransform ct = patch.getCoordinateTransform();
713 if (null != ct) tlist.add(ct);
719 // Apply the map of area vs tlist for the data section of d within the layer:
721 ((VectorData
)d
).apply(vdt
);
722 } catch (Exception t
) {
723 Utils
.log("ERROR transformation failed for " + d
+ " at layer " + layer
);
734 final static public void alignPatches(
735 final Align
.ParamOptimize p
,
736 final List
< Patch
> patches
,
737 final List
< Patch
> fixedPatches
,
738 final boolean tilesAreInPlace
,
739 final boolean largestGraphOnly
,
740 final boolean hideDisconnectedTiles
,
741 final boolean deleteDisconnectedTiles
)
743 final List
< AbstractAffineTile2D
< ?
> > tiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
744 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
745 Align
.tilesFromPatches( p
, patches
, fixedPatches
, tiles
, fixedTiles
);
747 transformPatchesAndVectorData(patches
, new Runnable() {
749 alignTiles( p
, tiles
, fixedTiles
, tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
);
755 final static public void alignTiles(
756 final Align
.ParamOptimize p
,
757 final List
< AbstractAffineTile2D
< ?
> > tiles
,
758 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
,
759 final boolean tilesAreInPlace
,
760 final boolean largestGraphOnly
,
761 final boolean hideDisconnectedTiles
,
762 final boolean deleteDisconnectedTiles
)
764 final List
< AbstractAffineTile2D
< ?
>[] > tilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
765 if ( tilesAreInPlace
)
766 AbstractAffineTile2D
.pairOverlappingTiles( tiles
, tilePairs
);
768 AbstractAffineTile2D
.pairTiles( tiles
, tilePairs
);
770 Align
.connectTilePairs( p
, tiles
, tilePairs
, Runtime
.getRuntime().availableProcessors() );
772 if ( Thread
.currentThread().isInterrupted() ) return;
774 List
< Set
< Tile
< ?
> > > graphs
= AbstractAffineTile2D
.identifyConnectedGraphs( tiles
);
776 final List
< AbstractAffineTile2D
< ?
> > interestingTiles
;
777 if ( largestGraphOnly
)
779 /* find largest graph. */
781 Set
< Tile
< ?
> > largestGraph
= null;
782 for ( Set
< Tile
< ?
> > graph
: graphs
)
783 if ( largestGraph
== null || largestGraph
.size() < graph
.size() )
784 largestGraph
= graph
;
786 interestingTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
787 for ( Tile
< ?
> t
: largestGraph
)
788 interestingTiles
.add( ( AbstractAffineTile2D
< ?
> )t
);
790 if ( hideDisconnectedTiles
)
791 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
792 if ( !interestingTiles
.contains( t
) )
793 t
.getPatch().setVisible( false );
794 if ( deleteDisconnectedTiles
)
795 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
796 if ( !interestingTiles
.contains( t
) )
797 t
.getPatch().remove( false );
801 interestingTiles
= tiles
;
804 * virtually interconnect disconnected intersecting graphs
806 * TODO Not yet tested---Do we need these virtual connections?
809 // if ( graphs.size() > 1 && tilesAreInPlace )
811 // for ( AbstractAffineTile2D< ? >[] tilePair : tilePairs )
812 // for ( Set< Tile< ? > > graph : graphs )
813 // if ( graph.contains( tilePair[ 0 ] ) && !graph.contains( tilePair[ 1 ] ) )
814 // tilePair[ 0 ].makeVirtualConnection( tilePair[ 1 ] );
818 if ( Thread
.currentThread().isInterrupted() ) return;
820 Align
.optimizeTileConfiguration( p
, interestingTiles
, fixedTiles
);
822 for ( AbstractAffineTile2D
< ?
> t
: interestingTiles
)
823 t
.getPatch().setAffineTransform( t
.getModel().createAffine() );
825 Utils
.log( "Montage done." );
828 final static public Bureaucrat
alignMultiLayerMosaicTask( final Layer l
)
830 return alignMultiLayerMosaicTask( l
, null );
833 final static public Bureaucrat
alignMultiLayerMosaicTask( final Layer l
, final Patch nail
)
835 Worker worker
= new Worker( "Aligning multi-layer mosaic", false, true )
840 try { alignMultiLayerMosaic( l
, nail
); }
841 catch ( Throwable e
) { IJError
.print( e
); }
842 finally { finishedWorking(); }
845 return Bureaucrat
.createAndStart(worker
, l
.getProject());
850 * Align a multi-layer mosaic.
852 * @param l the current layer
854 final public static void alignMultiLayerMosaic( final Layer l
, final Patch nail
)
856 /* layer range and misc */
858 final List
< Layer
> layers
= l
.getParent().getLayers();
859 final String
[] layerTitles
= new String
[ layers
.size() ];
860 for ( int i
= 0; i
< layers
.size(); ++i
)
861 layerTitles
[ i
] = l
.getProject().findLayerThing(layers
.get( i
)).toString();
863 final GenericDialog gd1
= new GenericDialog( "Align Multi-Layer Mosaic : Layer Range" );
865 gd1
.addMessage( "Layer Range:" );
866 final int sel
= l
.getParent().indexOf(l
);
867 gd1
.addChoice( "first :", layerTitles
, layerTitles
[ sel
] );
868 gd1
.addChoice( "last :", layerTitles
, layerTitles
[ sel
] );
870 gd1
.addMessage( "Miscellaneous:" );
871 gd1
.addCheckbox( "tiles are roughly in place", tilesAreInPlace
);
872 gd1
.addCheckbox( "consider largest graph only", largestGraphOnly
);
873 gd1
.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles
);
874 gd1
.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles
);
875 gd1
.addCheckbox( "deform layers", deform
);
878 if ( gd1
.wasCanceled() ) return;
880 final int first
= gd1
.getNextChoiceIndex();
881 final int last
= gd1
.getNextChoiceIndex();
882 final int d
= first
< last ?
1 : -1;
884 tilesAreInPlace
= gd1
.getNextBoolean();
885 largestGraphOnly
= gd1
.getNextBoolean();
886 hideDisconnectedTiles
= gd1
.getNextBoolean();
887 deleteDisconnectedTiles
= gd1
.getNextBoolean();
888 deform
= gd1
.getNextBoolean();
890 /* intra-layer parameters */
892 final GenericDialog gd2
= new GenericDialog( "Align Multi-Layer Mosaic : Intra-Layer" );
894 Align
.paramOptimize
.addFields( gd2
);
897 if ( gd2
.wasCanceled() ) return;
899 Align
.paramOptimize
.readFields( gd2
);
902 /* cross-layer parameters */
904 final GenericDialog gd3
= new GenericDialog( "Align Multi-Layer Mosaic : Cross-Layer" );
906 Align
.param
.addFields( gd3
);
909 if ( gd3
.wasCanceled() ) return;
911 Align
.param
.readFields( gd3
);
913 Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
914 Align
.Param cp
= Align
.param
.clone();
915 Align
.ParamOptimize pcp
= p
.clone();
916 pcp
.desiredModelIndex
= cp
.desiredModelIndex
;
918 final List
< Layer
> layerRange
= new ArrayList
< Layer
>();
919 for ( int i
= first
; i
!= last
+ d
; i
+= d
)
920 layerRange
.add( layers
.get( i
) );
922 alignMultiLayerMosaicTask( layerRange
, nail
, cp
, p
, pcp
, tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
, deform
);
925 final static private boolean alignGraphs(
929 final Set
< Tile
< ?
> > graph1
,
930 final Set
< Tile
< ?
> > graph2
)
932 final Align
.Param cp
= p
.clone();
934 final Selection selection1
= new Selection( null );
935 for ( final Tile
< ?
> tile
: graph1
)
936 selection1
.add( ( ( AbstractAffineTile2D
< ?
> )tile
).getPatch() );
937 final Rectangle graph1Box
= selection1
.getBox();
939 final Selection selection2
= new Selection( null );
940 for ( final Tile
< ?
> tile
: graph2
)
941 selection2
.add( ( ( AbstractAffineTile2D
< ?
> )tile
).getPatch() );
942 final Rectangle graph2Box
= selection2
.getBox();
944 final int maxLength
= Math
.max( Math
.max( Math
.max( graph1Box
.width
, graph1Box
.height
), graph2Box
.width
), graph2Box
.height
);
945 //final float scale = ( float )cp.sift.maxOctaveSize / maxLength;
946 /* rather ad hoc but we cannot just scale this to maxOctaveSize */
947 cp
.sift
.maxOctaveSize
= Math
.min( maxLength
, 2 * p
.sift
.maxOctaveSize
);
948 /* make sure that, despite rounding issues from scale, it is >= image size */
949 final float scale
= ( float )( cp
.sift
.maxOctaveSize
- 1 ) / maxLength
;
951 //cp.maxEpsilon *= scale;
953 final FloatArray2DSIFT sift
= new FloatArray2DSIFT( cp
.sift
);
954 final SIFT ijSIFT
= new SIFT( sift
);
955 final ArrayList
< Feature
> features1
= new ArrayList
< Feature
>();
956 final ArrayList
< Feature
> features2
= new ArrayList
< Feature
>();
957 final ArrayList
< PointMatch
> candidates
= new ArrayList
< PointMatch
>();
958 final ArrayList
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
960 long s
= System
.currentTimeMillis();
962 ijSIFT
.extractFeatures(
963 layer1
.getProject().getLoader().getFlatImage( layer1
, graph1Box
, scale
, 0xffffffff, ImagePlus
.GRAY8
, Patch
.class, selection1
.getSelected( Patch
.class ), false, Color
.GRAY
).getProcessor(),
965 Utils
.log( features1
.size() + " features extracted for graphs in layer \"" + layer1
.getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
967 ijSIFT
.extractFeatures(
968 layer2
.getProject().getLoader().getFlatImage( layer2
, graph2Box
, scale
, 0xffffffff, ImagePlus
.GRAY8
, Patch
.class, selection2
.getSelected( Patch
.class ), false, Color
.GRAY
).getProcessor(),
970 Utils
.log( features2
.size() + " features extracted for graphs in layer \"" + layer1
.getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
972 boolean modelFound
= false;
973 if ( features1
.size() > 0 && features2
.size() > 0 )
975 s
= System
.currentTimeMillis();
977 FeatureTransform
.matchFeatures(
983 final AbstractAffineModel2D
< ?
> model
;
984 switch ( cp
.expectedModelIndex
)
987 model
= new TranslationModel2D();
990 model
= new RigidModel2D();
993 model
= new SimilarityModel2D();
996 model
= new AffineModel2D();
1002 boolean again
= false;
1008 modelFound
= model
.filterRansac(
1016 if ( modelFound
&& cp
.rejectIdentity
)
1018 final ArrayList
< Point
> points
= new ArrayList
< Point
>();
1019 PointMatch
.sourcePoints( inliers
, points
);
1020 if ( Transforms
.isIdentity( model
, points
, cp
.identityTolerance
) )
1022 IJ
.log( "Identity transform for " + inliers
.size() + " matches rejected." );
1023 candidates
.removeAll( inliers
);
1031 catch ( NotEnoughDataPointsException e
)
1038 Utils
.log( "Model found for graphs in layer \"" + layer1
.getTitle() + "\" and \"" + layer2
.getTitle() + "\":\n correspondences " + inliers
.size() + " of " + candidates
.size() + "\n average residual error " + ( model
.getCost() / scale
) + " px\n took " + ( System
.currentTimeMillis() - s
) + " ms" );
1039 final AffineTransform b
= new AffineTransform();
1040 b
.translate( graph2Box
.x
, graph2Box
.y
);
1041 b
.scale( 1.0f
/ scale
, 1.0f
/ scale
);
1042 b
.concatenate( model
.createAffine() );
1043 b
.scale( scale
, scale
);
1044 b
.translate( -graph1Box
.x
, -graph1Box
.y
);
1046 for ( Displayable d
: selection1
.getSelected( Patch
.class ) )
1047 d
.preTransform( b
, false );
1048 Display
.repaint( layer1
);
1051 IJ
.log( "No model found for graphs in layer \"" + layer1
.getTitle() + "\" and \"" + layer2
.getTitle() + "\":\n correspondence candidates " + candidates
.size() + "\n took " + ( System
.currentTimeMillis() - s
) + " ms" );
1058 public static final void alignMultiLayerMosaicTask(
1059 final List
< Layer
> layerRange
,
1061 final Align
.Param cp
,
1062 final Align
.ParamOptimize p
,
1063 final Align
.ParamOptimize pcp
,
1064 final boolean tilesAreInPlace
,
1065 final boolean largestGraphOnly
,
1066 final boolean hideDisconnectedTiles
,
1067 final boolean deleteDisconnectedTiles
,
1068 final boolean deform
)
1073 final List
< AbstractAffineTile2D
< ?
> > allTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1074 final List
< AbstractAffineTile2D
< ?
> > allFixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1075 final List
< AbstractAffineTile2D
< ?
> > previousLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1076 final HashMap
< Patch
, PointMatch
> tileCenterPoints
= new HashMap
< Patch
, PointMatch
>();
1078 Collection
< Patch
> fixedPatches
= new HashSet
< Patch
>();
1080 fixedPatches
.add( nail
);
1082 Layer previousLayer
= null;
1084 for ( final Layer layer
: layerRange
)
1086 /* align all tiles in the layer */
1088 final List
< Patch
> patches
= new ArrayList
< Patch
>();
1089 for ( Displayable a
: layer
.getDisplayables( Patch
.class ) )
1090 if ( a
instanceof Patch
) patches
.add( ( Patch
)a
);
1091 final List
< AbstractAffineTile2D
< ?
> > currentLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1092 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
1093 Align
.tilesFromPatches( p
, patches
, fixedPatches
, currentLayerTiles
, fixedTiles
);
1095 alignTiles( p
, currentLayerTiles
, fixedTiles
, tilesAreInPlace
, false, false, false ); // Will consider graphs and hide/delete tiles when all cross-layer graphs are found.
1096 if (Thread
.currentThread().isInterrupted()) return;
1098 /* connect to the previous layer */
1101 /* generate tiles with the cross-section model from the current layer tiles */
1102 /* ------------------------------------------------------------------------ */
1103 /* TODO step back and make tiles bare containers for a patch and a model such that by changing the model the tile can be reused */
1104 final HashMap
< Patch
, AbstractAffineTile2D
< ?
> > currentLayerPatchTiles
= new HashMap
< Patch
, AbstractAffineTile2D
<?
> >();
1105 for ( final AbstractAffineTile2D
< ?
> t
: currentLayerTiles
)
1106 currentLayerPatchTiles
.put( t
.getPatch(), t
);
1108 final List
< AbstractAffineTile2D
< ?
> > csCurrentLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1109 final Set
< AbstractAffineTile2D
< ?
> > csFixedTiles
= new HashSet
< AbstractAffineTile2D
< ?
> > ();
1110 Align
.tilesFromPatches( cp
, patches
, fixedPatches
, csCurrentLayerTiles
, csFixedTiles
);
1112 final HashMap
< Tile
< ?
>, AbstractAffineTile2D
< ?
> > tileTiles
= new HashMap
< Tile
< ?
>, AbstractAffineTile2D
<?
> >();
1113 for ( final AbstractAffineTile2D
< ?
> t
: csCurrentLayerTiles
)
1114 tileTiles
.put( currentLayerPatchTiles
.get( t
.getPatch() ), t
);
1116 for ( final AbstractAffineTile2D
< ?
> t
: currentLayerTiles
)
1118 final AbstractAffineTile2D
< ?
> csLayerTile
= tileTiles
.get( t
);
1119 csLayerTile
.addMatches( t
.getMatches() );
1120 for ( Tile
< ?
> ct
: t
.getConnectedTiles() )
1121 csLayerTile
.addConnectedTile( tileTiles
.get( ct
) );
1124 /* add a fixed tile only if there was a Patch selected */
1125 allFixedTiles
.addAll( csFixedTiles
);
1127 /* first, align connected graphs to each other */
1129 /* graphs in the current layer */
1130 final List
< Set
< Tile
< ?
> > > currentLayerGraphs
= AbstractAffineTile2D
.identifyConnectedGraphs( csCurrentLayerTiles
);
1131 if (Thread
.currentThread().isInterrupted()) return;
1133 // /* TODO just for visualization */
1134 // for ( final Set< Tile< ? > > graph : currentLayerGraphs )
1136 // Display.getFront().getSelection().clear();
1137 // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
1139 // for ( final Tile< ? > tile : graph )
1141 // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
1142 // Display.repaint();
1144 // Utils.showMessage( "OK" );
1147 /* graphs from the whole system that are present in the previous layer */
1148 final List
< Set
< Tile
< ?
> > > graphs
= AbstractAffineTile2D
.identifyConnectedGraphs( allTiles
);
1149 final HashMap
< Set
< Tile
< ?
> >, Set
< Tile
< ?
> > > graphGraphs
= new HashMap
< Set
<Tile
<?
>>, Set
<Tile
<?
>> >();
1150 for ( final Set
< Tile
< ?
> > graph
: graphs
)
1152 if (Thread
.currentThread().isInterrupted()) return;
1153 final Set
< Tile
< ?
> > previousLayerGraph
= new HashSet
< Tile
< ?
> >();
1154 for ( final Tile
< ?
> tile
: previousLayerTiles
)
1156 if ( graph
.contains( tile
) )
1158 graphGraphs
.put( graph
, previousLayerGraph
);
1159 previousLayerGraph
.add( tile
);
1163 final Collection
< Set
< Tile
< ?
> > > previousLayerGraphs
= graphGraphs
.values();
1165 // /* TODO just for visualization */
1166 // for ( final Set< Tile< ? > > graph : previousLayerGraphs )
1168 // Display.getFront().getSelection().clear();
1169 // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
1171 // for ( final Tile< ? > tile : graph )
1173 // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
1174 // Display.repaint();
1176 // Utils.showMessage( "OK" );
1179 /* generate snapshots of the graphs and preregister them using the parameters defined in cp */
1180 final List
< AbstractAffineTile2D
< ?
>[] > crossLayerTilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
1181 for ( final Set
< Tile
< ?
> > currentLayerGraph
: currentLayerGraphs
)
1183 for ( final Set
< Tile
< ?
> > previousLayerGraph
: previousLayerGraphs
)
1185 if (Thread
.currentThread().isInterrupted()) return;
1186 alignGraphs( cp
, layer
, previousLayer
, currentLayerGraph
, previousLayerGraph
);
1188 /* TODO this is pointless data shuffling just for type incompatibility---fix this at the root */
1189 final ArrayList
< AbstractAffineTile2D
< ?
> > previousLayerGraphTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1190 previousLayerGraphTiles
.addAll( ( Set
)previousLayerGraph
);
1192 final ArrayList
< AbstractAffineTile2D
< ?
> > currentLayerGraphTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1193 currentLayerGraphTiles
.addAll( ( Set
)currentLayerGraph
);
1195 AbstractAffineTile2D
.pairOverlappingTiles( previousLayerGraphTiles
, currentLayerGraphTiles
, crossLayerTilePairs
);
1200 /* ------------------------------------------------------------------------ */
1203 /* this is without the affine/rigid approximation per graph */
1204 //AbstractAffineTile2D.pairTiles( previousLayerTiles, csCurrentLayerTiles, crossLayerTilePairs );
1206 Align
.connectTilePairs( cp
, csCurrentLayerTiles
, crossLayerTilePairs
, Runtime
.getRuntime().availableProcessors() );
1207 if (Thread
.currentThread().isInterrupted()) return;
1209 // for ( final AbstractAffineTile2D< ? >[] tilePair : crossLayerTilePairs )
1211 // Display.getFront().setLayer( tilePair[ 0 ].getPatch().getLayer() );
1212 // Display.getFront().getSelection().clear();
1213 // Display.getFront().getSelection().add( tilePair[ 0 ].getPatch() );
1214 // Display.getFront().getSelection().add( tilePair[ 1 ].getPatch() );
1216 // Utils.showMessage( "1: OK?" );
1218 // Display.getFront().setLayer( tilePair[ 1 ].getPatch().getLayer() );
1219 // Display.getFront().getSelection().clear();
1220 // Display.getFront().getSelection().add( tilePair[ 0 ].getPatch() );
1221 // Display.getFront().getSelection().add( tilePair[ 1 ].getPatch() );
1223 // Utils.showMessage( "2: OK?" );
1226 /* prepare the next loop */
1228 allTiles
.addAll( csCurrentLayerTiles
);
1229 previousLayerTiles
.clear();
1230 previousLayerTiles
.addAll( csCurrentLayerTiles
);
1233 Align
.optimizeTileConfiguration( pcp
, allTiles
, allFixedTiles
);
1234 if (Thread
.currentThread().isInterrupted()) return;
1236 for ( AbstractAffineTile2D
< ?
> t
: allTiles
)
1237 t
.getPatch().setAffineTransform( t
.getModel().createAffine() );
1239 previousLayer
= layer
;
1242 List
< Set
< Tile
< ?
> > > graphs
= AbstractAffineTile2D
.identifyConnectedGraphs( allTiles
);
1244 final List
< AbstractAffineTile2D
< ?
> > interestingTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1246 if ( largestGraphOnly
&& ( hideDisconnectedTiles
|| deleteDisconnectedTiles
) )
1248 if ( Thread
.currentThread().isInterrupted() ) return;
1250 /* find largest graph. */
1252 Set
< Tile
< ?
> > largestGraph
= null;
1253 for ( Set
< Tile
< ?
> > graph
: graphs
)
1254 if ( largestGraph
== null || largestGraph
.size() < graph
.size() )
1255 largestGraph
= graph
;
1257 final Set
<AbstractAffineTile2D
<?
>> tiles_to_keep
= new HashSet
<AbstractAffineTile2D
<?
>>();
1259 for ( Tile
< ?
> t
: largestGraph
)
1260 tiles_to_keep
.add( ( AbstractAffineTile2D
< ?
> )t
);
1262 if ( hideDisconnectedTiles
)
1263 for ( AbstractAffineTile2D
< ?
> t
: allTiles
)
1264 if ( !tiles_to_keep
.contains( t
) )
1265 t
.getPatch().setVisible( false );
1266 if ( deleteDisconnectedTiles
)
1267 for ( AbstractAffineTile2D
< ?
> t
: allTiles
)
1268 if ( !tiles_to_keep
.contains( t
) )
1269 t
.getPatch().remove( false );
1271 interestingTiles
.addAll(tiles_to_keep
);
1274 interestingTiles
.addAll( allTiles
);
1279 /* ############################################ */
1280 /* experimental: use the center points of all tiles to define a MLS deformation from the pure intra-layer registration to the globally optimal */
1282 Utils
.log( "deforming..." );
1284 /* store the center location of each single tile for later deformation */
1285 for ( final AbstractAffineTile2D
< ?
> t
: interestingTiles
)
1287 final float[] c
= new float[]{ ( float )t
.getWidth() / 2.0f
,( float )t
.getHeight() / 2.0f
};
1288 t
.getModel().applyInPlace( c
);
1289 final Point q
= new Point( c
);
1290 tileCenterPoints
.put( t
.getPatch(), new PointMatch( q
.clone(), q
) );
1293 for ( final Layer layer
: layerRange
)
1295 Utils
.log( "layer" + layer
);
1297 if ( Thread
.currentThread().isInterrupted() ) return;
1299 /* again, align all tiles in the layer */
1301 List
< Patch
> patches
= new ArrayList
< Patch
>();
1302 for ( Displayable a
: layer
.getDisplayables( Patch
.class ) )
1303 if ( a
instanceof Patch
) patches
.add( ( Patch
)a
);
1304 final List
< AbstractAffineTile2D
< ?
> > currentLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1305 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
1306 Align
.tilesFromPatches( p
, patches
, fixedPatches
, currentLayerTiles
, fixedTiles
);
1308 /* add a fixed tile only if there was a Patch selected */
1309 allFixedTiles
.addAll( fixedTiles
);
1311 alignTiles( p
, currentLayerTiles
, fixedTiles
, true, false, false, false ); // will consider graphs and hide/delete tiles when all cross-layer graphs are found
1313 /* for each independent graph do an independent transform */
1314 final List
< Set
< Tile
< ?
> > > currentLayerGraphs
= AbstractAffineTile2D
.identifyConnectedGraphs( currentLayerTiles
);
1315 for ( final Set
< Tile
< ?
> > graph
: currentLayerGraphs
)
1318 /* update the tile-center pointmatches */
1319 final Collection
< PointMatch
> matches
= new ArrayList
< PointMatch
>();
1320 final Collection
< AbstractAffineTile2D
< ?
> > toBeDeformedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1321 for ( final AbstractAffineTile2D
< ?
> t
: ( Collection
< AbstractAffineTile2D
< ?
> > )( Collection
)graph
)
1323 final PointMatch pm
= tileCenterPoints
.get( t
.getPatch() );
1324 if ( pm
== null ) continue;
1326 final float[] pl
= pm
.getP1().getL();
1327 pl
[ 0 ] = ( float )t
.getWidth() / 2.0f
;
1328 pl
[ 1 ] = ( float )t
.getHeight() / 2.0f
;
1329 t
.getModel().applyInPlace( pl
);
1331 toBeDeformedTiles
.add( t
);
1334 for ( final AbstractAffineTile2D
< ?
> t
: toBeDeformedTiles
)
1336 if ( Thread
.currentThread().isInterrupted() ) return;
1340 final Patch patch
= t
.getPatch();
1341 final Rectangle pbox
= patch
.getCoordinateTransformBoundingBox();
1342 final AffineTransform pat
= new AffineTransform();
1343 pat
.translate( -pbox
.x
, -pbox
.y
);
1344 pat
.preConcatenate( patch
.getAffineTransform() );
1346 final mpicbg
.trakem2
.transform
.AffineModel2D toWorld
= new mpicbg
.trakem2
.transform
.AffineModel2D();
1349 final MovingLeastSquaresTransform mlst
= Align
.createMLST( matches
, 1.0f
);
1351 final CoordinateTransformList
< CoordinateTransform
> ctl
= new CoordinateTransformList
< CoordinateTransform
>();
1354 ctl
.add( toWorld
.createInverse() );
1356 patch
.appendCoordinateTransform( ctl
);
1358 patch
.getProject().getLoader().regenerateMipMaps( patch
);
1360 catch ( Exception e
)
1362 e
.printStackTrace();
1369 layerRange
.get(0).getParent().setMinimumDimensions();
1370 IJ
.log( "Done: register multi-layer mosaic." );
1376 /** The ParamOptimize object containg all feature extraction and registration model parameters for the "snap" function. */
1377 static public final Align
.ParamOptimize p_snap
= Align
.paramOptimize
.clone();
1379 /** Find the most overlapping image to @param patch in the same layer where @param patch sits, and snap @param patch and all its linked Displayable objects.
1380 * If a null @param p_snap is given, it will use the AlignTask.p_snap.
1381 * If @param setup is true, it will show a dialog to adjust parameters. */
1382 static public final Bureaucrat
snap(final Patch patch
, final Align
.ParamOptimize p_snap
, final boolean setup
) {
1383 return Bureaucrat
.createAndStart(new Worker
.Task("Snapping", true) {
1384 public void exec() {
1386 final Align
.ParamOptimize p
= null == p_snap ? AlignTask
.p_snap
: p_snap
;
1387 if (setup
) p
.setup("Snap");
1389 // Collect Patch linked to active
1390 final List
<Displayable
> linked_images
= new ArrayList
<Displayable
>();
1391 for (final Displayable d
: patch
.getLinkedGroup(null)) {
1392 if (d
.getClass() == Patch
.class && d
!= patch
) linked_images
.add(d
);
1394 // Find overlapping images
1395 final List
<Patch
> overlapping
= new ArrayList
<Patch
>( (Collection
<Patch
>) (Collection
) patch
.getLayer().getIntersecting(patch
, Patch
.class));
1396 overlapping
.remove(patch
);
1397 if (0 == overlapping
.size()) return; // nothing overlaps
1399 // Discard from overlapping any linked images
1400 overlapping
.removeAll(linked_images
);
1402 if (0 == overlapping
.size()) {
1403 Utils
.log("Cannot snap: overlapping images are linked to the one to snap.");
1408 linked_images
.clear();
1410 // Find the image that overlaps the most
1411 Rectangle box
= patch
.getBoundingBox(null);
1413 Rectangle most_inter
= null;
1414 for (final Patch other
: overlapping
) {
1417 most_inter
= other
.getBoundingBox();
1420 Rectangle inter
= other
.getBoundingBox().intersection(box
);
1421 if (inter
.width
* inter
.height
> most_inter
.width
* most_inter
.height
) {
1427 overlapping
.clear();
1429 // Define two lists:
1430 // - a list with all involved tiles: the active and the most overlapping one
1431 final List
<Patch
> patches
= new ArrayList
<Patch
>();
1434 // - a list with all tiles except the active, to be set as fixed, immobile
1435 final List
<Patch
> fixedPatches
= new ArrayList
<Patch
>();
1436 fixedPatches
.add(most
);
1439 List
< AbstractAffineTile2D
< ?
> > tiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1440 List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
1441 Align
.tilesFromPatches( p
, patches
, fixedPatches
, tiles
, fixedTiles
);
1443 // Pair and connect overlapping tiles
1444 final List
< AbstractAffineTile2D
< ?
>[] > tilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
1445 AbstractAffineTile2D
.pairOverlappingTiles( tiles
, tilePairs
);
1446 Align
.connectTilePairs( p
, tiles
, tilePairs
, Runtime
.getRuntime().availableProcessors() );
1448 if ( Thread
.currentThread().isInterrupted() ) return;
1450 Align
.optimizeTileConfiguration( p
, tiles
, fixedTiles
);
1452 for ( AbstractAffineTile2D
< ?
> t
: tiles
) {
1453 if (t
.getPatch() == patch
) {
1454 AffineTransform at
= t
.getModel().createAffine();
1456 at
.concatenate(patch
.getAffineTransform().createInverse());
1457 patch
.transform(at
);
1458 } catch (NoninvertibleTransformException nite
) {
1459 IJError
.print(nite
);
1467 }}, patch
.getProject());
1470 static public final Bureaucrat
registerStackSlices(final Patch slice
) {
1471 return Bureaucrat
.createAndStart(new Worker
.Task("Registering slices", true) {
1472 public void exec() {
1475 ArrayList
<Patch
> slices
= slice
.getStackPatches();
1476 if (slices
.size() < 2) {
1477 Utils
.log2("Not a stack!");
1481 // check that none are linked to anything other than images
1482 for (final Patch patch
: slices
) {
1483 if (!patch
.isOnlyLinkedTo(Patch
.class)) {
1484 Utils
.log("Can't register: one or more slices are linked to objects other than images.");
1490 final Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
1491 p
.setup("Register stack slices");
1493 List
<Patch
> fixedSlices
= new ArrayList
<Patch
>();
1494 fixedSlices
.add(slice
);
1496 alignPatches( p
, slices
, fixedSlices
, false, false, false, false );
1500 }}, slice
.getProject());