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
.ExecutionException
;
41 import java
.util
.concurrent
.ExecutorService
;
42 import java
.util
.concurrent
.Executors
;
43 import java
.util
.concurrent
.Future
;
44 import java
.util
.concurrent
.atomic
.AtomicInteger
;
46 import mpicbg
.ij
.SIFT
;
47 import mpicbg
.ij
.blockmatching
.BlockMatching
;
48 import mpicbg
.imagefeatures
.Feature
;
49 import mpicbg
.imagefeatures
.FloatArray2DSIFT
;
50 import mpicbg
.models
.AbstractModel
;
51 import mpicbg
.models
.AffineModel2D
;
52 import mpicbg
.models
.ErrorStatistic
;
53 import mpicbg
.models
.HomographyModel2D
;
54 import mpicbg
.models
.InvertibleCoordinateTransform
;
55 import mpicbg
.models
.NotEnoughDataPointsException
;
56 import mpicbg
.models
.Point
;
57 import mpicbg
.models
.PointMatch
;
58 import mpicbg
.models
.RigidModel2D
;
59 import mpicbg
.models
.SimilarityModel2D
;
60 import mpicbg
.models
.Spring
;
61 import mpicbg
.models
.SpringMesh
;
62 import mpicbg
.models
.Tile
;
63 import mpicbg
.models
.TileConfiguration
;
64 import mpicbg
.models
.Transforms
;
65 import mpicbg
.models
.TranslationModel2D
;
66 import mpicbg
.models
.Vertex
;
67 import mpicbg
.trakem2
.transform
.MovingLeastSquaresTransform
;
68 import mpicbg
.trakem2
.transform
.MovingLeastSquaresTransform2
;
71 * @author Stephan Saalfeld <saalfeld@mpi-cbg.de>
74 public class ElasticLayerAlignment
extends AbstractElasticAlignment
76 final static protected class Param
implements Serializable
78 private static final long serialVersionUID
= 875358335352119865L;
80 final public ParamPointMatch ppm
= new ParamPointMatch();
86 * Maximal accepted alignment error in px
88 public float maxEpsilon
= 200.0f
;
91 * Inlier/candidates ratio
93 public float minInlierRatio
= 0.0f
;
96 * Minimal absolute number of inliers
98 public int minNumInliers
= 12;
101 * Transformation models for choice
103 final static public String
[] modelStrings
= new String
[]{ "Translation", "Rigid", "Similarity", "Affine", "Perspective" };
104 public int modelIndex
= 3;
107 * Ignore identity transform up to a given tolerance
109 public boolean rejectIdentity
= true;
110 public float identityTolerance
= 5.0f
;
113 * Maximal number of consecutive sections to be tested for an alignment model
115 public int maxNumNeighbors
= 10;
118 * Maximal number of consecutive slices for which no model could be found
120 public int maxNumFailures
= 3;
122 public float layerScale
= 0.1f
;
123 public float minR
= 0.8f
;
124 public float maxCurvatureR
= 3f
;
125 public float rodR
= 0.8f
;
127 public int modelIndexOptimize
= 1;
128 public int maxIterationsOptimize
= 1000;
129 public int maxPlateauwidthOptimize
= 200;
131 public int resolutionSpringMesh
= 16;
132 public float stiffnessSpringMesh
= 0.1f
;
133 public float dampSpringMesh
= 0.6f
;
134 public float maxStretchSpringMesh
= 2000.0f
;
135 public int maxIterationsSpringMesh
= 1000;
136 public int maxPlateauwidthSpringMesh
= 200;
138 public boolean visualize
= false;
141 public int maxNumThreads
= Runtime
.getRuntime().availableProcessors();
143 public boolean setup()
146 final GenericDialog gd
= new GenericDialog( "Elastically align layers: SIFT parameters" );
148 SIFT
.addFields( gd
, ppm
.sift
);
150 gd
.addNumericField( "closest/next_closest_ratio :", ppm
.rod
, 2 );
152 gd
.addMessage( "Geometric Consensus Filter:" );
153 gd
.addNumericField( "maximal_alignment_error :", maxEpsilon
, 2, 6, "px" );
154 gd
.addNumericField( "minimal_inlier_ratio :", minInlierRatio
, 2 );
155 gd
.addNumericField( "minimal_number_of_inliers :", minNumInliers
, 0 );
156 gd
.addChoice( "approximate_transformation :", Param
.modelStrings
, Param
.modelStrings
[ modelIndex
] );
157 gd
.addCheckbox( "ignore constant background", rejectIdentity
);
158 gd
.addNumericField( "tolerance :", identityTolerance
, 2, 6, "px" );
160 gd
.addMessage( "Matching:" );
161 gd
.addNumericField( "test_maximally :", maxNumNeighbors
, 0, 6, "layers" );
162 gd
.addNumericField( "give_up_after :", maxNumFailures
, 0, 6, "failures" );
164 gd
.addMessage( "Caching:" );
165 gd
.addCheckbox( "clear_cache", ppm
.clearCache
);
169 if ( gd
.wasCanceled() )
172 SIFT
.readFields( gd
, ppm
.sift
);
174 ppm
.rod
= ( float )gd
.getNextNumber();
176 maxEpsilon
= ( float )gd
.getNextNumber();
177 minInlierRatio
= ( float )gd
.getNextNumber();
178 minNumInliers
= ( int )gd
.getNextNumber();
179 modelIndex
= gd
.getNextChoiceIndex();
180 rejectIdentity
= gd
.getNextBoolean();
181 identityTolerance
= ( float )gd
.getNextNumber();
182 maxNumNeighbors
= ( int )gd
.getNextNumber();
183 maxNumFailures
= ( int )gd
.getNextNumber();
185 ppm
.clearCache
= gd
.getNextBoolean();
189 final GenericDialog gdBlockMatching
= new GenericDialog( "Elastically align layers: Block Matching parameters" );
190 gdBlockMatching
.addMessage( "Block Matching:" );
192 /* TODO suggest isotropic resolution for this parameter */
193 gdBlockMatching
.addNumericField( "layer_scale :", layerScale
, 2 );
194 gdBlockMatching
.addNumericField( "minimal_PMCC_r :", minR
, 2 );
195 gdBlockMatching
.addNumericField( "maximal_curvature_ratio :", maxCurvatureR
, 2 );
196 gdBlockMatching
.addNumericField( "maximal_second_best_r/best_r :", rodR
, 2 );
198 /* TODO suggest a resolution that matches maxEpsilon */
199 gdBlockMatching
.addNumericField( "resolution :", resolutionSpringMesh
, 0 );
201 gdBlockMatching
.showDialog();
203 if ( gdBlockMatching
.wasCanceled() )
206 layerScale
= ( float )gdBlockMatching
.getNextNumber();
207 minR
= ( float )gdBlockMatching
.getNextNumber();
208 maxCurvatureR
= ( float )gdBlockMatching
.getNextNumber();
209 rodR
= ( float )gdBlockMatching
.getNextNumber();
210 resolutionSpringMesh
= ( int )gdBlockMatching
.getNextNumber();
213 final GenericDialog gdOptimize
= new GenericDialog( "Elastically align layers: Optimization" );
215 gdOptimize
.addMessage( "Approximate Optimizer:" );
216 gdOptimize
.addChoice( "approximate_transformation :", Param
.modelStrings
, Param
.modelStrings
[ modelIndexOptimize
] );
217 gdOptimize
.addNumericField( "maximal_iterations :", maxIterationsOptimize
, 0 );
218 gdOptimize
.addNumericField( "maximal_plateauwidth :", maxPlateauwidthOptimize
, 0 );
220 gdOptimize
.addMessage( "Spring Mesh:" );
221 gdOptimize
.addNumericField( "stiffness :", stiffnessSpringMesh
, 2 );
222 gdOptimize
.addNumericField( "maximal_stretch :", maxStretchSpringMesh
, 2, 6, "px" );
223 gdOptimize
.addNumericField( "maximal_iterations :", maxIterationsSpringMesh
, 0 );
224 gdOptimize
.addNumericField( "maximal_plateauwidth :", maxPlateauwidthSpringMesh
, 0 );
226 gdOptimize
.showDialog();
228 if ( gdOptimize
.wasCanceled() )
231 modelIndexOptimize
= gdOptimize
.getNextChoiceIndex();
232 maxIterationsOptimize
= ( int )gdOptimize
.getNextNumber();
233 maxPlateauwidthOptimize
= ( int )gdOptimize
.getNextNumber();
235 stiffnessSpringMesh
= ( float )gdOptimize
.getNextNumber();
236 maxStretchSpringMesh
= ( float )gdOptimize
.getNextNumber();
237 maxIterationsSpringMesh
= ( int )gdOptimize
.getNextNumber();
238 maxPlateauwidthSpringMesh
= ( int )gdOptimize
.getNextNumber();
244 final static Param p
= new Param();
247 final static private String
layerName( final Layer layer
)
249 return new StringBuffer( "layer z=" )
250 .append( String
.format( "%.3f", layer
.getZ() ) )
252 .append( layer
.getTitle() )
259 * Extract SIFT features and save them into the project folder.
261 * @param layerSet the layerSet that contains all layers
262 * @param layerRange the list of layers to be aligned
263 * @param box a rectangular region of interest that will be used for alignment
264 * @param scale scale factor <= 1.0
265 * @param filter a name based filter for Patches (can be null)
266 * @param p SIFT extraction parameters
269 final static protected void extractAndSaveLayerFeatures(
270 final LayerSet layerSet
,
271 final List
< Layer
> layerRange
,
274 final Filter
< Patch
> filter
,
275 final FloatArray2DSIFT
.Param siftParam
,
276 final boolean clearCache
) throws ExecutionException
, InterruptedException
278 final ExecutorService exec
= Executors
.newFixedThreadPool( p
.maxNumThreads
);
280 /* extract features for all slices and store them to disk */
281 final AtomicInteger counter
= new AtomicInteger( 0 );
282 final ArrayList
< Future
< ArrayList
< Feature
> > > siftTasks
= new ArrayList
< Future
< ArrayList
< Feature
> > >();
284 for ( int i
= 0; i
< layerRange
.size(); ++i
)
286 final int layerIndex
= i
;
287 final Rectangle finalBox
= box
;
289 exec
.submit( new Callable
< ArrayList
< Feature
> >()
291 public ArrayList
< Feature
> call()
293 final Layer layer
= layerRange
.get( layerIndex
);
295 final String layerName
= layerName( layer
);
297 IJ
.showProgress( counter
.getAndIncrement(), layerRange
.size() - 1 );
299 final List
< Patch
> patches
= filterPatches( layer
, filter
);
301 ArrayList
< Feature
> fs
= null;
303 fs
= mpicbg
.trakem2
.align
.Util
.deserializeFeatures( layerSet
.getProject(), siftParam
, "layer", layer
.getId() );
308 layer
.getProject().getLoader().releaseAll();
310 final FloatArray2DSIFT sift
= new FloatArray2DSIFT( siftParam
);
311 final SIFT ijSIFT
= new SIFT( sift
);
312 fs
= new ArrayList
< Feature
>();
313 final ImageProcessor ip
= layer
.getProject().getLoader().getFlatImage( layer
, finalBox
, scale
, 0xffffffff, ImagePlus
.GRAY8
, Patch
.class, patches
, true ).getProcessor();
314 ijSIFT
.extractFeatures( ip
, fs
);
315 Utils
.log( fs
.size() + " features extracted for " + layerName
);
317 if ( !mpicbg
.trakem2
.align
.Util
.serializeFeatures( layerSet
.getProject(), siftParam
, "layer", layer
.getId(), fs
) )
318 Utils
.log( "FAILED to store serialized features for " + layerName
);
321 Utils
.log( fs
.size() + " features loaded for " + layerName
);
331 for ( Future
< ArrayList
< Feature
> > fu
: siftTasks
)
334 catch ( InterruptedException e
)
336 Utils
.log( "Feature extraction interrupted." );
341 catch ( ExecutionException e
)
343 Utils
.log( "Execution exception during feature extraction." );
356 * Stateful. Changing the parameters of this instance. Do not use in parallel.
361 * @param propagateTransform
365 final public void exec(
366 final LayerSet layerSet
,
369 final boolean propagateTransform
,
371 final Filter
< Patch
> filter
) throws Exception
373 if ( !p
.setup() ) return;
375 final List
< Layer
> layerRange
= layerSet
.getLayers( first
, last
);
377 Utils
.log( layerRange
.size() + "" );
379 Rectangle box
= null;
380 for ( Iterator
< Layer
> it
= layerRange
.iterator(); it
.hasNext(); )
382 /* remove empty layers */
383 final Layer la
= it
.next();
384 if ( !la
.contains( Patch
.class, true ) )
390 /* accumulate boxes */
391 if ( null == box
) // The first layer:
392 box
= la
.getMinimalBoundingBox( Patch
.class, true );
394 box
= box
.union( la
.getMinimalBoundingBox( Patch
.class, true ) );
398 box
= box
.intersection( fov
);
400 if ( box
.width
<= 0 || box
.height
<= 0 )
402 Utils
.log( "Bounding box empty." );
406 if ( layerRange
.size() == 0 )
408 Utils
.log( "All layers in range are empty!" );
412 /* do not work if there is only one layer selected */
413 if ( layerRange
.size() < 2 )
415 Utils
.log( "All except one layer in range are empty!" );
419 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
) );
421 /* extract and save features, overwrite cached files if requested */
424 extractAndSaveLayerFeatures( layerSet
, layerRange
, box
, scale
, filter
, p
.ppm
.sift
, p
.ppm
.clearCache
);
426 catch ( Exception e
)
431 /* create tiles and models for all layers */
432 final ArrayList
< Tile
< ?
> > tiles
= new ArrayList
< Tile
< ?
> >();
433 for ( int i
= 0; i
< layerRange
.size(); ++i
)
435 switch ( p
.modelIndexOptimize
)
438 tiles
.add( new Tile
< TranslationModel2D
>( new TranslationModel2D() ) );
441 tiles
.add( new Tile
< RigidModel2D
>( new RigidModel2D() ) );
444 tiles
.add( new Tile
< SimilarityModel2D
>( new SimilarityModel2D() ) );
447 tiles
.add( new Tile
< AffineModel2D
>( new AffineModel2D() ) );
450 tiles
.add( new Tile
< HomographyModel2D
>( new HomographyModel2D() ) );
457 /* collect all pairs of slices for which a model could be found */
458 final ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > > pairs
= new ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > >();
462 final float pointMatchScale
= p
.layerScale
/ scale
;
464 for ( int i
= 0; i
< layerRange
.size(); ++i
)
466 final ArrayList
< Thread
> threads
= new ArrayList
< Thread
>( p
.maxNumThreads
);
468 final int sliceA
= i
;
469 final Layer layerA
= layerRange
.get( i
);
470 final int range
= Math
.min( layerRange
.size(), i
+ p
.maxNumNeighbors
+ 1 );
472 final String layerNameA
= layerName( layerA
);
474 J
: for ( int j
= i
+ 1; j
< range
; )
476 final int numThreads
= Math
.min( p
.maxNumThreads
, range
- j
);
477 final ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > > models
=
478 new ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > >( numThreads
);
480 for ( int k
= 0; k
< numThreads
; ++k
)
483 for ( int t
= 0; t
< numThreads
&& j
< range
; ++t
, ++j
)
486 final int sliceB
= j
;
487 final Layer layerB
= layerRange
.get( j
);
489 final String layerNameB
= layerName( layerB
);
491 final Thread thread
= new Thread()
495 IJ
.showProgress( sliceA
, layerRange
.size() - 1 );
497 Utils
.log( "matching " + layerNameB
+ " -> " + layerNameA
+ "..." );
499 ArrayList
< PointMatch
> candidates
= null;
500 if ( !p
.ppm
.clearCache
)
501 candidates
= mpicbg
.trakem2
.align
.Util
.deserializePointMatches(
502 layerSet
.getProject(), p
.ppm
, "layer", layerB
.getId(), layerA
.getId() );
504 if ( null == candidates
)
506 ArrayList
< Feature
> fs1
= mpicbg
.trakem2
.align
.Util
.deserializeFeatures(
507 layerSet
.getProject(), p
.ppm
.sift
, "layer", layerA
.getId() );
508 ArrayList
< Feature
> fs2
= mpicbg
.trakem2
.align
.Util
.deserializeFeatures(
509 layerSet
.getProject(), p
.ppm
.sift
, "layer", layerB
.getId() );
510 candidates
= new ArrayList
< PointMatch
>( FloatArray2DSIFT
.createMatches( fs2
, fs1
, p
.ppm
.rod
) );
512 /* scale the candidates */
513 for ( final PointMatch pm
: candidates
)
515 final Point p1
= pm
.getP1();
516 final Point p2
= pm
.getP2();
517 final float[] l1
= p1
.getL();
518 final float[] w1
= p1
.getW();
519 final float[] l2
= p2
.getL();
520 final float[] w2
= p2
.getW();
522 l1
[ 0 ] *= pointMatchScale
;
523 l1
[ 1 ] *= pointMatchScale
;
524 w1
[ 0 ] *= pointMatchScale
;
525 w1
[ 1 ] *= pointMatchScale
;
526 l2
[ 0 ] *= pointMatchScale
;
527 l2
[ 1 ] *= pointMatchScale
;
528 w2
[ 0 ] *= pointMatchScale
;
529 w2
[ 1 ] *= pointMatchScale
;
533 if ( !mpicbg
.trakem2
.align
.Util
.serializePointMatches(
534 layerSet
.getProject(), p
.ppm
, "layer", layerB
.getId(), layerA
.getId(), candidates
) )
535 Utils
.log( "Could not store point match candidates for layers " + layerNameB
+ " and " + layerNameA
+ "." );
538 AbstractModel
< ?
> model
;
539 switch ( p
.modelIndex
)
542 model
= new TranslationModel2D();
545 model
= new RigidModel2D();
548 model
= new SimilarityModel2D();
551 model
= new AffineModel2D();
554 model
= new HomographyModel2D();
560 final ArrayList
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
563 boolean again
= false;
569 modelFound
= model
.filterRansac(
573 p
.maxEpsilon
* p
.layerScale
,
577 if ( modelFound
&& p
.rejectIdentity
)
579 final ArrayList
< Point
> points
= new ArrayList
< Point
>();
580 PointMatch
.sourcePoints( inliers
, points
);
581 if ( Transforms
.isIdentity( model
, points
, p
.identityTolerance
* p
.layerScale
) )
583 IJ
.log( "Identity transform for " + inliers
.size() + " matches rejected." );
584 candidates
.removeAll( inliers
);
592 catch ( NotEnoughDataPointsException e
)
599 Utils
.log( layerNameB
+ " -> " + layerNameA
+ ": " + inliers
.size() + " corresponding features with an average displacement of " + ( PointMatch
.meanDistance( inliers
) / p
.layerScale
) + "px identified." );
600 Utils
.log( "Estimated transformation model: " + model
);
601 models
.set( ti
, new Triple
< Integer
, Integer
, AbstractModel
< ?
> >( sliceA
, sliceB
, model
) );
605 Utils
.log( layerNameB
+ " -> " + layerNameA
+ ": no correspondences found." );
610 threads
.add( thread
);
616 for ( final Thread thread
: threads
)
619 catch ( InterruptedException e
)
621 Utils
.log( "Establishing feature correspondences interrupted." );
622 for ( final Thread thread
: threads
)
626 for ( final Thread thread
: threads
)
629 catch ( InterruptedException f
) {}
635 /* collect successfully matches pairs and break the search on gaps */
636 for ( int t
= 0; t
< models
.size(); ++t
)
638 final Triple
< Integer
, Integer
, AbstractModel
< ?
> > pair
= models
.get( t
);
641 if ( ++numFailures
> p
.maxNumFailures
)
653 /* Elastic alignment */
656 final TileConfiguration initMeshes
= new TileConfiguration();
658 final int meshWidth
= ( int )Math
.ceil( box
.width
* p
.layerScale
);
659 final int meshHeight
= ( int )Math
.ceil( box
.height
* p
.layerScale
);
661 final ArrayList
< SpringMesh
> meshes
= new ArrayList
< SpringMesh
>( layerRange
.size() );
662 for ( int i
= 0; i
< layerRange
.size(); ++i
)
665 p
.resolutionSpringMesh
,
668 p
.stiffnessSpringMesh
,
669 p
.maxStretchSpringMesh
* p
.layerScale
,
670 p
.dampSpringMesh
) );
672 final int blockRadius
= Math
.max( 32, meshWidth
/ p
.resolutionSpringMesh
/ 2 );
674 /** TODO set this something more than the largest error by the approximate model */
675 final int searchRadius
= ( int )Math
.round( p
.layerScale
* p
.maxEpsilon
);
677 for ( final Triple
< Integer
, Integer
, AbstractModel
< ?
> > pair
: pairs
)
680 layerSet
.getProject().getLoader().releaseAll();
682 final SpringMesh m1
= meshes
.get( pair
.a
);
683 final SpringMesh m2
= meshes
.get( pair
.b
);
685 ArrayList
< PointMatch
> pm12
= new ArrayList
< PointMatch
>();
686 ArrayList
< PointMatch
> pm21
= new ArrayList
< PointMatch
>();
688 final Collection
< Vertex
> v1
= m1
.getVertices();
689 final Collection
< Vertex
> v2
= m2
.getVertices();
691 final Layer layer1
= layerRange
.get( pair
.a
);
692 final Layer layer2
= layerRange
.get( pair
.b
);
694 final Image img1
= layerSet
.getProject().getLoader().getFlatAWTImage(
701 filterPatches( layer1
, filter
),
703 new Color( 0x00ffffff, true ) );
705 final Image img2
= layerSet
.getProject().getLoader().getFlatAWTImage(
712 filterPatches( layer2
, filter
),
714 new Color( 0x00ffffff, true ) );
716 final int width
= img1
.getWidth( null );
717 final int height
= img1
.getHeight( null );
719 final FloatProcessor ip1
= new FloatProcessor( width
, height
);
720 final FloatProcessor ip2
= new FloatProcessor( width
, height
);
721 final FloatProcessor ip1Mask
= new FloatProcessor( width
, height
);
722 final FloatProcessor ip2Mask
= new FloatProcessor( width
, height
);
724 mpicbg
.trakem2
.align
.Util
.imageToFloatAndMask( img1
, ip1
, ip1Mask
);
725 mpicbg
.trakem2
.align
.Util
.imageToFloatAndMask( img2
, ip2
, ip2Mask
);
729 BlockMatching
.matchByMaximalPMCC(
735 ( ( InvertibleCoordinateTransform
)pair
.c
).createInverse(),
745 new ErrorStatistic( 1 ) );
747 catch ( InterruptedException e
)
749 Utils
.log( "Block matching interrupted." );
750 IJ
.showProgress( 1.0 );
753 if ( Thread
.interrupted() )
755 Utils
.log( "Block matching interrupted." );
756 IJ
.showProgress( 1.0 );
760 Utils
.log( pair
.a
+ " > " + pair
.b
+ ": found " + pm12
.size() + " correspondences." );
762 /* <visualisation> */
763 // final List< Point > s1 = new ArrayList< Point >();
764 // PointMatch.sourcePoints( pm12, s1 );
765 // final ImagePlus imp1 = new ImagePlus( i + " >", ip1 );
767 // imp1.setOverlay( BlockMatching.illustrateMatches( pm12 ), Color.yellow, null );
768 // imp1.setRoi( Util.pointsToPointRoi( s1 ) );
769 // imp1.updateAndDraw();
770 /* </visualisation> */
774 BlockMatching
.matchByMaximalPMCC(
790 new ErrorStatistic( 1 ) );
792 catch ( InterruptedException e
)
794 Utils
.log( "Block matching interrupted." );
795 IJ
.showProgress( 1.0 );
798 if ( Thread
.interrupted() )
800 Utils
.log( "Block matching interrupted." );
801 IJ
.showProgress( 1.0 );
805 IJ
.log( pair
.a
+ " < " + pair
.b
+ ": found " + pm21
.size() + " correspondences." );
807 /* <visualisation> */
808 // final List< Point > s2 = new ArrayList< Point >();
809 // PointMatch.sourcePoints( pm21, s2 );
810 // final ImagePlus imp2 = new ImagePlus( i + " <", ip2 );
812 // imp2.setOverlay( BlockMatching.illustrateMatches( pm21 ), Color.yellow, null );
813 // imp2.setRoi( Util.pointsToPointRoi( s2 ) );
814 // imp2.updateAndDraw();
815 /* </visualisation> */
817 final float springConstant
= 1.0f
/ ( pair
.b
- pair
.a
);
818 IJ
.log( pair
.a
+ " <> " + pair
.b
+ " spring constant = " + springConstant
);
820 for ( final PointMatch pm
: pm12
)
822 final Vertex p1
= ( Vertex
)pm
.getP1();
823 final Vertex p2
= new Vertex( pm
.getP2() );
824 p1
.addSpring( p2
, new Spring( 0, springConstant
) );
825 m2
.addPassiveVertex( p2
);
828 for ( final PointMatch pm
: pm21
)
830 final Vertex p1
= ( Vertex
)pm
.getP1();
831 final Vertex p2
= new Vertex( pm
.getP2() );
832 p1
.addSpring( p2
, new Spring( 0, springConstant
) );
833 m1
.addPassiveVertex( p2
);
836 final Tile
< ?
> t1
= tiles
.get( pair
.a
);
837 final Tile
< ?
> t2
= tiles
.get( pair
.b
);
840 * adding Tiles to the initialing TileConfiguration, adding a Tile
841 * multiple times does not harm because the TileConfiguration is
844 if ( pm12
.size() > pair
.c
.getMinNumMatches() )
846 initMeshes
.addTile( t1
);
847 initMeshes
.addTile( t2
);
848 t1
.connect( t2
, pm12
);
850 if ( pm21
.size() > pair
.c
.getMinNumMatches() )
852 initMeshes
.addTile( t1
);
853 initMeshes
.addTile( t2
);
854 t2
.connect( t1
, pm21
);
858 /* pre-align by optimizing a piecewise linear model */
860 p
.maxEpsilon
* p
.layerScale
,
861 p
.maxIterationsSpringMesh
,
862 p
.maxPlateauwidthSpringMesh
);
863 for ( int i
= 0; i
< layerRange
.size(); ++i
)
864 meshes
.get( i
).init( tiles
.get( i
).getModel() );
866 /* optimize the meshes */
869 long t0
= System
.currentTimeMillis();
870 IJ
.log("Optimizing spring meshes...");
872 SpringMesh
.optimizeMeshes(
874 p
.maxEpsilon
* p
.layerScale
,
875 p
.maxIterationsSpringMesh
,
876 p
.maxPlateauwidthSpringMesh
,
879 IJ
.log("Done optimizing spring meshes. Took " + (System
.currentTimeMillis() - t0
) + " ms");
882 catch ( NotEnoughDataPointsException e
)
884 Utils
.log( "There were not enough data points to get the spring mesh optimizing." );
889 /* translate relative to bounding box */
890 for ( final SpringMesh mesh
: meshes
)
892 for ( final PointMatch pm
: mesh
.getVA().keySet() )
894 final Point p1
= pm
.getP1();
895 final Point p2
= pm
.getP2();
896 final float[] l
= p1
.getL();
897 final float[] w
= p2
.getW();
898 l
[ 0 ] = l
[ 0 ] / p
.layerScale
+ box
.x
;
899 l
[ 1 ] = l
[ 1 ] / p
.layerScale
+ box
.y
;
900 w
[ 0 ] = w
[ 0 ] / p
.layerScale
+ box
.x
;
901 w
[ 1 ] = w
[ 1 ] / p
.layerScale
+ box
.y
;
906 layerSet
.getProject().getLoader().releaseAll();
908 /* transfer layer transform into patch transforms and append to patches */
909 for ( int i
= 0; i
< layerRange
.size(); ++i
)
911 final Layer layer
= layerRange
.get( i
);
913 final MovingLeastSquaresTransform2 mlt
= new MovingLeastSquaresTransform2();
914 mlt
.setModel( AffineModel2D
.class );
915 mlt
.setAlpha( 2.0f
);
916 mlt
.setMatches( meshes
.get( i
).getVA().keySet() );
918 for ( final Patch patch
: filterPatches( layer
, filter
) )
919 mpicbg
.trakem2
.align
.Util
.applyLayerTransformToPatch( patch
, mlt
);
922 /* update patch mipmaps */
923 for ( final Layer layer
: layerRange
)
924 for ( final Patch patch
: filterPatches( layer
, filter
) )
925 patch
.updateMipMaps();
927 Utils
.log( "Done." );