4 package mpicbg
.trakem2
.align
;
7 import java
.awt
.Rectangle
;
8 import java
.awt
.geom
.AffineTransform
;
9 import java
.io
.Serializable
;
10 import java
.util
.ArrayList
;
11 import java
.util
.Collection
;
12 import java
.util
.List
;
14 import java
.util
.concurrent
.atomic
.AtomicInteger
;
18 import ij
.gui
.GenericDialog
;
20 import ini
.trakem2
.display
.Display
;
21 import ini
.trakem2
.display
.Displayable
;
22 import ini
.trakem2
.display
.Layer
;
23 import ini
.trakem2
.display
.Patch
;
24 import ini
.trakem2
.display
.Selection
;
25 import ini
.trakem2
.persistence
.Loader
;
26 import ini
.trakem2
.persistence
.FSLoader
;
27 import ini
.trakem2
.utils
.Utils
;
29 import mpicbg
.ij
.FeatureTransform
;
30 import mpicbg
.ij
.SIFT
;
31 import mpicbg
.imagefeatures
.Feature
;
32 import mpicbg
.imagefeatures
.FloatArray2DSIFT
;
33 import mpicbg
.models
.AbstractAffineModel2D
;
34 import mpicbg
.models
.AffineModel2D
;
35 import mpicbg
.models
.NotEnoughDataPointsException
;
36 import mpicbg
.models
.PointMatch
;
37 import mpicbg
.models
.SimilarityModel2D
;
38 import mpicbg
.models
.Tile
;
39 import mpicbg
.trakem2
.transform
.MovingLeastSquaresTransform
;
40 import mpicbg
.trakem2
.transform
.RigidModel2D
;
41 import mpicbg
.trakem2
.transform
.TranslationModel2D
;
44 * A collection of methods regarding SIFT-based alignment
46 * TODO Bring the methods and tasks into a class for each method and clean up this mess.
48 * @author Stephan Saalfeld <saalfeld@mpi-cbg.de>
52 static public class Param
implements Serializable
54 private static final long serialVersionUID
= -2247163691721712461L;
56 final public FloatArray2DSIFT
.Param sift
= new FloatArray2DSIFT
.Param();
59 * Closest/next closest neighbour distance ratio
61 public float rod
= 0.92f
;
64 * Maximal allowed alignment error in px
66 public float maxEpsilon
= 100.0f
;
69 * Inlier/candidates ratio
71 public float minInlierRatio
= 0.2f
;
74 * Implemeted transformation models for choice
76 final static public String
[] modelStrings
= new String
[]{ "Translation", "Rigid", "Similarity", "Affine" };
77 public int expectedModelIndex
= 1;
78 public int desiredModelIndex
= 1;
80 public float correspondenceWeight
= 1;
84 sift
.maxOctaveSize
= 600;
88 public void addFields( final GenericDialog gd
)
90 SIFT
.addFields( gd
, sift
);
92 gd
.addNumericField( "closest/next_closest_ratio :", rod
, 2 );
94 gd
.addMessage( "Geometric Consensus Filter:" );
95 gd
.addNumericField( "maximal_alignment_error :", maxEpsilon
, 2, 6, "px" );
96 gd
.addNumericField( "inlier_ratio :", minInlierRatio
, 2 );
97 gd
.addChoice( "expected_transformation :", modelStrings
, modelStrings
[ expectedModelIndex
] );
99 gd
.addMessage( "Alignment:" );
100 gd
.addChoice( "desired_transformation :", modelStrings
, modelStrings
[ desiredModelIndex
] );
101 gd
.addNumericField( "correspondence weight :", correspondenceWeight
, 2 );
104 public boolean readFields( final GenericDialog gd
)
106 SIFT
.readFields( gd
, sift
);
108 rod
= ( float )gd
.getNextNumber();
110 maxEpsilon
= ( float )gd
.getNextNumber();
111 minInlierRatio
= ( float )gd
.getNextNumber();
112 expectedModelIndex
= gd
.getNextChoiceIndex();
113 desiredModelIndex
= gd
.getNextChoiceIndex();
115 correspondenceWeight
= ( float )gd
.getNextNumber();
117 return !gd
.invalidNumber();
120 public boolean setup( final String title
)
122 final GenericDialog gd
= new GenericDialog( title
);
129 if ( gd
.wasCanceled() ) return false;
131 while ( !readFields( gd
) );
138 Param p
= new Param();
140 p
.sift
.initialSigma
= this.sift
.initialSigma
;
141 p
.sift
.steps
= this.sift
.steps
;
142 p
.sift
.minOctaveSize
= this.sift
.minOctaveSize
;
143 p
.sift
.maxOctaveSize
= this.sift
.maxOctaveSize
;
144 p
.sift
.fdSize
= this.sift
.fdSize
;
145 p
.sift
.fdBins
= this.sift
.fdBins
;
148 p
.maxEpsilon
= maxEpsilon
;
149 p
.minInlierRatio
= minInlierRatio
;
150 p
.expectedModelIndex
= expectedModelIndex
;
151 p
.desiredModelIndex
= desiredModelIndex
;
153 p
.correspondenceWeight
= correspondenceWeight
;
159 * Check if two parameter sets are equal. So far, this method ignores
160 * the parameter {@link #desiredModelIndex} which defines the
161 * transformation class to be used for {@link Tile} alignment. This
162 * makes sense for the current use in {@link PointMatch} serialization
163 * but might be misleading for other applications.
165 * TODO Think about this.
170 public boolean equals( Param p
)
173 sift
.equals( p
.sift
) &&
175 ( maxEpsilon
== p
.maxEpsilon
) &&
176 ( minInlierRatio
== p
.minInlierRatio
) &&
177 ( expectedModelIndex
== p
.expectedModelIndex
);
178 // && ( desiredModelIndex == p.desiredModelIndex );
182 final static public Param param
= new Param();
184 static public class ParamOptimize
extends Param
186 private static final long serialVersionUID
= 970673723211054580L;
189 * Maximal number of iteration allowed for the optimizer.
191 public int maxIterations
= 2000;
194 * Maximal number of iterations allowed to not change the parameter to
197 public int maxPlateauwidth
= 200;
200 public void addFields( final GenericDialog gd
)
202 super.addFields( gd
);
204 gd
.addNumericField( "maximal_iterations :", maxIterations
, 0 );
205 gd
.addNumericField( "maximal_plateauwidth :", maxPlateauwidth
, 0 );
209 public boolean readFields( final GenericDialog gd
)
211 super.readFields( gd
);
213 maxIterations
= ( int )gd
.getNextNumber();
214 maxPlateauwidth
= ( int )gd
.getNextNumber();
216 return !gd
.invalidNumber();
220 final public boolean setup( final String title
)
222 final GenericDialog gd
= new GenericDialog( title
);
229 if ( gd
.wasCanceled() ) return false;
231 while ( !readFields( gd
) );
237 final public ParamOptimize
clone()
239 ParamOptimize p
= new ParamOptimize();
241 p
.sift
.initialSigma
= this.sift
.initialSigma
;
242 p
.sift
.steps
= this.sift
.steps
;
243 p
.sift
.minOctaveSize
= this.sift
.minOctaveSize
;
244 p
.sift
.maxOctaveSize
= this.sift
.maxOctaveSize
;
245 p
.sift
.fdSize
= this.sift
.fdSize
;
246 p
.sift
.fdBins
= this.sift
.fdBins
;
249 p
.maxEpsilon
= maxEpsilon
;
250 p
.minInlierRatio
= minInlierRatio
;
251 p
.expectedModelIndex
= expectedModelIndex
;
253 p
.desiredModelIndex
= desiredModelIndex
;
254 p
.maxIterations
= maxIterations
;
255 p
.maxPlateauwidth
= maxPlateauwidth
;
260 public boolean equals( ParamOptimize p
)
264 ( maxIterations
== p
.maxIterations
) &&
265 ( maxPlateauwidth
== p
.maxPlateauwidth
);
269 final static public ParamOptimize paramOptimize
= new ParamOptimize();
271 final static private class Features
implements Serializable
273 private static final long serialVersionUID
= 2689219384710526198L;
275 FloatArray2DSIFT
.Param p
;
276 ArrayList
< Feature
> features
;
277 Features( final FloatArray2DSIFT
.Param p
, final ArrayList
< Feature
> features
)
280 this.features
= features
;
284 final static private class PointMatches
implements Serializable
286 private static final long serialVersionUID
= -2564147268101223484L;
289 ArrayList
< PointMatch
> pointMatches
;
290 PointMatches( final Param p
, final ArrayList
< PointMatch
> pointMatches
)
293 this.pointMatches
= pointMatches
;
298 * Extracts {@link Feature SIFT-features} from a {@link List} of
299 * {@link AbstractAffineTile2D Tiles} and saves them to disk.
301 final static protected class ExtractFeaturesThread
extends Thread
303 final protected Param p
;
304 final protected List
< AbstractAffineTile2D
< ?
> > tiles
;
305 // final protected HashMap< AbstractAffineTile2D< ? >, Collection< Feature > > tileFeatures;
306 final protected AtomicInteger ai
;
307 final protected AtomicInteger ap
;
308 final protected int steps
;
310 public ExtractFeaturesThread(
312 final List
< AbstractAffineTile2D
< ?
> > tiles
,
313 // final HashMap< AbstractAffineTile2D< ? >, Collection< Feature > > tileFeatures,
314 final AtomicInteger ai
,
315 final AtomicInteger ap
,
326 final public void run()
328 FloatArray2DSIFT sift
= new FloatArray2DSIFT( p
.sift
);
329 SIFT ijSIFT
= new SIFT( sift
);
331 for ( int i
= ai
.getAndIncrement(); i
< tiles
.size() && !isInterrupted(); i
= ai
.getAndIncrement() )
333 if (isInterrupted()) return;
334 AbstractAffineTile2D
< ?
> tile
= tiles
.get( i
);
335 Collection
< Feature
> features
= deserializeFeatures( p
, tile
);
336 if ( features
== null )
338 /* extract features and, in case there is not enough memory available, try to free it and do again */
339 boolean memoryFlushed
;
344 features
= new ArrayList
< Feature
>();
345 long s
= System
.currentTimeMillis();
346 ijSIFT
.extractFeatures( tile
.createMaskedByteImage(), features
);
347 IJ
.log( features
.size() + " features extracted in tile " + i
+ " \"" + tile
.getPatch().getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
348 if ( !serializeFeatures( p
, tile
, features
) )
349 IJ
.log( "Saving features failed for tile \"" + tile
.getPatch() + "\"" );
350 memoryFlushed
= false;
352 catch ( OutOfMemoryError e
)
354 Utils
.log2( "Flushing memory for feature extraction" );
355 Loader
.releaseAllCaches();
356 memoryFlushed
= true;
359 while ( memoryFlushed
);
363 IJ
.log( features
.size() + " features loaded for tile " + i
+ " \"" + tile
.getPatch().getTitle() + "\"." );
365 IJ
.showProgress( ap
.getAndIncrement(), steps
);
371 final static protected class MatchFeaturesAndFindModelThread
extends Thread
373 final protected Param p
;
374 final protected List
< AbstractAffineTile2D
< ?
> > tiles
;
375 //final protected HashMap< AbstractAffineTile2D< ? >, Collection< Feature > > tileFeatures;
376 final protected List
< AbstractAffineTile2D
< ?
>[] > tilePairs
;
377 final protected AtomicInteger ai
;
378 final protected AtomicInteger ap
;
379 final protected int steps
;
381 public MatchFeaturesAndFindModelThread(
383 final List
< AbstractAffineTile2D
< ?
> > tiles
,
384 final List
< AbstractAffineTile2D
< ?
>[] > tilePairs
,
385 final AtomicInteger ai
,
386 final AtomicInteger ap
,
391 this.tilePairs
= tilePairs
;
398 final public void run()
400 final List
< PointMatch
> candidates
= new ArrayList
< PointMatch
>();
402 for ( int i
= ai
.getAndIncrement(); i
< tilePairs
.size() && !isInterrupted(); i
= ai
.getAndIncrement() )
404 if (isInterrupted()) return;
406 final AbstractAffineTile2D
< ?
>[] tilePair
= tilePairs
.get( i
);
408 Collection
< PointMatch
> inliers
= deserializePointMatches( p
, tilePair
[ 0 ], tilePair
[ 1 ] );
410 if ( inliers
== null )
412 inliers
= new ArrayList
< PointMatch
>();
414 long s
= System
.currentTimeMillis();
416 FeatureTransform
.matchFeatures(
417 fetchFeatures( p
, tilePair
[ 0 ] ),
418 fetchFeatures( p
, tilePair
[ 1 ] ),
423 final AbstractAffineModel2D
< ?
> model
;
424 switch ( p
.expectedModelIndex
)
427 model
= new TranslationModel2D();
430 model
= new RigidModel2D();
433 model
= new SimilarityModel2D();
436 model
= new AffineModel2D();
445 modelFound
= model
.filterRansac(
451 Math
.max( 7, 3 * model
.getMinNumMatches() ),
454 catch ( NotEnoughDataPointsException e
)
459 IJ
.log( "Model found for tiles \"" + tilePair
[ 0 ].getPatch() + "\" and \"" + tilePair
[ 1 ].getPatch() + "\":\n correspondences " + inliers
.size() + " of " + candidates
.size() + "\n average residual error " + model
.getCost() + " px\n took " + ( System
.currentTimeMillis() - s
) + " ms" );
461 IJ
.log( "No model found for tiles \"" + tilePair
[ 0 ].getPatch() + "\" and \"" + tilePair
[ 1 ].getPatch() + "\":\n correspondence candidates " + candidates
.size() + "\n took " + ( System
.currentTimeMillis() - s
) + " ms" );
463 if ( !serializePointMatches( p
, tilePair
[ 0 ], tilePair
[ 1 ], inliers
) )
464 IJ
.log( "Saving point matches failed for tiles \"" + tilePair
[ 0 ].getPatch() + "\" and \"" + tilePair
[ 1 ].getPatch() + "\"" );
468 IJ
.log( "Point matches for tiles \"" + tilePair
[ 0 ].getPatch().getTitle() + "\" and \"" + tilePair
[ 1 ].getPatch().getTitle() + "\" fetched from disk cache" );
470 if ( inliers
!= null && inliers
.size() > 0 )
472 /* weight the inliers */
473 for ( final PointMatch pm
: inliers
)
474 pm
.setWeights( new float[]{ p
.correspondenceWeight
} );
476 synchronized ( tilePair
[ 0 ] )
478 synchronized ( tilePair
[ 1 ] ) { tilePair
[ 0 ].connect( tilePair
[ 1 ], inliers
); }
479 tilePair
[ 0 ].clearVirtualMatches();
481 synchronized ( tilePair
[ 1 ] ) { tilePair
[ 1 ].clearVirtualMatches(); }
484 IJ
.showProgress( ap
.getAndIncrement(), steps
);
489 final static protected boolean serializeFeatures( final Param p
, AbstractAffineTile2D
< ?
> t
, final Collection
< Feature
> f
)
491 final ArrayList
< Feature
> list
= new ArrayList
< Feature
>();
493 final Patch patch
= t
.getPatch();
494 final Loader loader
= patch
.getProject().getLoader();
495 final Features fe
= new Features( p
.sift
, list
);
496 return loader
.serialize( fe
, new StringBuilder( loader
.getUNUIdFolder() ).append( "features.ser/" )
497 .append( FSLoader
.createIdPath( Long
.toString( patch
.getId() ), "features", ".ser" ) ).toString() );
501 * Retrieve the features only if saved with the exact same relevant SIFT parameters.
503 final static protected Collection
< Feature
> deserializeFeatures( final Param p
, final AbstractAffineTile2D
< ?
> t
)
505 final Patch patch
= t
.getPatch();
506 final Loader loader
= patch
.getProject().getLoader();
508 final Object ob
= loader
.deserialize( new StringBuilder( loader
.getUNUIdFolder() ).append( "features.ser/" )
509 .append( FSLoader
.createIdPath( Long
.toString( patch
.getId() ), "features", ".ser" ) ).toString() );
514 final Features fe
= ( Features
)ob
;
515 if ( p
.sift
.equals( fe
.p
) && null != fe
.p
)
520 catch ( Exception e
)
529 final static protected Collection
< Feature
> fetchFeatures(
531 final AbstractAffineTile2D
< ?
> t
)
533 Collection
< Feature
> features
= deserializeFeatures( p
, t
);
534 if ( features
== null )
536 final FloatArray2DSIFT sift
= new FloatArray2DSIFT( p
.sift
);
537 final SIFT ijSIFT
= new SIFT( sift
);
538 features
= new ArrayList
< Feature
>();
539 long s
= System
.currentTimeMillis();
540 ijSIFT
.extractFeatures( t
.createMaskedByteImage(), features
);
541 IJ
.log( features
.size() + " features extracted in tile \"" + t
.getPatch().getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
542 if ( !serializeFeatures( p
, t
, features
) )
543 IJ
.log( "Saving features failed for tile: " + t
.getPatch() );
550 * Save a {@link Collection} of {@link PointMatch PointMatches} two-sided.
551 * Creates two serialization files which is desperately required to clean
552 * up properly invalid serializations on change of a {@link Patch}.
560 final static protected boolean serializePointMatches(
562 final AbstractAffineTile2D
< ?
> t1
,
563 final AbstractAffineTile2D
< ?
> t2
,
564 final Collection
< PointMatch
> m
)
566 final ArrayList
< PointMatch
> list
= new ArrayList
< PointMatch
>();
568 final ArrayList
< PointMatch
> tsil
= new ArrayList
< PointMatch
>();
569 PointMatch
.flip( m
, tsil
);
570 final Patch p1
= t1
.getPatch();
571 final Patch p2
= t2
.getPatch();
572 final Loader loader
= p1
.getProject().getLoader();
575 new PointMatches( p
, list
),
576 new StringBuilder( loader
.getUNUIdFolder() ).append( "pointmatches.ser/" ).append( FSLoader
.createIdPath( Long
.toString( p1
.getId() ) + "_" + Long
.toString( p2
.getId() ), "pointmatches", ".ser" ) ).toString() ) &&
578 new PointMatches( p
, tsil
),
579 new StringBuilder( loader
.getUNUIdFolder() ).append( "pointmatches.ser/" ).append( FSLoader
.createIdPath( Long
.toString( p2
.getId() ) + "_" + Long
.toString( p1
.getId() ), "pointmatches", ".ser" ) ).toString() );
583 final static protected Collection
< PointMatch
> deserializePointMatches(
585 final AbstractAffineTile2D
< ?
> t1
,
586 final AbstractAffineTile2D
< ?
> t2
)
588 final Patch p1
= t1
.getPatch();
589 final Patch p2
= t2
.getPatch();
590 final Loader loader
= p1
.getProject().getLoader();
592 final Object ob
= loader
.deserialize( new StringBuilder( loader
.getUNUIdFolder() ).append( "pointmatches.ser/" )
593 .append( FSLoader
.createIdPath( Long
.toString( p1
.getId() ) + "_" + Long
.toString( p2
.getId() ), "pointmatches", ".ser" ) ).toString() );
599 final PointMatches pm
= ( PointMatches
)ob
;
600 if ( p
.equals( pm
.p
) && null != pm
.p
)
602 return pm
.pointMatches
;
605 catch ( Exception e
)
615 * Fetch a {@link Collection} of corresponding
616 * {@link Feature SIFT-features}. Both {@link Feature SIFT-features} and
617 * {@linkplain PointMatch corresponding points} are cached to disk.
624 * <dt>null</dt><dd>if matching failed for some reasons</dd>
625 * <dt>empty {@link Collection}</dt><dd>if there was no consistent set
626 * of {@link PointMatch matches}</dd>
627 * <dt>{@link Collection} of {@link PointMatch PointMatches}</dt>
628 * <dd>if there was a consistent set of {@link PointMatch
632 final static protected Collection
< PointMatch
> fetchPointMatches(
634 final AbstractAffineTile2D
< ?
> t1
,
635 final AbstractAffineTile2D
< ?
> t2
)
637 Collection
< PointMatch
> pointMatches
= deserializePointMatches( p
, t1
, t2
);
638 if ( pointMatches
== null )
640 final List
< PointMatch
> candidates
= new ArrayList
< PointMatch
>();
641 final List
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
643 long s
= System
.currentTimeMillis();
644 FeatureTransform
.matchFeatures(
645 fetchFeatures( p
, t1
),
646 fetchFeatures( p
, t2
),
650 final AbstractAffineModel2D
< ?
> model
;
651 switch ( p
.expectedModelIndex
)
654 model
= new TranslationModel2D();
657 model
= new RigidModel2D();
660 model
= new SimilarityModel2D();
663 model
= new AffineModel2D();
672 modelFound
= model
.filterRansac(
678 3 * model
.getMinNumMatches(),
681 catch ( NotEnoughDataPointsException e
)
688 IJ
.log( "Model found for tiles \"" + t1
.getPatch() + "\" and \"" + t2
.getPatch().getTitle() + "\":\n correspondences " + inliers
.size() + " of " + candidates
.size() + "\n average residual error " + model
.getCost() + " px\n took " + ( System
.currentTimeMillis() - s
) + " ms" );
691 IJ
.log( "No model found for tiles " + t1
+ " and " + t2
+ "." );
693 if ( !serializePointMatches( p
, t1
, t2
, pointMatches
) )
694 IJ
.log( "Saving point matches failed for tile \"" + t1
.getPatch() + "\" and tile \"" + t2
.getPatch() + "\"" );
701 * Align a set of overlapping {@link AbstractAffineTile2D tiles} using
702 * the following procedure:
705 * <li>Extract {@link Feature SIFT-features} from all
706 * {@link AbstractAffineTile2D tiles}.</li>
707 * <li>Establish {@link PointMatch point-correspondences} from
708 * consistent sets of {@link Feature feature} matches among overlapping
710 * <li>Globally align the tile configuration.</li>
714 * {@link SIFT#extractFeatures(ij.process.ImageProcessor, Collection) feature extraction}
715 * and {@link FeatureTransform#matchFeatures(Collection, Collection, List, float) matching}
716 * are executed in multiple {@link Thread Threads}, with the number of
717 * {@link Thread Threads} being a parameter of the method.
723 final static public void alignTiles(
724 final ParamOptimize p
,
725 final List
< AbstractAffineTile2D
< ?
> > tiles
,
726 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
,
727 final int numThreads
)
729 final ArrayList
< AbstractAffineTile2D
< ?
>[] > tilePairs
= new ArrayList
< AbstractAffineTile2D
<?
>[] >();
730 AbstractAffineTile2D
.pairOverlappingTiles( tiles
, tilePairs
);
731 connectTilePairs( p
, tiles
, tilePairs
, numThreads
);
732 optimizeTileConfiguration( p
, tiles
, fixedTiles
);
736 * Align a set of {@link AbstractAffineTile2D tiles} that are
737 * interconnected by {@link PointMatch point-correspondences}.
739 final static public void optimizeTileConfiguration(
740 final ParamOptimize p
,
741 final List
< AbstractAffineTile2D
< ?
> > tiles
,
742 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
)
744 final TileConfiguration tc
= new TileConfiguration();
745 tc
.addTiles( tiles
);
747 ArrayList
< Set
< Tile
< ?
> > > graphs
= Tile
.identifyConnectedGraphs( tiles
);
748 for ( Set
< Tile
< ?
> > graph
: graphs
)
750 boolean pleaseFix
= true;
751 if ( fixedTiles
!= null )
752 for ( final Tile
< ?
> t
: fixedTiles
)
753 if ( graph
.contains( t
) )
758 tc
.fixTile( graph
.iterator().next() );
760 for ( final Tile
< ?
> t
: fixedTiles
)
765 tc
.optimize( p
.maxEpsilon
, p
.maxIterations
, p
.maxPlateauwidth
);
767 catch ( Exception e
) { IJ
.error( e
.getMessage() + " " + e
.getStackTrace() ); }
771 final static protected void pairwiseAlign(
772 AbstractAffineTile2D
< ?
> tile
,
773 Set
< AbstractAffineTile2D
< ?
> > visited
)
776 for ( Tile
< ?
> t
: tile
.getConnectedTiles() )
778 if ( visited
.contains( t
) ) continue;
779 pairwiseAlign( ( AbstractAffineTile2D
< ?
> )t
, visited
);
780 // TODO Actually do it ...
785 final static public void pairwiseAlignTileConfiguration(
786 final List
< AbstractAffineTile2D
< ?
> > tiles
)
793 * Connect a {@link List} of {@link AbstractAffineTile2D Tiles} by
794 * geometrically consistent {@link Feature SIFT-feature} correspondences.
800 final static public void connectTilePairs(
802 final List
< AbstractAffineTile2D
< ?
> > tiles
,
803 final List
< AbstractAffineTile2D
< ?
>[] > tilePairs
,
804 final int numThreads
)
806 final AtomicInteger ai
= new AtomicInteger( 0 );
807 final AtomicInteger ap
= new AtomicInteger( 0 );
808 final int steps
= tiles
.size() + tilePairs
.size();
809 final List
< ExtractFeaturesThread
> extractFeaturesThreads
= new ArrayList
< ExtractFeaturesThread
>();
810 final List
< MatchFeaturesAndFindModelThread
> matchFeaturesAndFindModelThreads
= new ArrayList
< MatchFeaturesAndFindModelThread
>();
812 /** Extract and save Features */
813 for ( int i
= 0; i
< numThreads
; ++i
)
815 final ExtractFeaturesThread thread
= new ExtractFeaturesThread( p
.clone(), tiles
, ai
, ap
, steps
);
816 extractFeaturesThreads
.add( thread
);
821 for ( final ExtractFeaturesThread thread
: extractFeaturesThreads
)
824 catch ( InterruptedException e
)
826 IJ
.log( "Feature extraction failed.\n" + e
.getMessage() + "\n" + e
.getStackTrace() );
830 /** Establish correspondences */
832 for ( int i
= 0; i
< numThreads
; ++i
)
834 MatchFeaturesAndFindModelThread thread
= new MatchFeaturesAndFindModelThread( p
.clone(), tiles
, tilePairs
, ai
, ap
, steps
);
835 matchFeaturesAndFindModelThreads
.add( thread
);
840 for ( final MatchFeaturesAndFindModelThread thread
: matchFeaturesAndFindModelThreads
)
843 catch ( InterruptedException e
)
845 IJ
.log( "Establishing feature correspondences failed.\n" + e
.getMessage() + "\n" + e
.getStackTrace() );
855 * @param fixedPatches
856 * @param tiles will contain the generated
857 * {@link AbstractAffineTile2D Tiles}
858 * @param fixedTiles will contain the {@link AbstractAffineTile2D Tiles}
859 * corresponding to the {@link Patch Patches} in fixedPatches
861 final static public void tilesFromPatches(
863 final List
< ?
extends Patch
> patches
,
864 final List
< ?
extends Patch
> fixedPatches
,
865 final List
< AbstractAffineTile2D
< ?
> > tiles
,
866 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
)
868 for ( final Patch patch
: patches
)
870 final AbstractAffineTile2D
< ?
> t
;
871 switch ( p
.desiredModelIndex
)
874 t
= new TranslationTile2D( patch
);
877 t
= new RigidTile2D( patch
);
880 t
= new SimilarityTile2D( patch
);
883 t
= new AffineTile2D( patch
);
889 if ( ( fixedPatches
!= null && fixedPatches
.contains( patch
) ) || patch
.isLocked() )
896 * Align a selection of {@link Patch patches} in a Layer.
900 final static public void alignSelectedPatches( Selection selection
, final int numThreads
)
902 final List
< Patch
> patches
= new ArrayList
< Patch
>();
903 for ( final Displayable d
: selection
.getSelected() )
904 if ( d
instanceof Patch
) patches
.add( ( Patch
)d
);
906 if ( patches
.size() < 2 ) return;
908 if ( !paramOptimize
.setup( "Align selected patches" ) ) return;
910 List
< AbstractAffineTile2D
< ?
> > tiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
911 List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
912 List
< Patch
> fixedPatches
= new ArrayList
< Patch
>();
913 final Displayable active
= selection
.getActive();
914 if ( active
!= null && active
instanceof Patch
)
915 fixedPatches
.add( ( Patch
)active
);
916 tilesFromPatches( paramOptimize
, patches
, fixedPatches
, tiles
, fixedTiles
);
918 alignTiles( paramOptimize
, tiles
, fixedTiles
, numThreads
);
920 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
921 t
.getPatch().setAffineTransform( t
.getModel().createAffine() );
926 * Align all {@link Patch patches} in a Layer.
930 final static public void alignLayer( final Layer layer
, final int numThreads
)
932 if ( !paramOptimize
.setup( "Align patches in layer" ) ) return;
934 List
< Displayable
> displayables
= layer
.getDisplayables( Patch
.class );
935 List
< Patch
> patches
= new ArrayList
< Patch
>();
936 for ( Displayable d
: displayables
)
937 patches
.add( ( Patch
)d
);
938 List
< AbstractAffineTile2D
< ?
> > tiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
939 List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
940 tilesFromPatches( paramOptimize
, patches
, null, tiles
, fixedTiles
);
942 alignTiles( paramOptimize
, tiles
, fixedTiles
, numThreads
);
944 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
945 t
.getPatch().setAffineTransform( t
.getModel().createAffine() );
949 final static public void alignLayersLinearly( final List
< Layer
> layers
, final int numThreads
)
951 param
.sift
.maxOctaveSize
= 1600;
953 if ( !param
.setup( "Align layers linearly" ) ) return;
955 final Rectangle box
= layers
.get( 0 ).getParent().getMinimalBoundingBox( Patch
.class );
956 final float scale
= Math
.min( 1.0f
, Math
.min( ( float )param
.sift
.maxOctaveSize
/ ( float )box
.width
, ( float )param
.sift
.maxOctaveSize
/ ( float )box
.height
) );
957 final Param p
= param
.clone();
958 p
.maxEpsilon
*= scale
;
960 final FloatArray2DSIFT sift
= new FloatArray2DSIFT( p
.sift
);
961 final SIFT ijSIFT
= new SIFT( sift
);
963 Rectangle box1
= null;
964 Rectangle box2
= null;
965 final Collection
< Feature
> features1
= new ArrayList
< Feature
>();
966 final Collection
< Feature
> features2
= new ArrayList
< Feature
>();
967 List
< PointMatch
> candidates
= new ArrayList
< PointMatch
>();
968 List
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
970 AffineTransform a
= new AffineTransform();
973 for ( Layer l
: layers
)
975 long s
= System
.currentTimeMillis();
978 features1
.addAll( features2
);
981 final Rectangle box3
= l
.getMinimalBoundingBox( Patch
.class );
983 if ( box3
== null ) continue;
988 ijSIFT
.extractFeatures(
989 l
.getProject().getLoader().getFlatImage( l
, box2
, scale
, 0xffffffff, ImagePlus
.GRAY8
, Patch
.class, true ).getProcessor(),
991 IJ
.log( features2
.size() + " features extracted in layer \"" + l
.getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
993 if ( features1
.size() > 0 )
995 s
= System
.currentTimeMillis();
999 FeatureTransform
.matchFeatures(
1005 final AbstractAffineModel2D
< ?
> model
;
1006 switch ( p
.expectedModelIndex
)
1009 model
= new TranslationModel2D();
1012 model
= new RigidModel2D();
1015 model
= new SimilarityModel2D();
1018 model
= new AffineModel2D();
1027 modelFound
= model
.filterRansac(
1033 3 * model
.getMinNumMatches(),
1036 catch ( NotEnoughDataPointsException e
)
1043 IJ
.log( "Model found for layer \"" + l
.getTitle() + "\" and its predecessor:\n correspondences " + inliers
.size() + " of " + candidates
.size() + "\n average residual error " + ( model
.getCost() / scale
) + " px\n took " + ( System
.currentTimeMillis() - s
) + " ms" );
1044 final AffineTransform b
= new AffineTransform();
1045 b
.translate( box1
.x
, box1
.y
);
1046 b
.scale( 1.0f
/ scale
, 1.0f
/ scale
);
1047 b
.concatenate( model
.createAffine() );
1048 b
.scale( scale
, scale
);
1049 b
.translate( -box2
.x
, -box2
.y
);
1052 l
.apply( Displayable
.class, a
);
1053 Display
.repaint( l
);
1056 IJ
.log( "No model found for layer \"" + l
.getTitle() + "\" and its predecessor." );
1058 IJ
.showProgress( ++i
, layers
.size() );
1063 * Temporary helper method that creates
1069 final static public MovingLeastSquaresTransform
createMLST( final Collection
< PointMatch
> matches
, final float alpha
) throws Exception
1071 final MovingLeastSquaresTransform mlst
= new MovingLeastSquaresTransform();
1072 mlst
.setAlpha( 1.0f
);
1073 Class
< ?
extends AbstractAffineModel2D
< ?
> > c
= AffineModel2D
.class;
1074 switch ( matches
.size() )
1077 c
= TranslationModel2D
.class;
1080 c
= SimilarityModel2D
.class;
1086 mlst
.setMatches( matches
);
1093 * Align two collections of tiles
1098 final static public void alignTileCollections( final Param p
, final Collection
< AbstractAffineTile2D
< ?
> > a
, final Collection
< AbstractAffineTile2D
< ?
> > b
)
1100 final ArrayList
< Patch
> pa
= new ArrayList
< Patch
>();
1101 final ArrayList
< Patch
> pb
= new ArrayList
< Patch
>();
1102 for ( final AbstractAffineTile2D
< ?
> t
: a
)
1103 pa
.add( t
.getPatch() );
1104 for ( final AbstractAffineTile2D
< ?
> t
: b
)
1105 pb
.add( t
.getPatch() );
1107 final Layer la
= pa
.iterator().next().getLayer();
1108 final Layer lb
= pb
.iterator().next().getLayer();
1110 final Rectangle boxA
= Displayable
.getBoundingBox( pa
, null );
1111 final Rectangle boxB
= Displayable
.getBoundingBox( pb
, null );
1113 final float scale
= Math
.min(
1117 ( float )p
.sift
.maxOctaveSize
/ ( float )boxA
.width
,
1118 ( float )p
.sift
.maxOctaveSize
/ ( float )boxA
.height
),
1120 ( float )p
.sift
.maxOctaveSize
/ ( float )boxB
.width
,
1121 ( float )p
.sift
.maxOctaveSize
/ ( float )boxB
.height
) ) );
1123 final Param pp
= p
.clone();
1124 pp
.maxEpsilon
*= scale
;
1126 final FloatArray2DSIFT sift
= new FloatArray2DSIFT( pp
.sift
);
1127 final SIFT ijSIFT
= new SIFT( sift
);
1129 final Collection
< Feature
> featuresA
= new ArrayList
< Feature
>();
1130 final Collection
< Feature
> featuresB
= new ArrayList
< Feature
>();
1131 List
< PointMatch
> candidates
= new ArrayList
< PointMatch
>();
1132 List
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
1134 long s
= System
.currentTimeMillis();
1135 ijSIFT
.extractFeatures(
1136 la
.getProject().getLoader().getFlatImage( la
, boxA
, scale
, 0xffffffff, ImagePlus
.GRAY8
, null, pa
, true, Color
.GRAY
).getProcessor(), featuresA
);
1137 Utils
.log( featuresA
.size() + " features extracted in graph A in layer \"" + la
.getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
1139 s
= System
.currentTimeMillis();
1140 ijSIFT
.extractFeatures(
1141 lb
.getProject().getLoader().getFlatImage( lb
, boxB
, scale
, 0xffffffff, ImagePlus
.GRAY8
, null, pb
, true, Color
.GRAY
).getProcessor(), featuresB
);
1142 Utils
.log( featuresB
.size() + " features extracted in graph B in layer \"" + lb
.getTitle() + "\" (took " + ( System
.currentTimeMillis() - s
) + " ms)." );
1144 if ( featuresA
.size() > 0 && featuresB
.size() > 0 )
1146 s
= System
.currentTimeMillis();
1147 FeatureTransform
.matchFeatures(
1153 final AbstractAffineModel2D
< ?
> model
;
1154 switch ( p
.expectedModelIndex
)
1157 model
= new TranslationModel2D();
1160 model
= new RigidModel2D();
1163 model
= new SimilarityModel2D();
1166 model
= new AffineModel2D();
1175 modelFound
= model
.filterRansac(
1181 3 * model
.getMinNumMatches(),
1184 catch ( NotEnoughDataPointsException e
)
1191 IJ
.log( "Model found for graph A and B in layers \"" + la
.getTitle() + "\" and \"" + lb
.getTitle() + "\":\n correspondences " + inliers
.size() + " of " + candidates
.size() + "\n average residual error " + ( model
.getCost() / scale
) + " px\n took " + ( System
.currentTimeMillis() - s
) + " ms" );
1192 final AffineTransform at
= new AffineTransform();
1193 at
.translate( boxA
.x
, boxA
.y
);
1194 at
.scale( 1.0f
/ scale
, 1.0f
/ scale
);
1195 at
.concatenate( model
.createAffine() );
1196 at
.scale( scale
, scale
);
1197 at
.translate( -boxB
.x
, -boxB
.y
);
1199 for ( final Patch t
: pa
)
1200 t
.preTransform( at
, false );
1201 Display
.repaint( la
);
1204 IJ
.log( "No model found for graph A and B in layers \"" + la
.getTitle() + "\" and \"" + lb
.getTitle() + "\"." );