use MovingLeastSquaresTransform2 for warping in multi-layer-montage
[trakem2.git] / mpicbg / trakem2 / align / AlignTask.java
blob1f733ef18cfb9a59af7a2ede935036633c395c53
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.MovingLeastSquaresTransform2;
38 import mpicbg.trakem2.transform.RigidModel2D;
39 import mpicbg.trakem2.transform.TranslationModel2D;
40 import mpicbg.models.NoninvertibleModelException;
41 import mpicbg.trakem2.transform.InvertibleCoordinateTransform;
43 import ij.IJ;
44 import ij.ImagePlus;
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;
64 /**
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) {
85 public void run() {
86 startedWorking();
87 try {
88 alignSelection( selection );
89 Display.repaint(selection.getLayer());
90 } catch (Throwable e) {
91 IJError.print(e);
92 } finally {
93 finishedWorking();
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.");
131 return null;
133 Worker worker = new Worker("Aligning images", false, true) {
134 public void run() {
135 startedWorking();
136 try {
137 alignPatches( patches, fixedPatches );
138 Display.repaint();
139 } catch (Throwable e) {
140 IJError.print(e);
141 } finally {
142 finishedWorking();
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.");
161 return;
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!");
169 return;
173 //final Align.ParamOptimize p = Align.paramOptimize;
175 final GenericDialog gdMode = new GenericDialog( "Montage mode" );
176 gdMode.addChoice( "mode :", modeStrings, modeStrings[ LINEAR ] );
177 gdMode.showDialog();
178 if ( gdMode.wasCanceled() )
179 return;
181 mode = gdMode.getNextChoiceIndex();
183 if ( mode == ELASTIC )
184 new ElasticMontage().exec( patches, fixedPatches );
185 else
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 );
197 gd.showDialog();
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) {
217 public void exec()
219 final GenericDialog gdMode = new GenericDialog( "Montage mode" );
220 gdMode.addChoice( "mode :", modeStrings, modeStrings[ LINEAR ] );
221 gdMode.showDialog();
222 if ( gdMode.wasCanceled() )
223 return;
225 mode = gdMode.getNextChoiceIndex();
227 if ( mode == ELASTIC )
229 final ElasticMontage.Param p = ElasticMontage.setup();
230 if ( p == null )
231 return;
232 else
234 try { montageLayers( p, layers ); }
235 catch ( Exception e ) { e.printStackTrace(); Utils.log( "Exception during montaging layers. Operation failed." ); }
238 else
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 );
250 gd.showDialog();
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 ) {
273 int i = 0;
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);
281 continue;
284 Utils.log("====\nMontaging layer " + layer);
285 Utils.showProgress(((double)i)/layers.size());
286 i++;
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
297 int i = 0;
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 );
311 continue;
314 Utils.log("====\nMontaging layer " + layer);
315 Utils.showProgress(((double)i)/layers.size());
316 i++;
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) {
326 this.ict = ict;
328 public final float[] apply(final float[] p) {
329 float[] q = p.clone();
330 applyInPlace(q);
331 return q;
333 public final float[] applyInverse(final float[] p) {
334 float[] q = p.clone();
335 applyInverseInPlace(q);
336 return q;
338 public final void applyInPlace(final float[] p) {
339 try {
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) {
346 ict.applyInPlace(p);
348 public final InvertibleCoordinateTransform createInverse() {
349 return null;
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 );
357 }});
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);
366 }});
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());
379 try {
380 for (final Patch patch : patches) {
381 tasks.add(exec.submit(new Runnable() {
382 public void run() {
383 Patch.TransformProperties props = patch.getTransformPropertiesCopy();
384 synchronized (tp) {
385 tp.put(patch.getId(), props);
387 // report
388 final int i = counter.incrementAndGet();
389 if (0 == i % 16) {
390 final String msg = new StringBuilder().append(i).append('/').append(patches.size()).toString();
391 Utils.log2(msg);
392 Utils.showStatus(msg);
395 }));
396 // When reaching 2*nproc, wait for nproc to complete
397 if (0 == tasks.size() % (nproc+nproc)) {
398 if (current.isInterrupted()) return tp;
399 int i = 0;
400 while (i < nproc) {
401 try { tasks.removeFirst().get(); } catch (Exception e) { IJError.print(e); }
402 i++;
406 // Wait for remaining tasks
407 Utils.wait(tasks);
408 Utils.log2(patches.size() + "/" + patches.size() + " -- done!");
409 } catch (Throwable t) {
410 IJError.print(t);
411 } finally {
412 exec.shutdownNow();
415 return tp;
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) {
427 this.tp = tp;
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();
454 try {
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
459 // Some checking
460 if (!(tgt_d instanceof VectorData)) {
461 Utils.log("WARNING ignoring provided tgt_vdata " + tgt_d + " which is NOT a VectorData instance!");
462 continue;
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() {
469 public void run() {
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;
502 try {
503 ltasks.add(exec.submit(new Runnable() {
504 public void run() {
505 if (current.isInterrupted()) return;
506 synchronized (patch) {
507 Patch.TransformProperties props;
508 synchronized (tp) {
509 props = tp.get(patch.getId());
511 if (null == props) {
512 props = patch.getTransformPropertiesCopy();
513 // Cache the props
514 synchronized (tp) {
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());
534 }));
535 } catch (Throwable t) {
536 IJError.print(t);
537 return;
542 }));
544 Utils.wait(dtasks);
545 Utils.wait(ltasks);
547 } catch (Throwable t) {
548 IJError.print(t);
549 } finally {
550 exec.shutdownNow();
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!");
560 return;
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;
568 it.remove();
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);
574 // 3 - Align:
575 alignment.run();
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());
592 if (null == la) {
593 Utils.log("ERROR layer with id " + lid + " NOT FOUND in target layerset!");
594 continue;
596 lidm.put(lid, la);
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());
606 if (null == layer) {
607 Utils.log("ERROR layer with id " + el.getKey() + " NOT FOUND in target layerset!");
608 continue;
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);
623 continue;
625 final Patch patch = (Patch)ob;
626 final Patch.TransformProperties props = rd.tp.get(pid); // no need to synch, read only from now on
627 if (null == props) {
628 Utils.log("ERROR: could not find any Patch.TransformProperties for patch " + patch);
629 continue;
631 final Area a = new Area(props.area);
632 a.subtract(used_area);
633 if (M.isEmpty(a)) {
634 continue; // skipping fully occluded Patch
636 // Accumulate:
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();
652 try {
653 aff_inv.set(props.at.createInverse());
654 } catch (NoninvertibleTransformException nite) {
655 Utils.log("ERROR: could not invert the affine transform for Patch " + patch);
656 IJError.print(nite);
657 continue;
659 tlist.add(aff_inv);
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));
671 tlist.add(aff);
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();
678 if (null != ct) {
679 tlist.add(ct);
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));
685 tlist.add(aff);
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());
691 tlist.add(new_aff);
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.
697 // The old part:
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);
701 old.add(mesh);
703 final mpicbg.models.AffineModel2D old_aff = new mpicbg.models.AffineModel2D();
704 old_aff.set(props.at);
705 old.add(old_aff);
707 tlist.add(new InverseICT(old));
709 // The new part:
710 final mpicbg.models.AffineModel2D new_aff = new mpicbg.models.AffineModel2D();
711 new_aff.set(patch.getAffineTransform());
712 tlist.add(new_aff);
713 final mpicbg.trakem2.transform.CoordinateTransform ct = patch.getCoordinateTransform();
714 if (null != ct) tlist.add(ct);
717 vdt.add(a, tlist);
720 // Apply the map of area vs tlist for the data section of d within the layer:
721 try {
722 ((VectorData)d).apply(vdt);
723 } catch (Exception t) {
724 Utils.log("ERROR transformation failed for " + d + " at layer " + layer);
725 IJError.print(t);
728 }}));
731 Utils.wait(fus);
732 Display.repaint();
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() {
749 public void run() {
750 alignTiles( p, tiles, fixedTiles, tilesAreInPlace, largestGraphOnly, hideDisconnectedTiles, deleteDisconnectedTiles );
751 Display.repaint();
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 );
768 else
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 );
800 else
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 )
811 // {
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 ] );
816 // }
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 )
838 public void run()
840 startedWorking();
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 );
878 gd1.showDialog();
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 );
897 gd2.showDialog();
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 );
909 gd3.showDialog();
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(
927 final Align.Param p,
928 final Layer layer1,
929 final Layer layer2,
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(),
965 features1 );
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(),
970 features2 );
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(
979 features1,
980 features2,
981 candidates,
982 cp.rod );
984 final AbstractAffineModel2D< ? > model;
985 switch ( cp.expectedModelIndex )
987 case 0:
988 model = new TranslationModel2D();
989 break;
990 case 1:
991 model = new RigidModel2D();
992 break;
993 case 2:
994 model = new SimilarityModel2D();
995 break;
996 case 3:
997 model = new AffineModel2D();
998 break;
999 default:
1000 return false;
1003 boolean again = false;
1008 again = false;
1009 modelFound = model.filterRansac(
1010 candidates,
1011 inliers,
1012 1000,
1013 cp.maxEpsilon,
1014 cp.minInlierRatio,
1015 cp.minNumInliers,
1016 3 );
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 );
1025 inliers.clear();
1026 again = true;
1030 while ( again );
1032 catch ( NotEnoughDataPointsException e )
1034 modelFound = false;
1037 if ( modelFound )
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 );
1051 else
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" );
1055 return modelFound;
1059 public static final void alignMultiLayerMosaicTask(
1060 final List< Layer > layerRange,
1061 final Patch nail,
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 )
1072 /* register */
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 >();
1080 if ( null != nail )
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 )
1136 // {
1137 // Display.getFront().getSelection().clear();
1138 // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
1140 // for ( final Tile< ? > tile : graph )
1141 // {
1142 // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
1143 // Display.repaint();
1144 // }
1145 // Utils.showMessage( "OK" );
1146 // }
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 )
1168 // {
1169 // Display.getFront().getSelection().clear();
1170 // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
1172 // for ( final Tile< ? > tile : graph )
1173 // {
1174 // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
1175 // Display.repaint();
1176 // }
1177 // Utils.showMessage( "OK" );
1178 // }
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 )
1211 // {
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?" );
1225 // }
1227 /* prepare the next loop */
1229 allTiles.addAll( csCurrentLayerTiles );
1230 previousLayerTiles.clear();
1231 previousLayerTiles.addAll( csCurrentLayerTiles );
1233 /* optimize */
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);
1274 else
1275 interestingTiles.addAll( allTiles );
1278 if ( deform )
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 );
1331 matches.add( pm );
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();
1348 toWorld.set( pat );
1350 final MovingLeastSquaresTransform2 mlst = Align.createMLST( matches, 1.0f );
1352 final CoordinateTransformList< CoordinateTransform > ctl = new CoordinateTransformList< CoordinateTransform >();
1353 ctl.add( toWorld );
1354 ctl.add( mlst );
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." );
1373 return;
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.");
1405 return;
1408 // flush
1409 linked_images.clear();
1411 // Find the image that overlaps the most
1412 Rectangle box = patch.getBoundingBox(null);
1413 Patch most = null;
1414 Rectangle most_inter = null;
1415 for (final Patch other : overlapping) {
1416 if (null == most) {
1417 most = other;
1418 most_inter = other.getBoundingBox();
1419 continue;
1421 Rectangle inter = other.getBoundingBox().intersection(box);
1422 if (inter.width * inter.height > most_inter.width * most_inter.height) {
1423 most = other;
1424 most_inter = inter;
1427 // flush
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>();
1433 patches.add(most);
1434 patches.add(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);
1439 // Patch as Tile
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();
1456 try {
1457 at.concatenate(patch.getAffineTransform().createInverse());
1458 patch.transform(at);
1459 } catch (NoninvertibleTransformException nite) {
1460 IJError.print(nite);
1462 break;
1466 Display.repaint();
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() {
1475 // build the list
1476 ArrayList<Patch> slices = slice.getStackPatches();
1477 if (slices.size() < 2) {
1478 Utils.log2("Not a stack!");
1479 return;
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.");
1486 return;
1490 // ok proceed
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 );
1499 Display.repaint();
1501 }}, slice.getProject());