ElasticMontage:
[trakem2.git] / mpicbg / trakem2 / align / AlignTask.java
blobcbeae4104cea8871d357aca5953b2c504899ecff
1 /**
2 *
3 */
4 package mpicbg.trakem2.align;
6 import java.awt.Color;
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;
18 import java.util.Map;
19 import java.util.Set;
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;
42 import ij.IJ;
43 import ij.ImagePlus;
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;
63 /**
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) {
84 public void run() {
85 startedWorking();
86 try {
87 alignSelection( selection );
88 Display.repaint(selection.getLayer());
89 } catch (Throwable e) {
90 IJError.print(e);
91 } finally {
92 finishedWorking();
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.");
130 return null;
132 Worker worker = new Worker("Aligning images", false, true) {
133 public void run() {
134 startedWorking();
135 try {
136 alignPatches( patches, fixedPatches );
137 Display.repaint();
138 } catch (Throwable e) {
139 IJError.print(e);
140 } finally {
141 finishedWorking();
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.");
160 return;
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!");
168 return;
172 //final Align.ParamOptimize p = Align.paramOptimize;
174 final GenericDialog gdMode = new GenericDialog( "Montage mode" );
175 gdMode.addChoice( "mode :", modeStrings, modeStrings[ LINEAR ] );
176 gdMode.showDialog();
177 if ( gdMode.wasCanceled() )
178 return;
180 mode = gdMode.getNextChoiceIndex();
182 if ( mode == ELASTIC )
183 new ElasticMontage().exec( patches, fixedPatches );
184 else
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 );
196 gd.showDialog();
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) {
216 public void exec()
218 final GenericDialog gdMode = new GenericDialog( "Montage mode" );
219 gdMode.addChoice( "mode :", modeStrings, modeStrings[ LINEAR ] );
220 gdMode.showDialog();
221 if ( gdMode.wasCanceled() )
222 return;
224 mode = gdMode.getNextChoiceIndex();
226 if ( mode == ELASTIC )
228 final ElasticMontage.Param p = ElasticMontage.setup();
229 if ( p == null )
230 return;
231 else
233 try { montageLayers( p, layers ); }
234 catch ( Exception e ) { e.printStackTrace(); Utils.log( "Exception during montaging layers. Operation failed." ); }
237 else
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 );
249 gd.showDialog();
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 ) {
272 int i = 0;
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);
280 continue;
283 Utils.log("====\nMontaging layer " + layer);
284 Utils.showProgress(((double)i)/layers.size());
285 i++;
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
296 int i = 0;
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 );
310 continue;
313 Utils.log("====\nMontaging layer " + layer);
314 Utils.showProgress(((double)i)/layers.size());
315 i++;
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) {
325 this.ict = ict;
327 public final float[] apply(final float[] p) {
328 float[] q = p.clone();
329 applyInPlace(q);
330 return q;
332 public final float[] applyInverse(final float[] p) {
333 float[] q = p.clone();
334 applyInverseInPlace(q);
335 return q;
337 public final void applyInPlace(final float[] p) {
338 try {
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) {
345 ict.applyInPlace(p);
347 public final InvertibleCoordinateTransform createInverse() {
348 return null;
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 );
356 }});
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);
365 }});
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());
378 try {
379 for (final Patch patch : patches) {
380 tasks.add(exec.submit(new Runnable() {
381 public void run() {
382 Patch.TransformProperties props = patch.getTransformPropertiesCopy();
383 synchronized (tp) {
384 tp.put(patch.getId(), props);
386 // report
387 final int i = counter.incrementAndGet();
388 if (0 == i % 16) {
389 final String msg = new StringBuilder().append(i).append('/').append(patches.size()).toString();
390 Utils.log2(msg);
391 Utils.showStatus(msg);
394 }));
395 // When reaching 2*nproc, wait for nproc to complete
396 if (0 == tasks.size() % (nproc+nproc)) {
397 if (current.isInterrupted()) return tp;
398 int i = 0;
399 while (i < nproc) {
400 try { tasks.removeFirst().get(); } catch (Exception e) { IJError.print(e); }
401 i++;
405 // Wait for remaining tasks
406 Utils.wait(tasks);
407 Utils.log2(patches.size() + "/" + patches.size() + " -- done!");
408 } catch (Throwable t) {
409 IJError.print(t);
410 } finally {
411 exec.shutdownNow();
414 return tp;
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) {
426 this.tp = tp;
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();
453 try {
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
458 // Some checking
459 if (!(tgt_d instanceof VectorData)) {
460 Utils.log("WARNING ignoring provided tgt_vdata " + tgt_d + " which is NOT a VectorData instance!");
461 continue;
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() {
468 public void run() {
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;
501 try {
502 ltasks.add(exec.submit(new Runnable() {
503 public void run() {
504 if (current.isInterrupted()) return;
505 synchronized (patch) {
506 Patch.TransformProperties props;
507 synchronized (tp) {
508 props = tp.get(patch.getId());
510 if (null == props) {
511 props = patch.getTransformPropertiesCopy();
512 // Cache the props
513 synchronized (tp) {
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());
533 }));
534 } catch (Throwable t) {
535 IJError.print(t);
536 return;
541 }));
543 Utils.wait(dtasks);
544 Utils.wait(ltasks);
546 } catch (Throwable t) {
547 IJError.print(t);
548 } finally {
549 exec.shutdownNow();
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!");
559 return;
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;
567 it.remove();
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);
573 // 3 - Align:
574 alignment.run();
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());
591 if (null == la) {
592 Utils.log("ERROR layer with id " + lid + " NOT FOUND in target layerset!");
593 continue;
595 lidm.put(lid, la);
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());
605 if (null == layer) {
606 Utils.log("ERROR layer with id " + el.getKey() + " NOT FOUND in target layerset!");
607 continue;
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);
622 continue;
624 final Patch patch = (Patch)ob;
625 final Patch.TransformProperties props = rd.tp.get(pid); // no need to synch, read only from now on
626 if (null == props) {
627 Utils.log("ERROR: could not find any Patch.TransformProperties for patch " + patch);
628 continue;
630 final Area a = new Area(props.area);
631 a.subtract(used_area);
632 if (M.isEmpty(a)) {
633 continue; // skipping fully occluded Patch
635 // Accumulate:
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();
651 try {
652 aff_inv.set(props.at.createInverse());
653 } catch (NoninvertibleTransformException nite) {
654 Utils.log("ERROR: could not invert the affine transform for Patch " + patch);
655 IJError.print(nite);
656 continue;
658 tlist.add(aff_inv);
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));
670 tlist.add(aff);
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();
677 if (null != ct) {
678 tlist.add(ct);
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));
684 tlist.add(aff);
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());
690 tlist.add(new_aff);
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.
696 // The old part:
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);
700 old.add(mesh);
702 final mpicbg.models.AffineModel2D old_aff = new mpicbg.models.AffineModel2D();
703 old_aff.set(props.at);
704 old.add(old_aff);
706 tlist.add(new InverseICT(old));
708 // The new part:
709 final mpicbg.models.AffineModel2D new_aff = new mpicbg.models.AffineModel2D();
710 new_aff.set(patch.getAffineTransform());
711 tlist.add(new_aff);
712 final mpicbg.trakem2.transform.CoordinateTransform ct = patch.getCoordinateTransform();
713 if (null != ct) tlist.add(ct);
716 vdt.add(a, tlist);
719 // Apply the map of area vs tlist for the data section of d within the layer:
720 try {
721 ((VectorData)d).apply(vdt);
722 } catch (Exception t) {
723 Utils.log("ERROR transformation failed for " + d + " at layer " + layer);
724 IJError.print(t);
727 }}));
730 Utils.wait(fus);
731 Display.repaint();
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() {
748 public void run() {
749 alignTiles( p, tiles, fixedTiles, tilesAreInPlace, largestGraphOnly, hideDisconnectedTiles, deleteDisconnectedTiles );
750 Display.repaint();
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 );
767 else
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 );
799 else
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 )
810 // {
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 ] );
815 // }
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 )
837 public void run()
839 startedWorking();
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 );
877 gd1.showDialog();
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 );
896 gd2.showDialog();
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 );
908 gd3.showDialog();
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(
926 final Align.Param p,
927 final Layer layer1,
928 final Layer layer2,
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(),
964 features1 );
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(),
969 features2 );
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(
978 features1,
979 features2,
980 candidates,
981 cp.rod );
983 final AbstractAffineModel2D< ? > model;
984 switch ( cp.expectedModelIndex )
986 case 0:
987 model = new TranslationModel2D();
988 break;
989 case 1:
990 model = new RigidModel2D();
991 break;
992 case 2:
993 model = new SimilarityModel2D();
994 break;
995 case 3:
996 model = new AffineModel2D();
997 break;
998 default:
999 return false;
1002 boolean again = false;
1007 again = false;
1008 modelFound = model.filterRansac(
1009 candidates,
1010 inliers,
1011 1000,
1012 cp.maxEpsilon,
1013 cp.minInlierRatio,
1014 cp.minNumInliers,
1015 3 );
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 );
1024 inliers.clear();
1025 again = true;
1029 while ( again );
1031 catch ( NotEnoughDataPointsException e )
1033 modelFound = false;
1036 if ( modelFound )
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 );
1050 else
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" );
1054 return modelFound;
1058 public static final void alignMultiLayerMosaicTask(
1059 final List< Layer > layerRange,
1060 final Patch nail,
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 )
1071 /* register */
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 >();
1079 if ( null != nail )
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 )
1135 // {
1136 // Display.getFront().getSelection().clear();
1137 // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
1139 // for ( final Tile< ? > tile : graph )
1140 // {
1141 // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
1142 // Display.repaint();
1143 // }
1144 // Utils.showMessage( "OK" );
1145 // }
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 )
1167 // {
1168 // Display.getFront().getSelection().clear();
1169 // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
1171 // for ( final Tile< ? > tile : graph )
1172 // {
1173 // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
1174 // Display.repaint();
1175 // }
1176 // Utils.showMessage( "OK" );
1177 // }
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 )
1210 // {
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?" );
1224 // }
1226 /* prepare the next loop */
1228 allTiles.addAll( csCurrentLayerTiles );
1229 previousLayerTiles.clear();
1230 previousLayerTiles.addAll( csCurrentLayerTiles );
1232 /* optimize */
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);
1273 else
1274 interestingTiles.addAll( allTiles );
1277 if ( deform )
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 );
1330 matches.add( pm );
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();
1347 toWorld.set( pat );
1349 final MovingLeastSquaresTransform mlst = Align.createMLST( matches, 1.0f );
1351 final CoordinateTransformList< CoordinateTransform > ctl = new CoordinateTransformList< CoordinateTransform >();
1352 ctl.add( toWorld );
1353 ctl.add( mlst );
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." );
1372 return;
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.");
1404 return;
1407 // flush
1408 linked_images.clear();
1410 // Find the image that overlaps the most
1411 Rectangle box = patch.getBoundingBox(null);
1412 Patch most = null;
1413 Rectangle most_inter = null;
1414 for (final Patch other : overlapping) {
1415 if (null == most) {
1416 most = other;
1417 most_inter = other.getBoundingBox();
1418 continue;
1420 Rectangle inter = other.getBoundingBox().intersection(box);
1421 if (inter.width * inter.height > most_inter.width * most_inter.height) {
1422 most = other;
1423 most_inter = inter;
1426 // flush
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>();
1432 patches.add(most);
1433 patches.add(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);
1438 // Patch as Tile
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();
1455 try {
1456 at.concatenate(patch.getAffineTransform().createInverse());
1457 patch.transform(at);
1458 } catch (NoninvertibleTransformException nite) {
1459 IJError.print(nite);
1461 break;
1465 Display.repaint();
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() {
1474 // build the list
1475 ArrayList<Patch> slices = slice.getStackPatches();
1476 if (slices.size() < 2) {
1477 Utils.log2("Not a stack!");
1478 return;
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.");
1485 return;
1489 // ok proceed
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 );
1498 Display.repaint();
1500 }}, slice.getProject());