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
.MovingLeastSquaresTransform2
;
38 import mpicbg
.trakem2
.transform
.RigidModel2D
;
39 import mpicbg
.trakem2
.transform
.TranslationModel2D
;
40 import mpicbg
.models
.NoninvertibleModelException
;
41 import mpicbg
.trakem2
.transform
.InvertibleCoordinateTransform
;
45 import ij
.gui
.GenericDialog
;
46 import ini
.trakem2
.display
.Display
;
47 import ini
.trakem2
.display
.Displayable
;
48 import ini
.trakem2
.display
.Layer
;
49 import ini
.trakem2
.display
.LayerSet
;
50 import ini
.trakem2
.display
.Patch
;
51 import ini
.trakem2
.display
.Selection
;
52 import ini
.trakem2
.display
.VectorData
;
53 import ini
.trakem2
.display
.VectorDataTransform
;
54 import ini
.trakem2
.persistence
.DBObject
;
55 import ini
.trakem2
.utils
.Worker
;
56 import ini
.trakem2
.utils
.Bureaucrat
;
57 import ini
.trakem2
.utils
.IJError
;
58 import ini
.trakem2
.utils
.M
;
59 import ini
.trakem2
.utils
.Utils
;
61 import java
.util
.concurrent
.ExecutorService
;
62 import java
.util
.concurrent
.Future
;
65 * Methods collection to be called from the GUI for alignment tasks.
68 final public class AlignTask
70 static protected int LINEAR
= 0, ELASTIC
= 1;
71 static protected int mode
= LINEAR
;
72 final static String
[] modeStrings
= new String
[]{
73 "least squares (linear feature correspondences)",
74 "elastic (non-linear block correspondences)" };
76 static protected boolean tilesAreInPlace
= false;
77 static protected boolean largestGraphOnly
= false;
78 static protected boolean hideDisconnectedTiles
= false;
79 static protected boolean deleteDisconnectedTiles
= false;
80 static protected boolean deform
= false;
82 final static public Bureaucrat
alignSelectionTask ( final Selection selection
)
84 Worker worker
= new Worker("Aligning selected images", false, true) {
88 alignSelection( selection
);
89 Display
.repaint(selection
.getLayer());
90 } catch (Throwable e
) {
96 public void cleanup() {
97 if (!selection
.isEmpty())
98 selection
.getLayer().getParent().undoOneStep();
101 return Bureaucrat
.createAndStart( worker
, selection
.getProject() );
105 final static public void alignSelection( final Selection selection
) throws Exception
107 List
< Patch
> patches
= new ArrayList
< Patch
>();
108 for ( Displayable d
: selection
.getSelected() )
109 if ( d
instanceof Patch
) patches
.add( ( Patch
)d
);
111 List
< Patch
> fixedPatches
= new ArrayList
< Patch
>();
113 // Add active Patch, if any, as the nail
114 Displayable active
= selection
.getActive();
115 if ( null != active
&& active
instanceof Patch
)
116 fixedPatches
.add( (Patch
)active
);
118 // Add all locked Patch instances to fixedPatches
119 for (final Patch patch
: patches
)
120 if ( patch
.isLocked() )
121 fixedPatches
.add( patch
);
123 alignPatches( patches
, fixedPatches
);
126 final static public Bureaucrat
alignPatchesTask ( final List
< Patch
> patches
, final List
< Patch
> fixedPatches
)
128 if ( 0 == patches
.size())
130 Utils
.log("Can't align zero patches.");
133 Worker worker
= new Worker("Aligning images", false, true) {
137 alignPatches( patches
, fixedPatches
);
139 } catch (Throwable e
) {
145 public void cleanup() {
146 patches
.get(0).getLayer().getParent().undoOneStep();
149 return Bureaucrat
.createAndStart( worker
, patches
.get(0).getProject() );
153 * @param patches: the list of Patch instances to align, all belonging to the same Layer.
154 * @param fixed: the list of Patch instances to keep locked in place, if any.
156 final static public void alignPatches( final List
< Patch
> patches
, final List
< Patch
> fixedPatches
) throws Exception
158 if ( patches
.size() < 2 )
160 Utils
.log("No images to align.");
164 for ( final Patch patch
: fixedPatches
)
166 if ( !patches
.contains( patch
) )
168 Utils
.log("The list of fixed patches contains at least one Patch not included in the list of patches to align!");
173 //final Align.ParamOptimize p = Align.paramOptimize;
175 final GenericDialog gdMode
= new GenericDialog( "Montage mode" );
176 gdMode
.addChoice( "mode :", modeStrings
, modeStrings
[ LINEAR
] );
178 if ( gdMode
.wasCanceled() )
181 mode
= gdMode
.getNextChoiceIndex();
183 if ( mode
== ELASTIC
)
184 new ElasticMontage().exec( patches
, fixedPatches
);
187 final GenericDialog gd
= new GenericDialog( "Montage" );
189 Align
.paramOptimize
.addFields( gd
);
191 gd
.addMessage( "Miscellaneous:" );
192 gd
.addCheckbox( "tiles are roughly in place", tilesAreInPlace
);
193 gd
.addCheckbox( "consider largest graph only", largestGraphOnly
);
194 gd
.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles
);
195 gd
.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles
);
198 if ( gd
.wasCanceled() ) return;
200 Align
.paramOptimize
.readFields( gd
);
201 tilesAreInPlace
= gd
.getNextBoolean();
202 largestGraphOnly
= gd
.getNextBoolean();
203 hideDisconnectedTiles
= gd
.getNextBoolean();
204 deleteDisconnectedTiles
= gd
.getNextBoolean();
206 final Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
207 alignPatches( p
, patches
, fixedPatches
, tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
);
211 /** Montage each layer independently, with SIFT.
212 * Does NOT register layers to each other.
213 * Considers visible Patches only. */
214 final static public Bureaucrat
montageLayersTask(final List
<Layer
> layers
) {
215 if (null == layers
|| layers
.isEmpty()) return null;
216 return Bureaucrat
.createAndStart(new Worker
.Task("Montaging layers", true) {
219 final GenericDialog gdMode
= new GenericDialog( "Montage mode" );
220 gdMode
.addChoice( "mode :", modeStrings
, modeStrings
[ LINEAR
] );
222 if ( gdMode
.wasCanceled() )
225 mode
= gdMode
.getNextChoiceIndex();
227 if ( mode
== ELASTIC
)
229 final ElasticMontage
.Param p
= ElasticMontage
.setup();
234 try { montageLayers( p
, layers
); }
235 catch ( Exception e
) { e
.printStackTrace(); Utils
.log( "Exception during montaging layers. Operation failed." ); }
240 //final Align.ParamOptimize p = Align.paramOptimize;
241 final GenericDialog gd
= new GenericDialog( "Montage Layers" );
242 Align
.paramOptimize
.addFields( gd
);
244 gd
.addMessage( "Miscellaneous:" );
245 gd
.addCheckbox( "tiles are roughly in place", tilesAreInPlace
);
246 gd
.addCheckbox( "consider largest graph only", largestGraphOnly
);
247 gd
.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles
);
248 gd
.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles
);
251 if ( gd
.wasCanceled() ) return;
253 Align
.paramOptimize
.readFields( gd
);
254 tilesAreInPlace
= gd
.getNextBoolean();
255 largestGraphOnly
= gd
.getNextBoolean();
256 hideDisconnectedTiles
= gd
.getNextBoolean();
257 deleteDisconnectedTiles
= gd
.getNextBoolean();
259 final Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
260 montageLayers(p
, layers
, tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
);
263 }, layers
.get(0).getProject());
266 final static public void montageLayers(
267 final Align
.ParamOptimize p
,
268 final List
<Layer
> layers
,
269 final boolean tilesAreInPlace
,
270 final boolean largestGraphOnly
,
271 final boolean hideDisconnectedTiles
,
272 final boolean deleteDisconnectedTiles
) {
274 for (final Layer layer
: layers
) {
275 if (Thread
.currentThread().isInterrupted()) return;
276 Collection
<Displayable
> patches
= layer
.getDisplayables(Patch
.class, true);
277 if (patches
.isEmpty()) continue;
278 for (final Displayable patch
: patches
) {
279 if (patch
.isLinked() && !patch
.isOnlyLinkedTo(Patch
.class)) {
280 Utils
.log("Cannot montage layer " + layer
+ "\nReason: at least one Patch is linked to non-image data: " + patch
);
284 Utils
.log("====\nMontaging layer " + layer
);
285 Utils
.showProgress(((double)i
)/layers
.size());
287 alignPatches(p
, new ArrayList
<Patch
>((Collection
<Patch
>)(Collection
)patches
), new ArrayList
<Patch
>(), tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
);
288 Display
.repaint(layer
);
293 final static public void montageLayers(
294 final ElasticMontage
.Param p
,
295 final List
< Layer
> layers
) throws Exception
298 for ( final Layer layer
: layers
)
300 if ( Thread
.currentThread().isInterrupted() ) return;
301 Collection
< Displayable
> patches
= layer
.getDisplayables( Patch
.class, true );
302 if ( patches
.isEmpty() ) continue;
303 final ArrayList
< Patch
> patchesList
= new ArrayList
< Patch
>();
304 for ( final Displayable d
: patches
)
305 if ( Patch
.class.isInstance( d
) )
306 patchesList
.add( ( Patch
)d
);
307 for (final Displayable patch
: patches
) {
308 if ( patch
.isLinked() && !patch
.isOnlyLinkedTo( Patch
.class ) )
310 Utils
.log( "Cannot montage layer " + layer
+ "\nReason: at least one Patch is linked to non-image data: " + patch
);
314 Utils
.log("====\nMontaging layer " + layer
);
315 Utils
.showProgress(((double)i
)/layers
.size());
317 new ElasticMontage().exec( p
, patchesList
, new ArrayList
< Patch
>() );
318 Display
.repaint( layer
);
322 final static private class InverseICT
implements mpicbg
.models
.InvertibleCoordinateTransform
{
323 final mpicbg
.models
.InvertibleCoordinateTransform ict
;
324 /** Sets this to the inverse of ict. */
325 InverseICT(final mpicbg
.models
.InvertibleCoordinateTransform ict
) {
328 public final float[] apply(final float[] p
) {
329 float[] q
= p
.clone();
333 public final float[] applyInverse(final float[] p
) {
334 float[] q
= p
.clone();
335 applyInverseInPlace(q
);
338 public final void applyInPlace(final float[] p
) {
340 ict
.applyInverseInPlace(p
);
341 } catch (NoninvertibleModelException e
) {
342 Utils
.log2("Point outside mesh: " + p
[0] + ", " + p
[1]);
345 public final void applyInverseInPlace(final float[] p
) {
348 public final InvertibleCoordinateTransform
createInverse() {
353 final static public void transformPatchesAndVectorData(final Layer layer
, final AffineTransform a
) {
354 AlignTask
.transformPatchesAndVectorData((Collection
<Patch
>)(Collection
)layer
.getDisplayables(Patch
.class),
355 new Runnable() { public void run() {
356 layer
.apply( Patch
.class, a
);
360 final static public void transformPatchesAndVectorData(final Collection
<Patch
> patches
, final AffineTransform a
) {
361 AlignTask
.transformPatchesAndVectorData(patches
,
362 new Runnable() { public void run() {
363 for (final Patch p
: patches
) {
364 p
.getAffineTransform().preConcatenate(a
);
370 final static public Map<Long,Patch.TransformProperties> createTransformPropertiesTable(final Collection<Patch> patches) {
371 final Map<Long,Patch.TransformProperties> tp = new HashMap<Long,Patch.TransformProperties>();
372 // Parallelize! This operation can be insanely expensive
373 final int nproc = Runtime.getRuntime().availableProcessors();
374 final ExecutorService exec = Utils.newFixedThreadPool(nproc, "AlignTask-createTransformPropertiesTable");
375 final LinkedList<Future> tasks = new LinkedList<Future>();
376 final Thread current = Thread.currentThread();
377 final AtomicInteger counter = new AtomicInteger(0);
378 Utils.log2("0/" + patches.size());
380 for (final Patch patch : patches) {
381 tasks.add(exec.submit(new Runnable() {
383 Patch.TransformProperties props = patch.getTransformPropertiesCopy();
385 tp.put(patch.getId(), props);
388 final int i = counter.incrementAndGet();
390 final String msg = new StringBuilder().append(i).append('/').append(patches.size()).toString();
392 Utils.showStatus(msg);
396 // When reaching 2*nproc, wait for nproc to complete
397 if (0 == tasks.size() % (nproc+nproc)) {
398 if (current.isInterrupted()) return tp;
401 try { tasks.removeFirst().get(); } catch (Exception e) { IJError.print(e); }
406 // Wait for remaining tasks
408 Utils.log2(patches.size() + "/" + patches.size() + " -- done!");
409 } catch (Throwable t) {
419 static public final class ReferenceData
{
420 /** Patch id vs transform */
421 final Map
<Long
,Patch
.TransformProperties
> tp
;
422 /** 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. */
423 final Map
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>> underlying
;
424 /** 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. */
425 final Set
<Long
> src_layer_lids_used
;
426 ReferenceData(final Map
<Long
,Patch
.TransformProperties
> tp
, Map
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>> underlying
, Set
<Long
> src_layer_lids_used
) {
428 this.underlying
= underlying
;
429 this.src_layer_lids_used
= src_layer_lids_used
;
433 /** Creates a map only for visible patches that intersect vdata.
434 * @param src_vdata represents the VectorData instances in original form, of the original project and layer set.
435 * @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).
436 * @param lids_to_operate The id of the layers on which any operation will be done
437 * tgt_data enables transformVectorData to apply the transforms to copies of the src_vdata in another project. */
438 final static public ReferenceData
createTransformPropertiesTable(final List
<Displayable
> src_vdata
, final List
<Displayable
> tgt_vdata
, final Set
<Long
> lids_to_operate
) {
439 if (src_vdata
.isEmpty()) return null;
440 final Map
<Long
,Patch
.TransformProperties
> tp
= new HashMap
<Long
,Patch
.TransformProperties
>();
441 // 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
442 final Map
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>> underlying
= new HashMap
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>>();
444 // The set of layers used
445 final Set
<Long
> src_layer_lids_used
= new HashSet
<Long
>();
447 // Parallelize! This operation can be insanely expensive
448 final int nproc
= Runtime
.getRuntime().availableProcessors();
449 final ExecutorService exec
= Utils
.newFixedThreadPool(nproc
, "AlignTask-createTransformPropertiesTable");
450 final List
<Future
<?
>> dtasks
= new ArrayList
<Future
<?
>>();
451 final List
<Future
<?
>> ltasks
= new ArrayList
<Future
<?
>>();
452 final Thread current
= Thread
.currentThread();
455 for (int i
=src_vdata
.size()-1; i
>-1; i
--) {
456 final Displayable src_d
= src_vdata
.get(i
);
457 if (!(src_d
instanceof VectorData
)) continue; // filter out
458 final Displayable tgt_d
= null == tgt_vdata ? src_d
: tgt_vdata
.get(i
); // use src_d if tgt_vdata is null
460 if (!(tgt_d
instanceof VectorData
)) {
461 Utils
.log("WARNING ignoring provided tgt_vdata " + tgt_d
+ " which is NOT a VectorData instance!");
464 if (src_d
.getClass() != tgt_d
.getClass()) {
465 Utils
.log("WARNING src_d and tgt_d are instances of different classes:\n src_d :: " + src_d
+ "\n tgt_d :: " + tgt_d
);
468 dtasks
.add(exec
.submit(new Runnable() {
470 final Map
<Long
,TreeMap
<Integer
,Long
>> under
= new HashMap
<Long
,TreeMap
<Integer
,Long
>>();
471 synchronized (underlying
) {
472 underlying
.put(tgt_d
, under
);
475 if (current
.isInterrupted()) return;
477 // Iterate the layers in which this VectorData has any data AND which have to be transformed
478 for (final Long olid
: src_d
.getLayerIds()) {
479 final long lid
= olid
.longValue();
481 if (!lids_to_operate
.contains(lid
)) continue; // layer with id 'lid' is not affected
483 final Layer la
= src_d
.getLayerSet().getLayer(lid
);
485 final Area a
= src_d
.getAreaAt(la
);
486 if (null == a
|| a
.isEmpty()) {
487 continue; // does not paint in the layer
490 // The list of patches that lay under VectorData d, sorted by their stack index in the layer
491 final TreeMap
<Integer
,Long
> stacked_patch_ids
= new TreeMap
<Integer
,Long
>();
492 synchronized (under
) {
493 under
.put(lid
, stacked_patch_ids
);
496 final boolean[] layer_visited
= new boolean[]{false};
498 // Iterate source patches
499 for (final Patch patch
: (Collection
<Patch
>)(Collection
)la
.getDisplayables(Patch
.class, a
, true)) { // pick visible patches only
500 if (current
.isInterrupted()) return;
503 ltasks
.add(exec
.submit(new Runnable() {
505 if (current
.isInterrupted()) return;
506 synchronized (patch
) {
507 Patch
.TransformProperties props
;
509 props
= tp
.get(patch
.getId());
512 props
= patch
.getTransformPropertiesCopy();
515 tp
.put(patch
.getId(), props
);
518 // Cache this patch as under the VectorData d
519 synchronized (stacked_patch_ids
) {
520 stacked_patch_ids
.put(la
.indexOf(patch
), patch
.getId()); // sorted by stack index
521 //Utils.log("Added patch for layer " + la + " with stack index " + la.indexOf(patch) + ", patch " + patch);
524 if (!layer_visited
[0]) {
525 // synch may fail to avoid adding it twice
526 // but it's ok since it's a Set anyway
527 layer_visited
[0] = true;
528 synchronized (src_layer_lids_used
) {
529 src_layer_lids_used
.add(la
.getId());
535 } catch (Throwable t
) {
547 } catch (Throwable t
) {
553 return new ReferenceData(tp
, underlying
, src_layer_lids_used
);
556 /** For registering within the same project instance. */
557 final static public void transformPatchesAndVectorData(final Collection
<Patch
> patches
, final Runnable alignment
) {
558 if (patches
.isEmpty()) {
559 Utils
.log("No patches to align!");
562 // 1 - Collect all VectorData to transform
563 final LayerSet ls
= patches
.iterator().next().getLayerSet();
564 final List
<Displayable
> vdata
= ls
.getDisplayables(); // from all layers
565 vdata
.addAll(ls
.getZDisplayables()); // no lazy seqs, no filter functions ... ole!
566 for (final Iterator
<Displayable
> it
= vdata
.iterator(); it
.hasNext(); ) {
567 if (it
.next() instanceof VectorData
) continue;
570 // 2 - Store current transformation of each Patch under any VectorData
571 final Set
<Long
> lids
= new HashSet
<Long
>();
572 for (final Patch p
: patches
) lids
.add(p
.getLayer().getId());
573 final ReferenceData rd
= createTransformPropertiesTable(vdata
, null, lids
);
576 // TODO check that alignTiles doesn't change the dimensions/origin of the LayerSet! That would invalidate the table of TransformProperties
577 // 4 - Transform VectorData instances to match the position of the Patch instances over which they were defined
578 if (null != rd
&& !vdata
.isEmpty()) transformVectorData(rd
, vdata
, ls
);
581 final static public void transformVectorData
582 (final ReferenceData rd
, /* The transformations of patches before alignment. */
583 final Collection
<Displayable
> vdata
, /* The VectorData instances to transform along with images. */
584 final LayerSet target_layerset
) /* The LayerSet in which the vdata and the transformed images exist. */
586 final ExecutorService exec
= Utils
.newFixedThreadPool("AlignTask-transformVectorData");
587 final Collection
<Future
<?
>> fus
= new ArrayList
<Future
<?
>>();
589 final HashMap
<Long
,Layer
> lidm
= new HashMap
<Long
,Layer
>();
590 for (final Long lid
: rd
.src_layer_lids_used
) {
591 Layer la
= target_layerset
.getLayer(lid
.longValue());
593 Utils
.log("ERROR layer with id " + lid
+ " NOT FOUND in target layerset!");
599 for (final Map
.Entry
<Displayable
,Map
<Long
,TreeMap
<Integer
,Long
>>> ed
: rd
.underlying
.entrySet()) {
600 final Displayable d
= ed
.getKey(); // The VectorData instance to transform
601 // Process Displayables concurrently:
602 fus
.add(exec
.submit(new Runnable() { public void run() {
603 for (final Map
.Entry
<Long
,TreeMap
<Integer
,Long
>> el
: ed
.getValue().entrySet()) {
604 // The entry has the id of the layer and the stack-index-ordered list of Patch that intersect VectorData d in that Layer
605 final Layer layer
= lidm
.get(el
.getKey());
607 Utils
.log("ERROR layer with id " + el
.getKey() + " NOT FOUND in target layerset!");
610 //Utils.log("Editing Displayable " + d + " at layer " + layer);
611 final ArrayList
<Long
> pids
= new ArrayList
<Long
>(el
.getValue().values()); // list of Patch ids affecting VectorData/Displayable d
612 Collections
.reverse(pids
); // so now Patch ids are sorted from top to bottom
613 // The area already processed in the layer
614 final Area used_area
= new Area();
615 // The map of areas vs transforms for each area to apply to the VectorData, to its data within the layer only
616 final VectorDataTransform vdt
= new VectorDataTransform(layer
);
617 // The list of transforms to apply to each VectorData
618 for (final long pid
: pids
) {
619 // Find the Patch with id 'pid' in Layer 'la' of the target LayerSet:
620 final DBObject ob
= layer
.findById(pid
);
621 if (null == ob
|| !(ob
instanceof Patch
)) {
622 Utils
.log("ERROR layer with id " + layer
.getId() + " DOES NOT CONTAIN a Patch with id " + pid
);
625 final Patch patch
= (Patch
)ob
;
626 final Patch
.TransformProperties props
= rd
.tp
.get(pid
); // no need to synch, read only from now on
628 Utils
.log("ERROR: could not find any Patch.TransformProperties for patch " + patch
);
631 final Area a
= new Area(props
.area
);
632 a
.subtract(used_area
);
634 continue; // skipping fully occluded Patch
637 used_area
.add(props
.area
);
639 // For the remaining area within this Layer, define a transform
640 // Generate a CoordinateTransformList that includes:
641 // 1 - an inverted transform from Patch coords to world coords
642 // 2 - the CoordinateTransform of the Patch, if any
643 // 3 - the AffineTransform of the Patch
645 // The idea is to first send the data from world to pixel space of the Patch, using the old transfroms,
646 // and then from pixel space of the Patch to world, using the new transforms.
649 final CoordinateTransformList tlist
= new CoordinateTransformList();
650 // 1. Inverse of the old affine: from world into the old patch mipmap
651 final mpicbg
.models
.AffineModel2D aff_inv
= new mpicbg
.models
.AffineModel2D();
653 aff_inv
.set(props
.at
.createInverse());
654 } catch (NoninvertibleTransformException nite
) {
655 Utils
.log("ERROR: could not invert the affine transform for Patch " + patch
);
661 // 2. Inverse of the old coordinate transform of the Patch: from old mipmap to pixels in original image
662 if (null != props
.ct
) {
663 // The props.ct is a CoordinateTransform, not necessarily an InvertibleCoordinateTransform
664 // So the mesh is necessary to ensure the invertibility
665 mpicbg
.trakem2
.transform
.TransformMesh mesh
= new mpicbg
.trakem2
.transform
.TransformMesh(props
.ct
, 32, props
.o_width
, props
.o_height
);
666 /* // Apparently not needed; the inverse affine in step 1 took care of it.
667 * // (the affine of step 1 includes the mesh translation)
668 Rectangle box = mesh.getBoundingBox();
669 AffineModel2D aff = new AffineModel2D();
670 aff.set(new AffineTransform(1, 0, 0, 1, box.x, box.y));
673 tlist
.add(new InverseICT(mesh
));
676 // 3. New coordinate transform of the Patch: from original image to new mipmap
677 final mpicbg
.trakem2
.transform
.CoordinateTransform ct
= patch
.getCoordinateTransform();
680 mpicbg
.trakem2
.transform
.TransformMesh mesh
= new mpicbg
.trakem2
.transform
.TransformMesh(ct
, 32, patch
.getOWidth(), patch
.getOHeight());
681 // 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!
682 Rectangle box
= mesh
.getBoundingBox();
683 AffineModel2D aff
= new AffineModel2D();
684 aff
.set(new AffineTransform(1, 0, 0, 1, -box
.x
, -box
.y
));
688 // 4. New affine transform of the Patch: from mipmap to world
689 final mpicbg
.models
.AffineModel2D new_aff
= new mpicbg
.models
.AffineModel2D();
690 new_aff
.set(patch
.getAffineTransform());
694 // TODO Consider caching the tlist for each Patch, or for a few thousand of them maximum.
695 // But it could blow up memory astronomically.
698 final mpicbg.models.InvertibleCoordinateTransformList old = new mpicbg.models.InvertibleCoordinateTransformList();
699 if (null != props.ct) {
700 mpicbg.trakem2.transform.TransformMesh mesh = new mpicbg.trakem2.transform.TransformMesh(props.ct, 32, props.o_width, props.o_height);
703 final mpicbg.models.AffineModel2D old_aff = new mpicbg.models.AffineModel2D();
704 old_aff.set(props.at);
707 tlist.add(new InverseICT(old));
710 final mpicbg.models.AffineModel2D new_aff = new mpicbg.models.AffineModel2D();
711 new_aff.set(patch.getAffineTransform());
713 final mpicbg.trakem2.transform.CoordinateTransform ct = patch.getCoordinateTransform();
714 if (null != ct) tlist.add(ct);
720 // Apply the map of area vs tlist for the data section of d within the layer:
722 ((VectorData
)d
).apply(vdt
);
723 } catch (Exception t
) {
724 Utils
.log("ERROR transformation failed for " + d
+ " at layer " + layer
);
735 final static public void alignPatches(
736 final Align
.ParamOptimize p
,
737 final List
< Patch
> patches
,
738 final List
< Patch
> fixedPatches
,
739 final boolean tilesAreInPlace
,
740 final boolean largestGraphOnly
,
741 final boolean hideDisconnectedTiles
,
742 final boolean deleteDisconnectedTiles
)
744 final List
< AbstractAffineTile2D
< ?
> > tiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
745 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
746 Align
.tilesFromPatches( p
, patches
, fixedPatches
, tiles
, fixedTiles
);
748 transformPatchesAndVectorData(patches
, new Runnable() {
750 alignTiles( p
, tiles
, fixedTiles
, tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
);
756 final static public void alignTiles(
757 final Align
.ParamOptimize p
,
758 final List
< AbstractAffineTile2D
< ?
> > tiles
,
759 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
,
760 final boolean tilesAreInPlace
,
761 final boolean largestGraphOnly
,
762 final boolean hideDisconnectedTiles
,
763 final boolean deleteDisconnectedTiles
)
765 final List
< AbstractAffineTile2D
< ?
>[] > tilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
766 if ( tilesAreInPlace
)
767 AbstractAffineTile2D
.pairOverlappingTiles( tiles
, tilePairs
);
769 AbstractAffineTile2D
.pairTiles( tiles
, tilePairs
);
771 Align
.connectTilePairs( p
, tiles
, tilePairs
, Runtime
.getRuntime().availableProcessors() );
773 if ( Thread
.currentThread().isInterrupted() ) return;
775 List
< Set
< Tile
< ?
> > > graphs
= AbstractAffineTile2D
.identifyConnectedGraphs( tiles
);
777 final List
< AbstractAffineTile2D
< ?
> > interestingTiles
;
778 if ( largestGraphOnly
)
780 /* find largest graph. */
782 Set
< Tile
< ?
> > largestGraph
= null;
783 for ( Set
< Tile
< ?
> > graph
: graphs
)
784 if ( largestGraph
== null || largestGraph
.size() < graph
.size() )
785 largestGraph
= graph
;
787 interestingTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
788 for ( Tile
< ?
> t
: largestGraph
)
789 interestingTiles
.add( ( AbstractAffineTile2D
< ?
> )t
);
791 if ( hideDisconnectedTiles
)
792 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
793 if ( !interestingTiles
.contains( t
) )
794 t
.getPatch().setVisible( false );
795 if ( deleteDisconnectedTiles
)
796 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
797 if ( !interestingTiles
.contains( t
) )
798 t
.getPatch().remove( false );
802 interestingTiles
= tiles
;
805 * virtually interconnect disconnected intersecting graphs
807 * TODO Not yet tested---Do we need these virtual connections?
810 // if ( graphs.size() > 1 && tilesAreInPlace )
812 // for ( AbstractAffineTile2D< ? >[] tilePair : tilePairs )
813 // for ( Set< Tile< ? > > graph : graphs )
814 // if ( graph.contains( tilePair[ 0 ] ) && !graph.contains( tilePair[ 1 ] ) )
815 // tilePair[ 0 ].makeVirtualConnection( tilePair[ 1 ] );
819 if ( Thread
.currentThread().isInterrupted() ) return;
821 Align
.optimizeTileConfiguration( p
, interestingTiles
, fixedTiles
);
823 for ( AbstractAffineTile2D
< ?
> t
: interestingTiles
)
824 t
.getPatch().setAffineTransform( t
.getModel().createAffine() );
826 Utils
.log( "Montage done." );
829 final static public Bureaucrat
alignMultiLayerMosaicTask( final Layer l
)
831 return alignMultiLayerMosaicTask( l
, null );
834 final static public Bureaucrat
alignMultiLayerMosaicTask( final Layer l
, final Patch nail
)
836 Worker worker
= new Worker( "Aligning multi-layer mosaic", false, true )
841 try { alignMultiLayerMosaic( l
, nail
); }
842 catch ( Throwable e
) { IJError
.print( e
); }
843 finally { finishedWorking(); }
846 return Bureaucrat
.createAndStart(worker
, l
.getProject());
851 * Align a multi-layer mosaic.
853 * @param l the current layer
855 final public static void alignMultiLayerMosaic( final Layer l
, final Patch nail
)
857 /* layer range and misc */
859 final List
< Layer
> layers
= l
.getParent().getLayers();
860 final String
[] layerTitles
= new String
[ layers
.size() ];
861 for ( int i
= 0; i
< layers
.size(); ++i
)
862 layerTitles
[ i
] = l
.getProject().findLayerThing(layers
.get( i
)).toString();
864 final GenericDialog gd1
= new GenericDialog( "Align Multi-Layer Mosaic : Layer Range" );
866 gd1
.addMessage( "Layer Range:" );
867 final int sel
= l
.getParent().indexOf(l
);
868 gd1
.addChoice( "first :", layerTitles
, layerTitles
[ sel
] );
869 gd1
.addChoice( "last :", layerTitles
, layerTitles
[ sel
] );
871 gd1
.addMessage( "Miscellaneous:" );
872 gd1
.addCheckbox( "tiles are roughly in place", tilesAreInPlace
);
873 gd1
.addCheckbox( "consider largest graph only", largestGraphOnly
);
874 gd1
.addCheckbox( "hide tiles from non-largest graph", hideDisconnectedTiles
);
875 gd1
.addCheckbox( "delete tiles from non-largest graph", deleteDisconnectedTiles
);
876 gd1
.addCheckbox( "deform layers", deform
);
879 if ( gd1
.wasCanceled() ) return;
881 final int first
= gd1
.getNextChoiceIndex();
882 final int last
= gd1
.getNextChoiceIndex();
883 final int d
= first
< last ?
1 : -1;
885 tilesAreInPlace
= gd1
.getNextBoolean();
886 largestGraphOnly
= gd1
.getNextBoolean();
887 hideDisconnectedTiles
= gd1
.getNextBoolean();
888 deleteDisconnectedTiles
= gd1
.getNextBoolean();
889 deform
= gd1
.getNextBoolean();
891 /* intra-layer parameters */
893 final GenericDialog gd2
= new GenericDialog( "Align Multi-Layer Mosaic : Intra-Layer" );
895 Align
.paramOptimize
.addFields( gd2
);
898 if ( gd2
.wasCanceled() ) return;
900 Align
.paramOptimize
.readFields( gd2
);
903 /* cross-layer parameters */
905 final GenericDialog gd3
= new GenericDialog( "Align Multi-Layer Mosaic : Cross-Layer" );
907 Align
.param
.addFields( gd3
);
910 if ( gd3
.wasCanceled() ) return;
912 Align
.param
.readFields( gd3
);
914 Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
915 Align
.Param cp
= Align
.param
.clone();
916 Align
.ParamOptimize pcp
= p
.clone();
917 pcp
.desiredModelIndex
= cp
.desiredModelIndex
;
919 final List
< Layer
> layerRange
= new ArrayList
< Layer
>();
920 for ( int i
= first
; i
!= last
+ d
; i
+= d
)
921 layerRange
.add( layers
.get( i
) );
923 alignMultiLayerMosaicTask( layerRange
, nail
, cp
, p
, pcp
, tilesAreInPlace
, largestGraphOnly
, hideDisconnectedTiles
, deleteDisconnectedTiles
, deform
);
926 final static private boolean alignGraphs(
930 final Set
< Tile
< ?
> > graph1
,
931 final Set
< Tile
< ?
> > graph2
)
933 final Align
.Param cp
= p
.clone();
935 final Selection selection1
= new Selection( null );
936 for ( final Tile
< ?
> tile
: graph1
)
937 selection1
.add( ( ( AbstractAffineTile2D
< ?
> )tile
).getPatch() );
938 final Rectangle graph1Box
= selection1
.getBox();
940 final Selection selection2
= new Selection( null );
941 for ( final Tile
< ?
> tile
: graph2
)
942 selection2
.add( ( ( AbstractAffineTile2D
< ?
> )tile
).getPatch() );
943 final Rectangle graph2Box
= selection2
.getBox();
945 final int maxLength
= Math
.max( Math
.max( Math
.max( graph1Box
.width
, graph1Box
.height
), graph2Box
.width
), graph2Box
.height
);
946 //final float scale = ( float )cp.sift.maxOctaveSize / maxLength;
947 /* rather ad hoc but we cannot just scale this to maxOctaveSize */
948 cp
.sift
.maxOctaveSize
= Math
.min( maxLength
, 2 * p
.sift
.maxOctaveSize
);
949 /* make sure that, despite rounding issues from scale, it is >= image size */
950 final float scale
= ( float )( cp
.sift
.maxOctaveSize
- 1 ) / maxLength
;
952 //cp.maxEpsilon *= scale;
954 final FloatArray2DSIFT sift
= new FloatArray2DSIFT( cp
.sift
);
955 final SIFT ijSIFT
= new SIFT( sift
);
956 final ArrayList
< Feature
> features1
= new ArrayList
< Feature
>();
957 final ArrayList
< Feature
> features2
= new ArrayList
< Feature
>();
958 final ArrayList
< PointMatch
> candidates
= new ArrayList
< PointMatch
>();
959 final ArrayList
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
961 long s
= System
.currentTimeMillis();
963 ijSIFT
.extractFeatures(
964 layer1
.getProject().getLoader().getFlatImage( layer1
, graph1Box
, scale
, 0xffffffff, ImagePlus
.GRAY8
, Patch
.class, selection1
.getSelected( Patch
.class ), false, Color
.GRAY
).getProcessor(),
966 Utils
.log( features1
.size() + " features extracted for graphs in layer \"" + layer1
.getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
968 ijSIFT
.extractFeatures(
969 layer2
.getProject().getLoader().getFlatImage( layer2
, graph2Box
, scale
, 0xffffffff, ImagePlus
.GRAY8
, Patch
.class, selection2
.getSelected( Patch
.class ), false, Color
.GRAY
).getProcessor(),
971 Utils
.log( features2
.size() + " features extracted for graphs in layer \"" + layer1
.getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
973 boolean modelFound
= false;
974 if ( features1
.size() > 0 && features2
.size() > 0 )
976 s
= System
.currentTimeMillis();
978 FeatureTransform
.matchFeatures(
984 final AbstractAffineModel2D
< ?
> model
;
985 switch ( cp
.expectedModelIndex
)
988 model
= new TranslationModel2D();
991 model
= new RigidModel2D();
994 model
= new SimilarityModel2D();
997 model
= new AffineModel2D();
1003 boolean again
= false;
1009 modelFound
= model
.filterRansac(
1017 if ( modelFound
&& cp
.rejectIdentity
)
1019 final ArrayList
< Point
> points
= new ArrayList
< Point
>();
1020 PointMatch
.sourcePoints( inliers
, points
);
1021 if ( Transforms
.isIdentity( model
, points
, cp
.identityTolerance
) )
1023 IJ
.log( "Identity transform for " + inliers
.size() + " matches rejected." );
1024 candidates
.removeAll( inliers
);
1032 catch ( NotEnoughDataPointsException e
)
1039 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" );
1040 final AffineTransform b
= new AffineTransform();
1041 b
.translate( graph2Box
.x
, graph2Box
.y
);
1042 b
.scale( 1.0f
/ scale
, 1.0f
/ scale
);
1043 b
.concatenate( model
.createAffine() );
1044 b
.scale( scale
, scale
);
1045 b
.translate( -graph1Box
.x
, -graph1Box
.y
);
1047 for ( Displayable d
: selection1
.getSelected( Patch
.class ) )
1048 d
.preTransform( b
, false );
1049 Display
.repaint( layer1
);
1052 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" );
1059 public static final void alignMultiLayerMosaicTask(
1060 final List
< Layer
> layerRange
,
1062 final Align
.Param cp
,
1063 final Align
.ParamOptimize p
,
1064 final Align
.ParamOptimize pcp
,
1065 final boolean tilesAreInPlace
,
1066 final boolean largestGraphOnly
,
1067 final boolean hideDisconnectedTiles
,
1068 final boolean deleteDisconnectedTiles
,
1069 final boolean deform
)
1074 final List
< AbstractAffineTile2D
< ?
> > allTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1075 final List
< AbstractAffineTile2D
< ?
> > allFixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1076 final List
< AbstractAffineTile2D
< ?
> > previousLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1077 final HashMap
< Patch
, PointMatch
> tileCenterPoints
= new HashMap
< Patch
, PointMatch
>();
1079 Collection
< Patch
> fixedPatches
= new HashSet
< Patch
>();
1081 fixedPatches
.add( nail
);
1083 Layer previousLayer
= null;
1085 for ( final Layer layer
: layerRange
)
1087 /* align all tiles in the layer */
1089 final List
< Patch
> patches
= new ArrayList
< Patch
>();
1090 for ( Displayable a
: layer
.getDisplayables( Patch
.class ) )
1091 if ( a
instanceof Patch
) patches
.add( ( Patch
)a
);
1092 final List
< AbstractAffineTile2D
< ?
> > currentLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1093 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
1094 Align
.tilesFromPatches( p
, patches
, fixedPatches
, currentLayerTiles
, fixedTiles
);
1096 alignTiles( p
, currentLayerTiles
, fixedTiles
, tilesAreInPlace
, false, false, false ); // Will consider graphs and hide/delete tiles when all cross-layer graphs are found.
1097 if (Thread
.currentThread().isInterrupted()) return;
1099 /* connect to the previous layer */
1102 /* generate tiles with the cross-section model from the current layer tiles */
1103 /* ------------------------------------------------------------------------ */
1104 /* 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 */
1105 final HashMap
< Patch
, AbstractAffineTile2D
< ?
> > currentLayerPatchTiles
= new HashMap
< Patch
, AbstractAffineTile2D
<?
> >();
1106 for ( final AbstractAffineTile2D
< ?
> t
: currentLayerTiles
)
1107 currentLayerPatchTiles
.put( t
.getPatch(), t
);
1109 final List
< AbstractAffineTile2D
< ?
> > csCurrentLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1110 final Set
< AbstractAffineTile2D
< ?
> > csFixedTiles
= new HashSet
< AbstractAffineTile2D
< ?
> > ();
1111 Align
.tilesFromPatches( cp
, patches
, fixedPatches
, csCurrentLayerTiles
, csFixedTiles
);
1113 final HashMap
< Tile
< ?
>, AbstractAffineTile2D
< ?
> > tileTiles
= new HashMap
< Tile
< ?
>, AbstractAffineTile2D
<?
> >();
1114 for ( final AbstractAffineTile2D
< ?
> t
: csCurrentLayerTiles
)
1115 tileTiles
.put( currentLayerPatchTiles
.get( t
.getPatch() ), t
);
1117 for ( final AbstractAffineTile2D
< ?
> t
: currentLayerTiles
)
1119 final AbstractAffineTile2D
< ?
> csLayerTile
= tileTiles
.get( t
);
1120 csLayerTile
.addMatches( t
.getMatches() );
1121 for ( Tile
< ?
> ct
: t
.getConnectedTiles() )
1122 csLayerTile
.addConnectedTile( tileTiles
.get( ct
) );
1125 /* add a fixed tile only if there was a Patch selected */
1126 allFixedTiles
.addAll( csFixedTiles
);
1128 /* first, align connected graphs to each other */
1130 /* graphs in the current layer */
1131 final List
< Set
< Tile
< ?
> > > currentLayerGraphs
= AbstractAffineTile2D
.identifyConnectedGraphs( csCurrentLayerTiles
);
1132 if (Thread
.currentThread().isInterrupted()) return;
1134 // /* TODO just for visualization */
1135 // for ( final Set< Tile< ? > > graph : currentLayerGraphs )
1137 // Display.getFront().getSelection().clear();
1138 // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
1140 // for ( final Tile< ? > tile : graph )
1142 // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
1143 // Display.repaint();
1145 // Utils.showMessage( "OK" );
1148 /* graphs from the whole system that are present in the previous layer */
1149 final List
< Set
< Tile
< ?
> > > graphs
= AbstractAffineTile2D
.identifyConnectedGraphs( allTiles
);
1150 final HashMap
< Set
< Tile
< ?
> >, Set
< Tile
< ?
> > > graphGraphs
= new HashMap
< Set
<Tile
<?
>>, Set
<Tile
<?
>> >();
1151 for ( final Set
< Tile
< ?
> > graph
: graphs
)
1153 if (Thread
.currentThread().isInterrupted()) return;
1154 final Set
< Tile
< ?
> > previousLayerGraph
= new HashSet
< Tile
< ?
> >();
1155 for ( final Tile
< ?
> tile
: previousLayerTiles
)
1157 if ( graph
.contains( tile
) )
1159 graphGraphs
.put( graph
, previousLayerGraph
);
1160 previousLayerGraph
.add( tile
);
1164 final Collection
< Set
< Tile
< ?
> > > previousLayerGraphs
= graphGraphs
.values();
1166 // /* TODO just for visualization */
1167 // for ( final Set< Tile< ? > > graph : previousLayerGraphs )
1169 // Display.getFront().getSelection().clear();
1170 // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
1172 // for ( final Tile< ? > tile : graph )
1174 // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
1175 // Display.repaint();
1177 // Utils.showMessage( "OK" );
1180 /* generate snapshots of the graphs and preregister them using the parameters defined in cp */
1181 final List
< AbstractAffineTile2D
< ?
>[] > crossLayerTilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
1182 for ( final Set
< Tile
< ?
> > currentLayerGraph
: currentLayerGraphs
)
1184 for ( final Set
< Tile
< ?
> > previousLayerGraph
: previousLayerGraphs
)
1186 if (Thread
.currentThread().isInterrupted()) return;
1187 alignGraphs( cp
, layer
, previousLayer
, currentLayerGraph
, previousLayerGraph
);
1189 /* TODO this is pointless data shuffling just for type incompatibility---fix this at the root */
1190 final ArrayList
< AbstractAffineTile2D
< ?
> > previousLayerGraphTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1191 previousLayerGraphTiles
.addAll( ( Set
)previousLayerGraph
);
1193 final ArrayList
< AbstractAffineTile2D
< ?
> > currentLayerGraphTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1194 currentLayerGraphTiles
.addAll( ( Set
)currentLayerGraph
);
1196 AbstractAffineTile2D
.pairOverlappingTiles( previousLayerGraphTiles
, currentLayerGraphTiles
, crossLayerTilePairs
);
1201 /* ------------------------------------------------------------------------ */
1204 /* this is without the affine/rigid approximation per graph */
1205 //AbstractAffineTile2D.pairTiles( previousLayerTiles, csCurrentLayerTiles, crossLayerTilePairs );
1207 Align
.connectTilePairs( cp
, csCurrentLayerTiles
, crossLayerTilePairs
, Runtime
.getRuntime().availableProcessors() );
1208 if (Thread
.currentThread().isInterrupted()) return;
1210 // for ( final AbstractAffineTile2D< ? >[] tilePair : crossLayerTilePairs )
1212 // Display.getFront().setLayer( tilePair[ 0 ].getPatch().getLayer() );
1213 // Display.getFront().getSelection().clear();
1214 // Display.getFront().getSelection().add( tilePair[ 0 ].getPatch() );
1215 // Display.getFront().getSelection().add( tilePair[ 1 ].getPatch() );
1217 // Utils.showMessage( "1: OK?" );
1219 // Display.getFront().setLayer( tilePair[ 1 ].getPatch().getLayer() );
1220 // Display.getFront().getSelection().clear();
1221 // Display.getFront().getSelection().add( tilePair[ 0 ].getPatch() );
1222 // Display.getFront().getSelection().add( tilePair[ 1 ].getPatch() );
1224 // Utils.showMessage( "2: OK?" );
1227 /* prepare the next loop */
1229 allTiles
.addAll( csCurrentLayerTiles
);
1230 previousLayerTiles
.clear();
1231 previousLayerTiles
.addAll( csCurrentLayerTiles
);
1234 Align
.optimizeTileConfiguration( pcp
, allTiles
, allFixedTiles
);
1235 if (Thread
.currentThread().isInterrupted()) return;
1237 for ( AbstractAffineTile2D
< ?
> t
: allTiles
)
1238 t
.getPatch().setAffineTransform( t
.getModel().createAffine() );
1240 previousLayer
= layer
;
1243 List
< Set
< Tile
< ?
> > > graphs
= AbstractAffineTile2D
.identifyConnectedGraphs( allTiles
);
1245 final List
< AbstractAffineTile2D
< ?
> > interestingTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1247 if ( largestGraphOnly
&& ( hideDisconnectedTiles
|| deleteDisconnectedTiles
) )
1249 if ( Thread
.currentThread().isInterrupted() ) return;
1251 /* find largest graph. */
1253 Set
< Tile
< ?
> > largestGraph
= null;
1254 for ( Set
< Tile
< ?
> > graph
: graphs
)
1255 if ( largestGraph
== null || largestGraph
.size() < graph
.size() )
1256 largestGraph
= graph
;
1258 final Set
<AbstractAffineTile2D
<?
>> tiles_to_keep
= new HashSet
<AbstractAffineTile2D
<?
>>();
1260 for ( Tile
< ?
> t
: largestGraph
)
1261 tiles_to_keep
.add( ( AbstractAffineTile2D
< ?
> )t
);
1263 if ( hideDisconnectedTiles
)
1264 for ( AbstractAffineTile2D
< ?
> t
: allTiles
)
1265 if ( !tiles_to_keep
.contains( t
) )
1266 t
.getPatch().setVisible( false );
1267 if ( deleteDisconnectedTiles
)
1268 for ( AbstractAffineTile2D
< ?
> t
: allTiles
)
1269 if ( !tiles_to_keep
.contains( t
) )
1270 t
.getPatch().remove( false );
1272 interestingTiles
.addAll(tiles_to_keep
);
1275 interestingTiles
.addAll( allTiles
);
1280 /* ############################################ */
1281 /* experimental: use the center points of all tiles to define a MLS deformation from the pure intra-layer registration to the globally optimal */
1283 Utils
.log( "deforming..." );
1285 /* store the center location of each single tile for later deformation */
1286 for ( final AbstractAffineTile2D
< ?
> t
: interestingTiles
)
1288 final float[] c
= new float[]{ ( float )t
.getWidth() / 2.0f
,( float )t
.getHeight() / 2.0f
};
1289 t
.getModel().applyInPlace( c
);
1290 final Point q
= new Point( c
);
1291 tileCenterPoints
.put( t
.getPatch(), new PointMatch( q
.clone(), q
) );
1294 for ( final Layer layer
: layerRange
)
1296 Utils
.log( "layer" + layer
);
1298 if ( Thread
.currentThread().isInterrupted() ) return;
1300 /* again, align all tiles in the layer */
1302 List
< Patch
> patches
= new ArrayList
< Patch
>();
1303 for ( Displayable a
: layer
.getDisplayables( Patch
.class ) )
1304 if ( a
instanceof Patch
) patches
.add( ( Patch
)a
);
1305 final List
< AbstractAffineTile2D
< ?
> > currentLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1306 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
1307 Align
.tilesFromPatches( p
, patches
, fixedPatches
, currentLayerTiles
, fixedTiles
);
1309 /* add a fixed tile only if there was a Patch selected */
1310 allFixedTiles
.addAll( fixedTiles
);
1312 alignTiles( p
, currentLayerTiles
, fixedTiles
, true, false, false, false ); // will consider graphs and hide/delete tiles when all cross-layer graphs are found
1314 /* for each independent graph do an independent transform */
1315 final List
< Set
< Tile
< ?
> > > currentLayerGraphs
= AbstractAffineTile2D
.identifyConnectedGraphs( currentLayerTiles
);
1316 for ( final Set
< Tile
< ?
> > graph
: currentLayerGraphs
)
1319 /* update the tile-center pointmatches */
1320 final Collection
< PointMatch
> matches
= new ArrayList
< PointMatch
>();
1321 final Collection
< AbstractAffineTile2D
< ?
> > toBeDeformedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1322 for ( final AbstractAffineTile2D
< ?
> t
: ( Collection
< AbstractAffineTile2D
< ?
> > )( Collection
)graph
)
1324 final PointMatch pm
= tileCenterPoints
.get( t
.getPatch() );
1325 if ( pm
== null ) continue;
1327 final float[] pl
= pm
.getP1().getL();
1328 pl
[ 0 ] = ( float )t
.getWidth() / 2.0f
;
1329 pl
[ 1 ] = ( float )t
.getHeight() / 2.0f
;
1330 t
.getModel().applyInPlace( pl
);
1332 toBeDeformedTiles
.add( t
);
1335 for ( final AbstractAffineTile2D
< ?
> t
: toBeDeformedTiles
)
1337 if ( Thread
.currentThread().isInterrupted() ) return;
1341 final Patch patch
= t
.getPatch();
1342 final Rectangle pbox
= patch
.getCoordinateTransformBoundingBox();
1343 final AffineTransform pat
= new AffineTransform();
1344 pat
.translate( -pbox
.x
, -pbox
.y
);
1345 pat
.preConcatenate( patch
.getAffineTransform() );
1347 final mpicbg
.trakem2
.transform
.AffineModel2D toWorld
= new mpicbg
.trakem2
.transform
.AffineModel2D();
1350 final MovingLeastSquaresTransform2 mlst
= Align
.createMLST( matches
, 1.0f
);
1352 final CoordinateTransformList
< CoordinateTransform
> ctl
= new CoordinateTransformList
< CoordinateTransform
>();
1355 ctl
.add( toWorld
.createInverse() );
1357 patch
.appendCoordinateTransform( ctl
);
1359 patch
.getProject().getLoader().regenerateMipMaps( patch
);
1361 catch ( Exception e
)
1363 e
.printStackTrace();
1370 layerRange
.get(0).getParent().setMinimumDimensions();
1371 IJ
.log( "Done: register multi-layer mosaic." );
1377 /** The ParamOptimize object containg all feature extraction and registration model parameters for the "snap" function. */
1378 static public final Align
.ParamOptimize p_snap
= Align
.paramOptimize
.clone();
1380 /** 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.
1381 * If a null @param p_snap is given, it will use the AlignTask.p_snap.
1382 * If @param setup is true, it will show a dialog to adjust parameters. */
1383 static public final Bureaucrat
snap(final Patch patch
, final Align
.ParamOptimize p_snap
, final boolean setup
) {
1384 return Bureaucrat
.createAndStart(new Worker
.Task("Snapping", true) {
1385 public void exec() {
1387 final Align
.ParamOptimize p
= null == p_snap ? AlignTask
.p_snap
: p_snap
;
1388 if (setup
) p
.setup("Snap");
1390 // Collect Patch linked to active
1391 final List
<Displayable
> linked_images
= new ArrayList
<Displayable
>();
1392 for (final Displayable d
: patch
.getLinkedGroup(null)) {
1393 if (d
.getClass() == Patch
.class && d
!= patch
) linked_images
.add(d
);
1395 // Find overlapping images
1396 final List
<Patch
> overlapping
= new ArrayList
<Patch
>( (Collection
<Patch
>) (Collection
) patch
.getLayer().getIntersecting(patch
, Patch
.class));
1397 overlapping
.remove(patch
);
1398 if (0 == overlapping
.size()) return; // nothing overlaps
1400 // Discard from overlapping any linked images
1401 overlapping
.removeAll(linked_images
);
1403 if (0 == overlapping
.size()) {
1404 Utils
.log("Cannot snap: overlapping images are linked to the one to snap.");
1409 linked_images
.clear();
1411 // Find the image that overlaps the most
1412 Rectangle box
= patch
.getBoundingBox(null);
1414 Rectangle most_inter
= null;
1415 for (final Patch other
: overlapping
) {
1418 most_inter
= other
.getBoundingBox();
1421 Rectangle inter
= other
.getBoundingBox().intersection(box
);
1422 if (inter
.width
* inter
.height
> most_inter
.width
* most_inter
.height
) {
1428 overlapping
.clear();
1430 // Define two lists:
1431 // - a list with all involved tiles: the active and the most overlapping one
1432 final List
<Patch
> patches
= new ArrayList
<Patch
>();
1435 // - a list with all tiles except the active, to be set as fixed, immobile
1436 final List
<Patch
> fixedPatches
= new ArrayList
<Patch
>();
1437 fixedPatches
.add(most
);
1440 List
< AbstractAffineTile2D
< ?
> > tiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
1441 List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
1442 Align
.tilesFromPatches( p
, patches
, fixedPatches
, tiles
, fixedTiles
);
1444 // Pair and connect overlapping tiles
1445 final List
< AbstractAffineTile2D
< ?
>[] > tilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
1446 AbstractAffineTile2D
.pairOverlappingTiles( tiles
, tilePairs
);
1447 Align
.connectTilePairs( p
, tiles
, tilePairs
, Runtime
.getRuntime().availableProcessors() );
1449 if ( Thread
.currentThread().isInterrupted() ) return;
1451 Align
.optimizeTileConfiguration( p
, tiles
, fixedTiles
);
1453 for ( AbstractAffineTile2D
< ?
> t
: tiles
) {
1454 if (t
.getPatch() == patch
) {
1455 AffineTransform at
= t
.getModel().createAffine();
1457 at
.concatenate(patch
.getAffineTransform().createInverse());
1458 patch
.transform(at
);
1459 } catch (NoninvertibleTransformException nite
) {
1460 IJError
.print(nite
);
1468 }}, patch
.getProject());
1471 static public final Bureaucrat
registerStackSlices(final Patch slice
) {
1472 return Bureaucrat
.createAndStart(new Worker
.Task("Registering slices", true) {
1473 public void exec() {
1476 ArrayList
<Patch
> slices
= slice
.getStackPatches();
1477 if (slices
.size() < 2) {
1478 Utils
.log2("Not a stack!");
1482 // check that none are linked to anything other than images
1483 for (final Patch patch
: slices
) {
1484 if (!patch
.isOnlyLinkedTo(Patch
.class)) {
1485 Utils
.log("Can't register: one or more slices are linked to objects other than images.");
1491 final Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
1492 p
.setup("Register stack slices");
1494 List
<Patch
> fixedSlices
= new ArrayList
<Patch
>();
1495 fixedSlices
.add(slice
);
1497 alignPatches( p
, slices
, fixedSlices
, false, false, false, false );
1501 }}, slice
.getProject());