4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License 2
6 * as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 package mpicbg
.trakem2
.align
;
22 import ij
.gui
.GenericDialog
;
23 import ij
.process
.FloatProcessor
;
24 import ij
.process
.ImageProcessor
;
25 import ini
.trakem2
.display
.Layer
;
26 import ini
.trakem2
.display
.LayerSet
;
27 import ini
.trakem2
.display
.Patch
;
28 import ini
.trakem2
.utils
.Filter
;
29 import ini
.trakem2
.utils
.Utils
;
31 import java
.awt
.Color
;
32 import java
.awt
.Image
;
33 import java
.awt
.Rectangle
;
34 import java
.io
.Serializable
;
35 import java
.util
.ArrayList
;
36 import java
.util
.Collection
;
37 import java
.util
.Iterator
;
38 import java
.util
.List
;
39 import java
.util
.concurrent
.Callable
;
40 import java
.util
.concurrent
.ExecutorService
;
41 import java
.util
.concurrent
.Executors
;
42 import java
.util
.concurrent
.Future
;
43 import java
.util
.concurrent
.atomic
.AtomicInteger
;
45 import mpicbg
.ij
.SIFT
;
46 import mpicbg
.ij
.blockmatching
.BlockMatching
;
47 import mpicbg
.imagefeatures
.Feature
;
48 import mpicbg
.imagefeatures
.FloatArray2DSIFT
;
49 import mpicbg
.models
.AbstractModel
;
50 import mpicbg
.models
.AffineModel2D
;
51 import mpicbg
.models
.ErrorStatistic
;
52 import mpicbg
.models
.HomographyModel2D
;
53 import mpicbg
.models
.InvertibleCoordinateTransform
;
54 import mpicbg
.models
.NotEnoughDataPointsException
;
55 import mpicbg
.models
.Point
;
56 import mpicbg
.models
.PointMatch
;
57 import mpicbg
.models
.RigidModel2D
;
58 import mpicbg
.models
.SimilarityModel2D
;
59 import mpicbg
.models
.Spring
;
60 import mpicbg
.models
.SpringMesh
;
61 import mpicbg
.models
.Tile
;
62 import mpicbg
.models
.TileConfiguration
;
63 import mpicbg
.models
.Transforms
;
64 import mpicbg
.models
.TranslationModel2D
;
65 import mpicbg
.models
.Vertex
;
66 import mpicbg
.trakem2
.transform
.MovingLeastSquaresTransform
;
69 * @author Stephan Saalfeld <saalfeld@mpi-cbg.de>
72 public class ElasticAlign
75 final static private class Triple
< A
, B
, C
>
81 Triple( final A a
, final B b
, final C c
)
89 final static private class Param
implements Serializable
91 private static final long serialVersionUID
= 3816564377727147658L;
93 final static private class ParamPointMatch
implements Serializable
95 private static final long serialVersionUID
= 2605327789375628874L;
97 final public FloatArray2DSIFT
.Param sift
= new FloatArray2DSIFT
.Param();
100 * Closest/next closest neighbor distance ratio
102 public float rod
= 0.92f
;
105 public boolean equals( Object o
)
107 if ( getClass().isInstance( o
) )
109 final ParamPointMatch oppm
= ( ParamPointMatch
)o
;
111 oppm
.sift
.equals( sift
) &
118 public boolean clearCache
= false;
121 final public ParamPointMatch ppm
= new ParamPointMatch();
124 * Maximal accepted alignment error in px
126 public float maxEpsilon
= 25.0f
;
129 * Inlier/candidates ratio
131 public float minInlierRatio
= 0.0f
;
134 * Minimal absolute number of inliers
136 public int minNumInliers
= 12;
139 * Transformation models for choice
141 final static public String
[] modelStrings
= new String
[]{ "Translation", "Rigid", "Similarity", "Affine", "Perspective" };
142 public int modelIndex
= 3;
145 * Ignore identity transform up to a given tolerance
147 public boolean rejectIdentity
= true;
148 public float identityTolerance
= 5.0f
;
151 * Maximal number of consecutive sections to be tested for an alignment model
153 public int maxNumNeighbors
= 10;
156 * Maximal number of consecutive slices for which no model could be found
158 public int maxNumFailures
= 3;
160 public float layerScale
= 0.1f
;
161 public float minR
= 0.8f
;
162 public float maxCurvatureR
= 3f
;
163 public float rodR
= 0.8f
;
165 public int modelIndexOptimize
= 1;
166 public int maxIterationsOptimize
= 1000;
167 public int maxPlateauwidthOptimize
= 200;
169 public int resolutionSpringMesh
= 16;
170 public float stiffnessSpringMesh
= 0.1f
;
171 public float dampSpringMesh
= 0.6f
;
172 public float maxStretchSpringMesh
= 2000.0f
;
173 public int maxIterationsSpringMesh
= 1000;
174 public int maxPlateauwidthSpringMesh
= 200;
176 public boolean visualize
= false;
179 public int maxNumThreads
= Runtime
.getRuntime().availableProcessors();
181 public boolean setup()
184 final GenericDialog gd
= new GenericDialog( "Elastically align layers: SIFT parameters" );
186 SIFT
.addFields( gd
, ppm
.sift
);
188 gd
.addNumericField( "closest/next_closest_ratio :", ppm
.rod
, 2 );
190 gd
.addMessage( "Geometric Consensus Filter:" );
191 gd
.addNumericField( "maximal_alignment_error :", maxEpsilon
, 2, 6, "px" );
192 gd
.addNumericField( "minimal_inlier_ratio :", minInlierRatio
, 2 );
193 gd
.addNumericField( "minimal_number_of_inliers :", minNumInliers
, 0 );
194 gd
.addChoice( "approximate_transformation :", Param
.modelStrings
, Param
.modelStrings
[ modelIndex
] );
195 gd
.addCheckbox( "ignore constant background", rejectIdentity
);
196 gd
.addNumericField( "tolerance :", identityTolerance
, 2, 6, "px" );
198 gd
.addMessage( "Matching:" );
199 gd
.addNumericField( "test_maximally :", maxNumNeighbors
, 0, 6, "layers" );
200 gd
.addNumericField( "give_up_after :", maxNumFailures
, 0, 6, "failures" );
202 gd
.addMessage( "Caching:" );
203 gd
.addCheckbox( "clear_cache", ppm
.clearCache
);
207 if ( gd
.wasCanceled() )
210 SIFT
.readFields( gd
, ppm
.sift
);
212 ppm
.rod
= ( float )gd
.getNextNumber();
214 maxEpsilon
= ( float )gd
.getNextNumber();
215 minInlierRatio
= ( float )gd
.getNextNumber();
216 minNumInliers
= ( int )gd
.getNextNumber();
217 modelIndex
= gd
.getNextChoiceIndex();
218 rejectIdentity
= gd
.getNextBoolean();
219 identityTolerance
= ( float )gd
.getNextNumber();
220 maxNumNeighbors
= ( int )gd
.getNextNumber();
221 maxNumFailures
= ( int )gd
.getNextNumber();
223 ppm
.clearCache
= gd
.getNextBoolean();
227 final GenericDialog gdBlockMatching
= new GenericDialog( "Elastically align layers: Block Matching parameters" );
228 gdBlockMatching
.addMessage( "Block Matching:" );
230 /* TODO suggest isotropic resolution for this parameter */
231 gdBlockMatching
.addNumericField( "layer_scale :", layerScale
, 2 );
232 gdBlockMatching
.addNumericField( "minimal_PMCC_r :", minR
, 2 );
233 gdBlockMatching
.addNumericField( "maximal_curvature_ratio :", maxCurvatureR
, 2 );
234 gdBlockMatching
.addNumericField( "maximal_second_best_r/best_r :", rodR
, 2 );
236 /* TODO suggest a resolution that matches maxEpsilon */
237 gdBlockMatching
.addNumericField( "resolution :", resolutionSpringMesh
, 0 );
239 gdBlockMatching
.showDialog();
241 if ( gdBlockMatching
.wasCanceled() )
244 layerScale
= ( float )gdBlockMatching
.getNextNumber();
245 minR
= ( float )gdBlockMatching
.getNextNumber();
246 maxCurvatureR
= ( float )gdBlockMatching
.getNextNumber();
247 rodR
= ( float )gdBlockMatching
.getNextNumber();
248 resolutionSpringMesh
= ( int )gdBlockMatching
.getNextNumber();
251 final GenericDialog gdOptimize
= new GenericDialog( "Elastically align layers: Optimization" );
253 gdOptimize
.addMessage( "Approximate Optimizer:" );
254 gdOptimize
.addChoice( "approximate_transformation :", Param
.modelStrings
, Param
.modelStrings
[ modelIndexOptimize
] );
255 gdOptimize
.addNumericField( "maximal_iterations :", maxIterationsOptimize
, 0 );
256 gdOptimize
.addNumericField( "maximal_plateauwidth :", maxPlateauwidthOptimize
, 0 );
258 gdOptimize
.addMessage( "Spring Mesh:" );
259 gdOptimize
.addNumericField( "stiffness :", stiffnessSpringMesh
, 2 );
260 gdOptimize
.addNumericField( "maximal_stretch :", maxStretchSpringMesh
, 2, 6, "px" );
261 gdOptimize
.addNumericField( "maximal_iterations :", maxIterationsSpringMesh
, 0 );
262 gdOptimize
.addNumericField( "maximal_plateauwidth :", maxPlateauwidthSpringMesh
, 0 );
264 gdOptimize
.showDialog();
266 if ( gdOptimize
.wasCanceled() )
269 modelIndexOptimize
= gdOptimize
.getNextChoiceIndex();
270 maxIterationsOptimize
= ( int )gdOptimize
.getNextNumber();
271 maxPlateauwidthOptimize
= ( int )gdOptimize
.getNextNumber();
273 stiffnessSpringMesh
= ( float )gdOptimize
.getNextNumber();
274 maxStretchSpringMesh
= ( float )gdOptimize
.getNextNumber();
275 maxIterationsSpringMesh
= ( int )gdOptimize
.getNextNumber();
276 maxPlateauwidthSpringMesh
= ( int )gdOptimize
.getNextNumber();
282 final static Param p
= new Param();
284 final static protected String
layerName( final Layer layer
)
286 return new StringBuffer( "layer z=" )
287 .append( String
.format( "%.3f", layer
.getZ() ) )
289 .append( layer
.getTitle() )
295 final static List
< Patch
> filterPatches( final Layer layer
, final Filter
< Patch
> filter
)
297 final List
< Patch
> patches
= layer
.getAll( Patch
.class );
298 if ( filter
!= null )
300 for ( final Iterator
< Patch
> it
= patches
.iterator(); it
.hasNext(); )
302 if ( !filter
.accept( it
.next() ) )
311 * Extract SIFT features and save them into the project folder.
313 * @param layerSet the layerSet that contains all layers
314 * @param layerRange the list of layers to be aligned
315 * @param box a rectangular region of interest that will be used for alignment
316 * @param scale scale factor <= 1.0
317 * @param filter a name based filter for Patches (can be null)
318 * @param p SIFT extraction parameters
321 final static protected void extractAndSaveLayerFeatures(
322 final LayerSet layerSet
,
323 final List
< Layer
> layerRange
,
326 final Filter
< Patch
> filter
,
327 final FloatArray2DSIFT
.Param siftParam
,
328 final boolean clearCache
) throws Exception
330 final ExecutorService exec
= Executors
.newFixedThreadPool( p
.maxNumThreads
);
332 /* extract features for all slices and store them to disk */
333 final AtomicInteger counter
= new AtomicInteger( 0 );
334 final ArrayList
< Future
< ArrayList
< Feature
> > > siftTasks
= new ArrayList
< Future
< ArrayList
< Feature
> > >();
336 for ( int i
= 0; i
< layerRange
.size(); ++i
)
338 final int layerIndex
= i
;
339 final Rectangle finalBox
= box
;
341 exec
.submit( new Callable
< ArrayList
< Feature
> >()
343 public ArrayList
< Feature
> call()
345 final Layer layer
= layerRange
.get( layerIndex
);
347 final String layerName
= layerName( layer
);
349 IJ
.showProgress( counter
.getAndIncrement(), layerRange
.size() - 1 );
351 final List
< Patch
> patches
= filterPatches( layer
, filter
);
353 ArrayList
< Feature
> fs
= null;
355 fs
= mpicbg
.trakem2
.align
.Util
.deserializeFeatures( layerSet
.getProject(), siftParam
, "layer", layer
.getId() );
360 final FloatArray2DSIFT sift
= new FloatArray2DSIFT( siftParam
);
361 final SIFT ijSIFT
= new SIFT( sift
);
362 fs
= new ArrayList
< Feature
>();
363 final ImageProcessor ip
= layer
.getProject().getLoader().getFlatImage( layer
, finalBox
, scale
, 0xffffffff, ImagePlus
.GRAY8
, Patch
.class, patches
, true ).getProcessor();
364 ijSIFT
.extractFeatures( ip
, fs
);
365 Utils
.log( fs
.size() + " features extracted for " + layerName
);
367 if ( !mpicbg
.trakem2
.align
.Util
.serializeFeatures( layerSet
.getProject(), p
.ppm
.sift
, "layer", layer
.getId(), fs
) )
368 Utils
.log( "FAILED to store serialized features for " + layerName
);
371 Utils
.log( fs
.size() + " features loaded for " + layerName
);
379 for ( Future
< ArrayList
< Feature
> > fu
: siftTasks
)
389 * Stateful. Changing the parameters of this instance. Do not use in parallel.
394 * @param propagateTransform
398 final public void exec(
399 final LayerSet layerSet
,
402 final boolean propagateTransform
,
404 final Filter
< Patch
> filter
) throws Exception
406 if ( !p
.setup() ) return;
408 final List
< Layer
> layerRange
= layerSet
.getLayers( first
, last
);
410 Utils
.log( layerRange
.size() + "" );
412 Rectangle box
= null;
413 for ( Iterator
< Layer
> it
= layerRange
.iterator(); it
.hasNext(); )
415 /* remove empty layers */
416 final Layer la
= it
.next();
417 if ( !la
.contains( Patch
.class, true ) )
423 /* accumulate boxes */
424 if ( null == box
) // The first layer:
425 box
= la
.getMinimalBoundingBox( Patch
.class, true );
427 box
= box
.union( la
.getMinimalBoundingBox( Patch
.class, true ) );
431 box
= box
.intersection( fov
);
433 if ( box
.width
<= 0 || box
.height
<= 0 )
435 Utils
.log( "Bounding box empty." );
439 if ( layerRange
.size() == 0 )
441 Utils
.log( "All layers in range are empty!" );
445 /* do not work if there is only one layer selected */
446 if ( layerRange
.size() < 2 )
448 Utils
.log( "All except one layer in range are empty!" );
452 final float scale
= Math
.min( 1.0f
, Math
.min( ( float )p
.ppm
.sift
.maxOctaveSize
/ ( float )box
.width
, ( float )p
.ppm
.sift
.maxOctaveSize
/ ( float )box
.height
) );
454 /* extract and save features, overwrite cached files if requested */
455 extractAndSaveLayerFeatures( layerSet
, layerRange
, box
, scale
, filter
, p
.ppm
.sift
, p
.ppm
.clearCache
);
457 /* create tiles and models for all layers */
458 final ArrayList
< Tile
< ?
> > tiles
= new ArrayList
< Tile
< ?
> >();
459 for ( int i
= 0; i
< layerRange
.size(); ++i
)
461 switch ( p
.modelIndexOptimize
)
464 tiles
.add( new Tile
< TranslationModel2D
>( new TranslationModel2D() ) );
467 tiles
.add( new Tile
< RigidModel2D
>( new RigidModel2D() ) );
470 tiles
.add( new Tile
< SimilarityModel2D
>( new SimilarityModel2D() ) );
473 tiles
.add( new Tile
< AffineModel2D
>( new AffineModel2D() ) );
476 tiles
.add( new Tile
< HomographyModel2D
>( new HomographyModel2D() ) );
483 /* collect all pairs of slices for which a model could be found */
484 final ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > > pairs
= new ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > >();
488 final float pointMatchScale
= p
.layerScale
/ scale
;
490 for ( int i
= 0; i
< layerRange
.size(); ++i
)
492 final ArrayList
< Thread
> threads
= new ArrayList
< Thread
>( p
.maxNumThreads
);
494 final int sliceA
= i
;
495 final Layer layerA
= layerRange
.get( i
);
496 final int range
= Math
.min( layerRange
.size(), i
+ p
.maxNumNeighbors
+ 1 );
498 final String layerNameA
= layerName( layerA
);
500 J
: for ( int j
= i
+ 1; j
< range
; )
502 final int numThreads
= Math
.min( p
.maxNumThreads
, range
- j
);
503 final ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > > models
=
504 new ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > >( numThreads
);
506 for ( int k
= 0; k
< numThreads
; ++k
)
509 for ( int t
= 0; t
< numThreads
&& j
< range
; ++t
, ++j
)
512 final int sliceB
= j
;
513 final Layer layerB
= layerRange
.get( j
);
515 final String layerNameB
= layerName( layerB
);
517 final Thread thread
= new Thread()
521 IJ
.showProgress( sliceA
, layerRange
.size() - 1 );
523 Utils
.log( "matching " + layerNameB
+ " -> " + layerNameA
+ "..." );
525 ArrayList
< PointMatch
> candidates
= null;
526 if ( !p
.ppm
.clearCache
)
527 candidates
= mpicbg
.trakem2
.align
.Util
.deserializePointMatches(
528 layerSet
.getProject(), p
.ppm
, "layer", layerB
.getId(), layerA
.getId() );
530 if ( null == candidates
)
532 ArrayList
< Feature
> fs1
= mpicbg
.trakem2
.align
.Util
.deserializeFeatures(
533 layerSet
.getProject(), p
.ppm
.sift
, "layer", layerA
.getId() );
534 ArrayList
< Feature
> fs2
= mpicbg
.trakem2
.align
.Util
.deserializeFeatures(
535 layerSet
.getProject(), p
.ppm
.sift
, "layer", layerB
.getId() );
536 candidates
= new ArrayList
< PointMatch
>( FloatArray2DSIFT
.createMatches( fs2
, fs1
, p
.ppm
.rod
) );
538 /* scale the candidates */
539 for ( final PointMatch pm
: candidates
)
541 final Point p1
= pm
.getP1();
542 final Point p2
= pm
.getP2();
543 final float[] l1
= p1
.getL();
544 final float[] w1
= p1
.getW();
545 final float[] l2
= p2
.getL();
546 final float[] w2
= p2
.getW();
548 l1
[ 0 ] *= pointMatchScale
;
549 l1
[ 1 ] *= pointMatchScale
;
550 w1
[ 0 ] *= pointMatchScale
;
551 w1
[ 1 ] *= pointMatchScale
;
552 l2
[ 0 ] *= pointMatchScale
;
553 l2
[ 1 ] *= pointMatchScale
;
554 w2
[ 0 ] *= pointMatchScale
;
555 w2
[ 1 ] *= pointMatchScale
;
559 if ( !mpicbg
.trakem2
.align
.Util
.serializePointMatches(
560 layerSet
.getProject(), p
.ppm
, "layer", layerB
.getId(), layerA
.getId(), candidates
) )
561 Utils
.log( "Could not store point match candidates for layers " + layerNameB
+ " and " + layerNameA
+ "." );
564 AbstractModel
< ?
> model
;
565 switch ( p
.modelIndex
)
568 model
= new TranslationModel2D();
571 model
= new RigidModel2D();
574 model
= new SimilarityModel2D();
577 model
= new AffineModel2D();
580 model
= new HomographyModel2D();
586 final ArrayList
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
589 boolean again
= false;
595 modelFound
= model
.filterRansac(
599 p
.maxEpsilon
* p
.layerScale
,
603 if ( modelFound
&& p
.rejectIdentity
)
605 final ArrayList
< Point
> points
= new ArrayList
< Point
>();
606 PointMatch
.sourcePoints( inliers
, points
);
607 if ( Transforms
.isIdentity( model
, points
, p
.identityTolerance
* p
.layerScale
) )
609 IJ
.log( "Identity transform for " + inliers
.size() + " matches rejected." );
610 candidates
.removeAll( inliers
);
618 catch ( NotEnoughDataPointsException e
)
625 Utils
.log( layerNameB
+ " -> " + layerNameA
+ ": " + inliers
.size() + " corresponding features with an average displacement of " + ( PointMatch
.meanDistance( inliers
) / p
.layerScale
) + "px identified." );
626 Utils
.log( "Estimated transformation model: " + model
);
627 models
.set( ti
, new Triple
< Integer
, Integer
, AbstractModel
< ?
> >( sliceA
, sliceB
, model
) );
631 Utils
.log( layerNameB
+ " -> " + layerNameA
+ ": no correspondences found." );
636 threads
.add( thread
);
640 for ( final Thread thread
: threads
)
645 /* collect successfully matches pairs and break the search on gaps */
646 for ( int t
= 0; t
< models
.size(); ++t
)
648 final Triple
< Integer
, Integer
, AbstractModel
< ?
> > pair
= models
.get( t
);
651 if ( ++numFailures
> p
.maxNumFailures
)
663 /* Elastic alignment */
666 final TileConfiguration initMeshes
= new TileConfiguration();
668 final int meshWidth
= ( int )Math
.ceil( box
.width
* p
.layerScale
);
669 final int meshHeight
= ( int )Math
.ceil( box
.height
* p
.layerScale
);
671 final ArrayList
< SpringMesh
> meshes
= new ArrayList
< SpringMesh
>( layerRange
.size() );
672 for ( int i
= 0; i
< layerRange
.size(); ++i
)
675 p
.resolutionSpringMesh
,
678 p
.stiffnessSpringMesh
,
679 p
.maxStretchSpringMesh
* p
.layerScale
,
680 p
.dampSpringMesh
) );
682 final int blockRadius
= Math
.max( 32, meshWidth
/ p
.resolutionSpringMesh
/ 2 );
684 /** TODO set this something more than the largest error by the approximate model */
685 final int searchRadius
= ( int )Math
.round( p
.layerScale
* p
.maxEpsilon
);
687 for ( final Triple
< Integer
, Integer
, AbstractModel
< ?
> > pair
: pairs
)
689 final SpringMesh m1
= meshes
.get( pair
.a
);
690 final SpringMesh m2
= meshes
.get( pair
.b
);
692 ArrayList
< PointMatch
> pm12
= new ArrayList
< PointMatch
>();
693 ArrayList
< PointMatch
> pm21
= new ArrayList
< PointMatch
>();
695 final Collection
< Vertex
> v1
= m1
.getVertices();
696 final Collection
< Vertex
> v2
= m2
.getVertices();
698 final Layer layer1
= layerRange
.get( pair
.a
);
699 final Layer layer2
= layerRange
.get( pair
.b
);
701 final Image img1
= layerSet
.getProject().getLoader().getFlatAWTImage(
708 filterPatches( layer1
, filter
),
710 new Color( 0x00ffffff, true ) );
712 final Image img2
= layerSet
.getProject().getLoader().getFlatAWTImage(
719 filterPatches( layer2
, filter
),
721 new Color( 0x00ffffff, true ) );
723 final int width
= img1
.getWidth( null );
724 final int height
= img1
.getHeight( null );
726 final FloatProcessor ip1
= new FloatProcessor( width
, height
);
727 final FloatProcessor ip2
= new FloatProcessor( width
, height
);
728 final FloatProcessor ip1Mask
= new FloatProcessor( width
, height
);
729 final FloatProcessor ip2Mask
= new FloatProcessor( width
, height
);
731 mpicbg
.trakem2
.align
.Util
.imageToFloatAndMask( img1
, ip1
, ip1Mask
);
732 mpicbg
.trakem2
.align
.Util
.imageToFloatAndMask( img2
, ip2
, ip2Mask
);
734 BlockMatching
.matchByMaximalPMCC(
740 ( ( InvertibleCoordinateTransform
)pair
.c
).createInverse(),
750 new ErrorStatistic( 1 ) );
752 IJ
.log( pair
.a
+ " > " + pair
.b
+ ": found " + pm12
.size() + " correspondences." );
754 /* <visualisation> */
755 // final List< Point > s1 = new ArrayList< Point >();
756 // PointMatch.sourcePoints( pm12, s1 );
757 // final ImagePlus imp1 = new ImagePlus( i + " >", ip1 );
759 // imp1.setOverlay( BlockMatching.illustrateMatches( pm12 ), Color.yellow, null );
760 // imp1.setRoi( Util.pointsToPointRoi( s1 ) );
761 // imp1.updateAndDraw();
762 /* </visualisation> */
764 BlockMatching
.matchByMaximalPMCC(
780 new ErrorStatistic( 1 ) );
782 IJ
.log( pair
.a
+ " < " + pair
.b
+ ": found " + pm21
.size() + " correspondences." );
784 /* <visualisation> */
785 // final List< Point > s2 = new ArrayList< Point >();
786 // PointMatch.sourcePoints( pm21, s2 );
787 // final ImagePlus imp2 = new ImagePlus( i + " <", ip2 );
789 // imp2.setOverlay( BlockMatching.illustrateMatches( pm21 ), Color.yellow, null );
790 // imp2.setRoi( Util.pointsToPointRoi( s2 ) );
791 // imp2.updateAndDraw();
792 /* </visualisation> */
794 final float springConstant
= 1.0f
/ ( pair
.b
- pair
.a
);
795 IJ
.log( pair
.a
+ " <> " + pair
.b
+ " spring constant = " + springConstant
);
797 for ( final PointMatch pm
: pm12
)
799 final Vertex p1
= ( Vertex
)pm
.getP1();
800 final Vertex p2
= new Vertex( pm
.getP2() );
801 p1
.addSpring( p2
, new Spring( 0, springConstant
) );
802 m2
.addPassiveVertex( p2
);
805 for ( final PointMatch pm
: pm21
)
807 final Vertex p1
= ( Vertex
)pm
.getP1();
808 final Vertex p2
= new Vertex( pm
.getP2() );
809 p1
.addSpring( p2
, new Spring( 0, springConstant
) );
810 m1
.addPassiveVertex( p2
);
813 final Tile
< ?
> t1
= tiles
.get( pair
.a
);
814 final Tile
< ?
> t2
= tiles
.get( pair
.b
);
817 * adding Tiles to the initialing TileConfiguration, adding a Tile
818 * multiple times does not harm because the TileConfiguration is
821 if ( pm12
.size() > pair
.c
.getMinNumMatches() )
823 initMeshes
.addTile( t1
);
824 initMeshes
.addTile( t2
);
825 t1
.connect( t2
, pm12
);
827 if ( pm21
.size() > pair
.c
.getMinNumMatches() )
829 initMeshes
.addTile( t1
);
830 initMeshes
.addTile( t2
);
831 t2
.connect( t1
, pm21
);
835 /* pre-align by optimizing a piecewise linear model */
837 p
.maxEpsilon
* p
.layerScale
,
838 p
.maxIterationsSpringMesh
,
839 p
.maxPlateauwidthSpringMesh
);
840 for ( int i
= 0; i
< layerRange
.size(); ++i
)
841 meshes
.get( i
).init( tiles
.get( i
).getModel() );
843 /* optimize the meshes */
846 long t0
= System
.currentTimeMillis();
847 IJ
.log("Optimizing spring meshes...");
849 SpringMesh
.optimizeMeshes(
851 p
.maxEpsilon
* p
.layerScale
,
852 p
.maxIterationsSpringMesh
,
853 p
.maxPlateauwidthSpringMesh
,
856 IJ
.log("Done optimizing spring meshes. Took " + (System
.currentTimeMillis() - t0
) + " ms");
859 catch ( NotEnoughDataPointsException e
) { e
.printStackTrace(); }
861 /* translate relative to bounding box */
862 for ( final SpringMesh mesh
: meshes
)
864 for ( final PointMatch pm
: mesh
.getVA().keySet() )
866 final Point p1
= pm
.getP1();
867 final Point p2
= pm
.getP2();
868 final float[] l
= p1
.getL();
869 final float[] w
= p2
.getW();
870 l
[ 0 ] = l
[ 0 ] / p
.layerScale
+ box
.x
;
871 l
[ 1 ] = l
[ 1 ] / p
.layerScale
+ box
.y
;
872 w
[ 0 ] = w
[ 0 ] / p
.layerScale
+ box
.x
;
873 w
[ 1 ] = w
[ 1 ] / p
.layerScale
+ box
.y
;
877 for ( int i
= 0; i
< layerRange
.size(); ++i
)
879 final Layer layer
= layerRange
.get( i
);
881 final MovingLeastSquaresTransform mlt
= new MovingLeastSquaresTransform();
882 mlt
.setModel( AffineModel2D
.class );
883 mlt
.setAlpha( 2.0f
);
884 mlt
.setMatches( meshes
.get( i
).getVA().keySet() );
886 for ( final Patch patch
: filterPatches( layer
, filter
) )
888 mpicbg
.trakem2
.align
.Util
.applyLayerTransformToPatch( patch
, mlt
);
892 Utils
.log( "Done." );