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 ini
.trakem2
.Project
;
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
.HashSet
;
38 import java
.util
.Iterator
;
39 import java
.util
.List
;
41 import java
.util
.concurrent
.atomic
.AtomicInteger
;
43 import mpicbg
.ij
.blockmatching
.BlockMatching
;
44 import mpicbg
.imagefeatures
.Feature
;
45 import mpicbg
.imagefeatures
.FloatArray2DSIFT
;
46 import mpicbg
.models
.AbstractModel
;
47 import mpicbg
.models
.AffineModel2D
;
48 import mpicbg
.models
.ErrorStatistic
;
49 import mpicbg
.models
.HomographyModel2D
;
50 import mpicbg
.models
.InvertibleCoordinateTransform
;
51 import mpicbg
.models
.NotEnoughDataPointsException
;
52 import mpicbg
.models
.Point
;
53 import mpicbg
.models
.PointMatch
;
54 import mpicbg
.models
.RigidModel2D
;
55 import mpicbg
.models
.SimilarityModel2D
;
56 import mpicbg
.models
.Spring
;
57 import mpicbg
.models
.SpringMesh
;
58 import mpicbg
.models
.Tile
;
59 import mpicbg
.models
.TileConfiguration
;
60 import mpicbg
.models
.Transforms
;
61 import mpicbg
.models
.TranslationModel2D
;
62 import mpicbg
.models
.Vertex
;
63 import mpicbg
.trakem2
.transform
.MovingLeastSquaresTransform2
;
64 import mpicbg
.trakem2
.util
.Triple
;
67 * @author Stephan Saalfeld <saalfeld@mpi-cbg.de>
69 public class ElasticLayerAlignment
71 final static public class Param
extends AbstractLayerAlignmentParam
implements Serializable
73 private static final long serialVersionUID
= 398966126705836033L;
75 public boolean isAligned
= false;
77 public float layerScale
= 0.1f
;
78 public float minR
= 0.6f
;
79 public float maxCurvatureR
= 10f
;
80 public float rodR
= 0.9f
;
81 public int searchRadius
= 200;
82 public int blockRadius
= -1;
84 public boolean useLocalSmoothnessFilter
= true;
85 public int localModelIndex
= 1;
86 public float localRegionSigma
= searchRadius
;
87 public float maxLocalEpsilon
= searchRadius
/ 2;
88 public float maxLocalTrust
= 3;
90 public int resolutionSpringMesh
= 16;
91 public float stiffnessSpringMesh
= 0.1f
;
92 public float dampSpringMesh
= 0.6f
;
93 public float maxStretchSpringMesh
= 2000.0f
;
94 public int maxIterationsSpringMesh
= 1000;
95 public int maxPlateauwidthSpringMesh
= 200;
97 public boolean setup( final Rectangle box
)
100 if ( blockRadius
< 0 )
102 blockRadius
= box
.width
/ resolutionSpringMesh
/ 2;
104 final GenericDialog gdBlockMatching
= new GenericDialog( "Elastically align layers: Block Matching parameters" );
106 gdBlockMatching
.addMessage( "Block Matching:" );
107 /* TODO suggest isotropic resolution for this parameter */
108 gdBlockMatching
.addNumericField( "layer_scale :", layerScale
, 2 );
109 gdBlockMatching
.addNumericField( "search_radius :", searchRadius
, 0, 6, "px" );
110 gdBlockMatching
.addNumericField( "block_radius :", blockRadius
, 0, 6, "px" );
111 /* TODO suggest a resolution that matches searchRadius */
112 gdBlockMatching
.addNumericField( "resolution :", resolutionSpringMesh
, 0 );
114 gdBlockMatching
.addMessage( "Correlation Filters:" );
115 gdBlockMatching
.addNumericField( "minimal_PMCC_r :", minR
, 2 );
116 gdBlockMatching
.addNumericField( "maximal_curvature_ratio :", maxCurvatureR
, 2 );
117 gdBlockMatching
.addNumericField( "maximal_second_best_r/best_r :", rodR
, 2 );
119 gdBlockMatching
.addMessage( "Local Smoothness Filter:" );
120 gdBlockMatching
.addCheckbox( "use_local_smoothness_filter", useLocalSmoothnessFilter
);
121 gdBlockMatching
.addChoice( "approximate_local_transformation :", Param
.modelStrings
, Param
.modelStrings
[ localModelIndex
] );
122 gdBlockMatching
.addNumericField( "local_region_sigma:", localRegionSigma
, 2, 6, "px" );
123 gdBlockMatching
.addNumericField( "maximal_local_displacement (absolute):", maxLocalEpsilon
, 2, 6, "px" );
124 gdBlockMatching
.addNumericField( "maximal_local_displacement (relative):", maxLocalTrust
, 2 );
126 gdBlockMatching
.addMessage( "Miscellaneous:" );
127 gdBlockMatching
.addCheckbox( "layers_are_pre-aligned", isAligned
);
128 gdBlockMatching
.addNumericField( "test_maximally :", maxNumNeighbors
, 0, 6, "layers" );
130 gdBlockMatching
.showDialog();
132 if ( gdBlockMatching
.wasCanceled() )
135 layerScale
= ( float )gdBlockMatching
.getNextNumber();
136 searchRadius
= ( int )gdBlockMatching
.getNextNumber();
137 blockRadius
= ( int )gdBlockMatching
.getNextNumber();
138 resolutionSpringMesh
= ( int )gdBlockMatching
.getNextNumber();
139 minR
= ( float )gdBlockMatching
.getNextNumber();
140 maxCurvatureR
= ( float )gdBlockMatching
.getNextNumber();
141 rodR
= ( float )gdBlockMatching
.getNextNumber();
142 useLocalSmoothnessFilter
= gdBlockMatching
.getNextBoolean();
143 localModelIndex
= gdBlockMatching
.getNextChoiceIndex();
144 localRegionSigma
= ( float )gdBlockMatching
.getNextNumber();
145 maxLocalEpsilon
= ( float )gdBlockMatching
.getNextNumber();
146 maxLocalTrust
= ( float )gdBlockMatching
.getNextNumber();
147 isAligned
= gdBlockMatching
.getNextBoolean();
148 maxNumNeighbors
= ( int )gdBlockMatching
.getNextNumber();
153 if ( !setupSIFT( "Elastically align layers: " ) )
156 /* Geometric filters */
158 final GenericDialog gd
= new GenericDialog( "Elastically align layers: Geometric filters" );
160 gd
.addNumericField( "maximal_alignment_error :", maxEpsilon
, 2, 6, "px" );
161 gd
.addNumericField( "minimal_inlier_ratio :", minInlierRatio
, 2 );
162 gd
.addNumericField( "minimal_number_of_inliers :", minNumInliers
, 0 );
163 gd
.addChoice( "approximate_transformation :", Param
.modelStrings
, Param
.modelStrings
[ expectedModelIndex
] );
164 gd
.addCheckbox( "ignore constant background", rejectIdentity
);
165 gd
.addNumericField( "tolerance :", identityTolerance
, 2, 6, "px" );
166 gd
.addNumericField( "give_up_after :", maxNumFailures
, 0, 6, "failures" );
170 if ( gd
.wasCanceled() )
173 maxEpsilon
= ( float )gd
.getNextNumber();
174 minInlierRatio
= ( float )gd
.getNextNumber();
175 minNumInliers
= ( int )gd
.getNextNumber();
176 expectedModelIndex
= gd
.getNextChoiceIndex();
177 rejectIdentity
= gd
.getNextBoolean();
178 identityTolerance
= ( float )gd
.getNextNumber();
179 maxNumFailures
= ( int )gd
.getNextNumber();
184 final GenericDialog gdOptimize
= new GenericDialog( "Elastically align layers: Optimization" );
186 gdOptimize
.addMessage( "Approximate Optimizer:" );
187 gdOptimize
.addChoice( "approximate_transformation :", Param
.modelStrings
, Param
.modelStrings
[ desiredModelIndex
] );
188 gdOptimize
.addNumericField( "maximal_iterations :", maxIterationsOptimize
, 0 );
189 gdOptimize
.addNumericField( "maximal_plateauwidth :", maxPlateauwidthOptimize
, 0 );
191 gdOptimize
.addMessage( "Spring Mesh:" );
192 gdOptimize
.addNumericField( "stiffness :", stiffnessSpringMesh
, 2 );
193 gdOptimize
.addNumericField( "maximal_stretch :", maxStretchSpringMesh
, 2, 6, "px" );
194 gdOptimize
.addNumericField( "maximal_iterations :", maxIterationsSpringMesh
, 0 );
195 gdOptimize
.addNumericField( "maximal_plateauwidth :", maxPlateauwidthSpringMesh
, 0 );
197 gdOptimize
.showDialog();
199 if ( gdOptimize
.wasCanceled() )
202 desiredModelIndex
= gdOptimize
.getNextChoiceIndex();
203 maxIterationsOptimize
= ( int )gdOptimize
.getNextNumber();
204 maxPlateauwidthOptimize
= ( int )gdOptimize
.getNextNumber();
206 stiffnessSpringMesh
= ( float )gdOptimize
.getNextNumber();
207 maxStretchSpringMesh
= ( float )gdOptimize
.getNextNumber();
208 maxIterationsSpringMesh
= ( int )gdOptimize
.getNextNumber();
209 maxPlateauwidthSpringMesh
= ( int )gdOptimize
.getNextNumber();
217 final int SIFTfdBins
,
218 final int SIFTfdSize
,
219 final float SIFTinitialSigma
,
220 final int SIFTmaxOctaveSize
,
221 final int SIFTminOctaveSize
,
224 final boolean clearCache
,
225 final int maxNumThreadsSift
,
228 final int desiredModelIndex
,
229 final int expectedModelIndex
,
230 final float identityTolerance
,
231 final boolean isAligned
,
232 final float maxEpsilon
,
233 final int maxIterationsOptimize
,
234 final int maxNumFailures
,
235 final int maxNumNeighbors
,
236 final int maxNumThreads
,
237 final int maxPlateauwidthOptimize
,
238 final float minInlierRatio
,
239 final int minNumInliers
,
240 final boolean multipleHypotheses
,
241 final boolean rejectIdentity
,
242 final boolean visualize
,
244 final int blockRadius
,
245 final float dampSpringMesh
,
246 final float layerScale
,
247 final int localModelIndex
,
248 final float localRegionSigma
,
249 final float maxCurvatureR
,
250 final int maxIterationsSpringMesh
,
251 final float maxLocalEpsilon
,
252 final float maxLocalTrust
,
253 final int maxPlateauwidthSpringMesh
,
254 final float maxStretchSpringMesh
,
256 final int resolutionSpringMesh
,
258 final int searchRadius
,
259 final float stiffnessSpringMesh
,
260 final boolean useLocalSmoothnessFilter
)
276 maxIterationsOptimize
,
280 maxPlateauwidthOptimize
,
287 this.isAligned
= isAligned
;
288 this.blockRadius
= blockRadius
;
289 this.dampSpringMesh
= dampSpringMesh
;
290 this.layerScale
= layerScale
;
291 this.localModelIndex
= localModelIndex
;
292 this.localRegionSigma
= localRegionSigma
;
293 this.maxCurvatureR
= maxCurvatureR
;
294 this.maxIterationsSpringMesh
= maxIterationsSpringMesh
;
295 this.maxLocalEpsilon
= maxLocalEpsilon
;
296 this.maxLocalTrust
= maxLocalTrust
;
297 this.maxPlateauwidthSpringMesh
= maxPlateauwidthSpringMesh
;
298 this.maxStretchSpringMesh
= maxStretchSpringMesh
;
300 this.resolutionSpringMesh
= resolutionSpringMesh
;
302 this.searchRadius
= searchRadius
;
303 this.stiffnessSpringMesh
= stiffnessSpringMesh
;
304 this.useLocalSmoothnessFilter
= useLocalSmoothnessFilter
;
313 ppm
.sift
.initialSigma
,
314 ppm
.sift
.maxOctaveSize
,
315 ppm
.sift
.minOctaveSize
,
319 ppm
.maxNumThreadsSift
,
327 maxIterationsOptimize
,
331 maxPlateauwidthOptimize
,
344 maxIterationsSpringMesh
,
347 maxPlateauwidthSpringMesh
,
348 maxStretchSpringMesh
,
350 resolutionSpringMesh
,
354 useLocalSmoothnessFilter
);
358 final static Param p
= new Param();
361 final static private String
layerName( final Layer layer
)
363 return new StringBuffer( "layer z=" )
364 .append( String
.format( "%.3f", layer
.getZ() ) )
366 .append( layer
.getTitle() )
380 * @param propagateTransform
385 final public void exec(
387 final Project project
,
388 final List
< Layer
> layerRange
,
389 final Set
< Layer
> fixedLayers
,
390 final Set
< Layer
> emptyLayers
,
392 final boolean propagateTransformBefore
,
393 final boolean propagateTransformAfter
,
394 final Filter
< Patch
> filter
) throws Exception
396 final double scale
= Math
.min( 1.0, Math
.min( ( double )param
.ppm
.sift
.maxOctaveSize
/ ( double )box
.width
, ( double )param
.ppm
.sift
.maxOctaveSize
/ ( double )box
.height
) );
399 /* create tiles and models for all layers */
400 final ArrayList
< Tile
< ?
> > tiles
= new ArrayList
< Tile
< ?
> >();
401 for ( int i
= 0; i
< layerRange
.size(); ++i
)
403 switch ( param
.desiredModelIndex
)
406 tiles
.add( new Tile
< TranslationModel2D
>( new TranslationModel2D() ) );
409 tiles
.add( new Tile
< RigidModel2D
>( new RigidModel2D() ) );
412 tiles
.add( new Tile
< SimilarityModel2D
>( new SimilarityModel2D() ) );
415 tiles
.add( new Tile
< AffineModel2D
>( new AffineModel2D() ) );
418 tiles
.add( new Tile
< HomographyModel2D
>( new HomographyModel2D() ) );
425 /* collect all pairs of slices for which a model could be found */
426 final ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > > pairs
= new ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > >();
429 if ( !param
.isAligned
)
432 /* extract and save features, overwrite cached files if requested */
435 AlignmentUtils
.extractAndSaveLayerFeatures( layerRange
, box
, scale
, filter
, param
.ppm
.sift
, param
.ppm
.clearCache
, param
.ppm
.maxNumThreadsSift
);
437 catch ( final Exception e
)
442 /* match and filter feature correspondences */
445 final double pointMatchScale
= param
.layerScale
/ scale
;
447 for ( int i
= 0; i
< layerRange
.size(); ++i
)
449 final ArrayList
< Thread
> threads
= new ArrayList
< Thread
>( param
.maxNumThreads
);
451 final int sliceA
= i
;
452 final Layer layerA
= layerRange
.get( i
);
453 final int range
= Math
.min( layerRange
.size(), i
+ param
.maxNumNeighbors
+ 1 );
455 final String layerNameA
= layerName( layerA
);
457 J
: for ( int j
= i
+ 1; j
< range
; )
459 final int numThreads
= Math
.min( param
.maxNumThreads
, range
- j
);
460 final ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > > models
=
461 new ArrayList
< Triple
< Integer
, Integer
, AbstractModel
< ?
> > >( numThreads
);
463 for ( int k
= 0; k
< numThreads
; ++k
)
466 for ( int t
= 0; t
< numThreads
&& j
< range
; ++t
, ++j
)
469 final int sliceB
= j
;
470 final Layer layerB
= layerRange
.get( j
);
472 final String layerNameB
= layerName( layerB
);
474 final Thread thread
= new Thread()
479 IJ
.showProgress( sliceA
, layerRange
.size() - 1 );
481 Utils
.log( "matching " + layerNameB
+ " -> " + layerNameA
+ "..." );
483 ArrayList
< PointMatch
> candidates
= null;
484 if ( !param
.ppm
.clearCache
)
485 candidates
= mpicbg
.trakem2
.align
.Util
.deserializePointMatches(
486 project
, param
.ppm
, "layer", layerB
.getId(), layerA
.getId() );
488 if ( null == candidates
)
490 final ArrayList
< Feature
> fs1
= mpicbg
.trakem2
.align
.Util
.deserializeFeatures(
491 project
, param
.ppm
.sift
, "layer", layerA
.getId() );
492 final ArrayList
< Feature
> fs2
= mpicbg
.trakem2
.align
.Util
.deserializeFeatures(
493 project
, param
.ppm
.sift
, "layer", layerB
.getId() );
494 candidates
= new ArrayList
< PointMatch
>( FloatArray2DSIFT
.createMatches( fs2
, fs1
, param
.ppm
.rod
) );
496 /* scale the candidates */
497 for ( final PointMatch pm
: candidates
)
499 final Point p1
= pm
.getP1();
500 final Point p2
= pm
.getP2();
501 final float[] l1
= p1
.getL();
502 final float[] w1
= p1
.getW();
503 final float[] l2
= p2
.getL();
504 final float[] w2
= p2
.getW();
506 l1
[ 0 ] *= pointMatchScale
;
507 l1
[ 1 ] *= pointMatchScale
;
508 w1
[ 0 ] *= pointMatchScale
;
509 w1
[ 1 ] *= pointMatchScale
;
510 l2
[ 0 ] *= pointMatchScale
;
511 l2
[ 1 ] *= pointMatchScale
;
512 w2
[ 0 ] *= pointMatchScale
;
513 w2
[ 1 ] *= pointMatchScale
;
517 if ( !mpicbg
.trakem2
.align
.Util
.serializePointMatches(
518 project
, param
.ppm
, "layer", layerB
.getId(), layerA
.getId(), candidates
) )
519 Utils
.log( "Could not store point match candidates for layers " + layerNameB
+ " and " + layerNameA
+ "." );
522 AbstractModel
< ?
> model
;
523 switch ( param
.expectedModelIndex
)
526 model
= new TranslationModel2D();
529 model
= new RigidModel2D();
532 model
= new SimilarityModel2D();
535 model
= new AffineModel2D();
538 model
= new HomographyModel2D();
544 final ArrayList
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
547 boolean again
= false;
553 modelFound
= model
.filterRansac(
557 param
.maxEpsilon
* param
.layerScale
,
558 param
.minInlierRatio
,
561 if ( modelFound
&& param
.rejectIdentity
)
563 final ArrayList
< Point
> points
= new ArrayList
< Point
>();
564 PointMatch
.sourcePoints( inliers
, points
);
565 if ( Transforms
.isIdentity( model
, points
, param
.identityTolerance
* param
.layerScale
) )
567 IJ
.log( "Identity transform for " + inliers
.size() + " matches rejected." );
568 candidates
.removeAll( inliers
);
576 catch ( final NotEnoughDataPointsException e
)
583 Utils
.log( layerNameB
+ " -> " + layerNameA
+ ": " + inliers
.size() + " corresponding features with an average displacement of " + ( PointMatch
.meanDistance( inliers
) / param
.layerScale
) + "px identified." );
584 Utils
.log( "Estimated transformation model: " + model
);
585 models
.set( ti
, new Triple
< Integer
, Integer
, AbstractModel
< ?
> >( sliceA
, sliceB
, model
) );
589 Utils
.log( layerNameB
+ " -> " + layerNameA
+ ": no correspondences found." );
594 threads
.add( thread
);
600 for ( final Thread thread
: threads
)
603 catch ( final InterruptedException e
)
605 Utils
.log( "Establishing feature correspondences interrupted." );
606 for ( final Thread thread
: threads
)
610 for ( final Thread thread
: threads
)
613 catch ( final InterruptedException f
) {}
619 /* collect successfully matches pairs and break the search on gaps */
620 for ( int t
= 0; t
< models
.size(); ++t
)
622 final Triple
< Integer
, Integer
, AbstractModel
< ?
> > pair
= models
.get( t
);
625 if ( ++numFailures
> param
.maxNumFailures
)
639 for ( int i
= 0; i
< layerRange
.size(); ++i
)
641 final int range
= Math
.min( layerRange
.size(), i
+ param
.maxNumNeighbors
+ 1 );
643 for ( int j
= i
+ 1; j
< range
; ++j
)
645 pairs
.add( new Triple
< Integer
, Integer
, AbstractModel
< ?
> >( i
, j
, new TranslationModel2D() ) );
650 /* Elastic alignment */
653 final TileConfiguration initMeshes
= new TileConfiguration();
655 final int meshWidth
= ( int )Math
.ceil( box
.width
* param
.layerScale
);
656 final int meshHeight
= ( int )Math
.ceil( box
.height
* param
.layerScale
);
658 final ArrayList
< SpringMesh
> meshes
= new ArrayList
< SpringMesh
>( layerRange
.size() );
659 for ( int i
= 0; i
< layerRange
.size(); ++i
)
662 param
.resolutionSpringMesh
,
665 param
.stiffnessSpringMesh
,
666 param
.maxStretchSpringMesh
* param
.layerScale
,
667 param
.dampSpringMesh
) );
669 //final int blockRadius = Math.max( 32, meshWidth / p.resolutionSpringMesh / 2 );
670 final int blockRadius
= Math
.max( 16, mpicbg
.util
.Util
.roundPos( param
.layerScale
* param
.blockRadius
) );
672 Utils
.log( "effective block radius = " + blockRadius
);
674 /* scale pixel distances */
675 final int searchRadius
= ( int )Math
.round( param
.layerScale
* param
.searchRadius
);
676 final float localRegionSigma
= param
.layerScale
* param
.localRegionSigma
;
677 final float maxLocalEpsilon
= param
.layerScale
* param
.maxLocalEpsilon
;
679 final AbstractModel
< ?
> localSmoothnessFilterModel
= Util
.createModel( param
.localModelIndex
);
682 for ( final Triple
< Integer
, Integer
, AbstractModel
< ?
> > pair
: pairs
)
685 project
.getLoader().releaseAll();
687 final SpringMesh m1
= meshes
.get( pair
.a
);
688 final SpringMesh m2
= meshes
.get( pair
.b
);
690 final ArrayList
< PointMatch
> pm12
= new ArrayList
< PointMatch
>();
691 final ArrayList
< PointMatch
> pm21
= new ArrayList
< PointMatch
>();
693 final Collection
< Vertex
> v1
= m1
.getVertices();
694 final Collection
< Vertex
> v2
= m2
.getVertices();
696 final Layer layer1
= layerRange
.get( pair
.a
);
697 final Layer layer2
= layerRange
.get( pair
.b
);
699 final boolean layer1Fixed
= fixedLayers
.contains( layer1
);
700 final boolean layer2Fixed
= fixedLayers
.contains( layer2
);
702 final Tile
< ?
> t1
= tiles
.get( pair
.a
);
703 final Tile
< ?
> t2
= tiles
.get( pair
.b
);
705 if ( !( layer1Fixed
&& layer2Fixed
) )
707 final Image img1
= project
.getLoader().getFlatAWTImage(
714 AlignmentUtils
.filterPatches( layer1
, filter
),
716 new Color( 0x00ffffff, true ) );
718 final Image img2
= project
.getLoader().getFlatAWTImage(
725 AlignmentUtils
.filterPatches( layer2
, filter
),
727 new Color( 0x00ffffff, true ) );
729 final int width
= img1
.getWidth( null );
730 final int height
= img1
.getHeight( null );
732 final FloatProcessor ip1
= new FloatProcessor( width
, height
);
733 final FloatProcessor ip2
= new FloatProcessor( width
, height
);
734 final FloatProcessor ip1Mask
= new FloatProcessor( width
, height
);
735 final FloatProcessor ip2Mask
= new FloatProcessor( width
, height
);
737 mpicbg
.trakem2
.align
.Util
.imageToFloatAndMask( img1
, ip1
, ip1Mask
);
738 mpicbg
.trakem2
.align
.Util
.imageToFloatAndMask( img2
, ip2
, ip2Mask
);
740 final float springConstant
= 1.0f
/ ( pair
.b
- pair
.a
);
743 initMeshes
.fixTile( t1
);
748 BlockMatching
.matchByMaximalPMCC(
754 ( ( InvertibleCoordinateTransform
)pair
.c
).createInverse(),
764 new ErrorStatistic( 1 ) );
766 catch ( final InterruptedException e
)
768 Utils
.log( "Block matching interrupted." );
769 IJ
.showProgress( 1.0 );
772 if ( Thread
.interrupted() )
774 Utils
.log( "Block matching interrupted." );
775 IJ
.showProgress( 1.0 );
779 if ( param
.useLocalSmoothnessFilter
)
781 Utils
.log( pair
.a
+ " > " + pair
.b
+ ": found " + pm12
.size() + " correspondence candidates." );
782 localSmoothnessFilterModel
.localSmoothnessFilter( pm12
, pm12
, localRegionSigma
, maxLocalEpsilon
, param
.maxLocalTrust
);
783 Utils
.log( pair
.a
+ " > " + pair
.b
+ ": " + pm12
.size() + " candidates passed local smoothness filter." );
787 Utils
.log( pair
.a
+ " > " + pair
.b
+ ": found " + pm12
.size() + " correspondences." );
790 /* <visualisation> */
791 // final List< Point > s1 = new ArrayList< Point >();
792 // PointMatch.sourcePoints( pm12, s1 );
793 // final ImagePlus imp1 = new ImagePlus( i + " >", ip1 );
795 // imp1.setOverlay( BlockMatching.illustrateMatches( pm12 ), Color.yellow, null );
796 // imp1.setRoi( Util.pointsToPointRoi( s1 ) );
797 // imp1.updateAndDraw();
798 /* </visualisation> */
800 for ( final PointMatch pm
: pm12
)
802 final Vertex p1
= ( Vertex
)pm
.getP1();
803 final Vertex p2
= new Vertex( pm
.getP2() );
804 p1
.addSpring( p2
, new Spring( 0, springConstant
) );
805 m2
.addPassiveVertex( p2
);
809 * adding Tiles to the initialing TileConfiguration, adding a Tile
810 * multiple times does not harm because the TileConfiguration is
813 if ( pm12
.size() > pair
.c
.getMinNumMatches() )
815 initMeshes
.addTile( t1
);
816 initMeshes
.addTile( t2
);
817 t1
.connect( t2
, pm12
);
822 initMeshes
.fixTile( t2
);
827 BlockMatching
.matchByMaximalPMCC(
843 new ErrorStatistic( 1 ) );
845 catch ( final InterruptedException e
)
847 Utils
.log( "Block matching interrupted." );
848 IJ
.showProgress( 1.0 );
851 if ( Thread
.interrupted() )
853 Utils
.log( "Block matching interrupted." );
854 IJ
.showProgress( 1.0 );
858 if ( param
.useLocalSmoothnessFilter
)
860 Utils
.log( pair
.a
+ " < " + pair
.b
+ ": found " + pm21
.size() + " correspondence candidates." );
861 localSmoothnessFilterModel
.localSmoothnessFilter( pm21
, pm21
, localRegionSigma
, maxLocalEpsilon
, param
.maxLocalTrust
);
862 Utils
.log( pair
.a
+ " < " + pair
.b
+ ": " + pm21
.size() + " candidates passed local smoothness filter." );
866 Utils
.log( pair
.a
+ " < " + pair
.b
+ ": found " + pm21
.size() + " correspondences." );
869 /* <visualisation> */
870 // final List< Point > s2 = new ArrayList< Point >();
871 // PointMatch.sourcePoints( pm21, s2 );
872 // final ImagePlus imp2 = new ImagePlus( i + " <", ip2 );
874 // imp2.setOverlay( BlockMatching.illustrateMatches( pm21 ), Color.yellow, null );
875 // imp2.setRoi( Util.pointsToPointRoi( s2 ) );
876 // imp2.updateAndDraw();
877 /* </visualisation> */
879 for ( final PointMatch pm
: pm21
)
881 final Vertex p1
= ( Vertex
)pm
.getP1();
882 final Vertex p2
= new Vertex( pm
.getP2() );
883 p1
.addSpring( p2
, new Spring( 0, springConstant
) );
884 m1
.addPassiveVertex( p2
);
888 * adding Tiles to the initialing TileConfiguration, adding a Tile
889 * multiple times does not harm because the TileConfiguration is
892 if ( pm21
.size() > pair
.c
.getMinNumMatches() )
894 initMeshes
.addTile( t1
);
895 initMeshes
.addTile( t2
);
896 t2
.connect( t1
, pm21
);
900 Utils
.log( pair
.a
+ " <> " + pair
.b
+ " spring constant = " + springConstant
);
904 /* pre-align by optimizing a piecewise linear model */
906 param
.maxEpsilon
* param
.layerScale
,
907 param
.maxIterationsSpringMesh
,
908 param
.maxPlateauwidthSpringMesh
);
909 for ( int i
= 0; i
< layerRange
.size(); ++i
)
910 meshes
.get( i
).init( tiles
.get( i
).getModel() );
912 /* optimize the meshes */
915 final long t0
= System
.currentTimeMillis();
916 Utils
.log("Optimizing spring meshes...");
918 SpringMesh
.optimizeMeshes(
920 param
.maxEpsilon
* param
.layerScale
,
921 param
.maxIterationsSpringMesh
,
922 param
.maxPlateauwidthSpringMesh
,
925 Utils
.log("Done optimizing spring meshes. Took " + (System
.currentTimeMillis() - t0
) + " ms");
928 catch ( final NotEnoughDataPointsException e
)
930 Utils
.log( "There were not enough data points to get the spring mesh optimizing." );
935 /* translate relative to bounding box */
936 for ( final SpringMesh mesh
: meshes
)
938 for ( final PointMatch pm
: mesh
.getVA().keySet() )
940 final Point p1
= pm
.getP1();
941 final Point p2
= pm
.getP2();
942 final float[] l
= p1
.getL();
943 final float[] w
= p2
.getW();
944 l
[ 0 ] = l
[ 0 ] / param
.layerScale
+ box
.x
;
945 l
[ 1 ] = l
[ 1 ] / param
.layerScale
+ box
.y
;
946 w
[ 0 ] = w
[ 0 ] / param
.layerScale
+ box
.x
;
947 w
[ 1 ] = w
[ 1 ] / param
.layerScale
+ box
.y
;
952 project
.getLoader().releaseAll();
954 final Layer first
= layerRange
.get( 0 );
955 final List
< Layer
> layers
= first
.getParent().getLayers();
957 /* transfer layer transform into patch transforms and append to patches */
958 if ( propagateTransformBefore
|| propagateTransformAfter
)
960 if ( propagateTransformBefore
)
962 final MovingLeastSquaresTransform2 mlt
= makeMLST2( meshes
.get( 0 ).getVA().keySet() );
963 final int firstLayerIndex
= first
.getParent().getLayerIndex( first
.getId() );
964 for ( int i
= 0; i
< firstLayerIndex
; ++i
)
965 applyTransformToLayer( layers
.get( i
), mlt
, filter
);
967 if ( propagateTransformAfter
)
969 final Layer last
= layerRange
.get( layerRange
.size() - 1 );
970 final MovingLeastSquaresTransform2 mlt
= makeMLST2( meshes
.get( meshes
.size() - 1 ).getVA().keySet() );
971 final int lastLayerIndex
= last
.getParent().getLayerIndex( last
.getId() );
972 for ( int i
= lastLayerIndex
+ 1; i
< layers
.size(); ++i
)
973 applyTransformToLayer( layers
.get( i
), mlt
, filter
);
976 for ( int l
= 0; l
< layerRange
.size(); ++l
)
978 IJ
.showStatus( "Applying transformation to patches ..." );
979 IJ
.showProgress( 0, layerRange
.size() );
981 final Layer layer
= layerRange
.get( l
);
983 final MovingLeastSquaresTransform2 mlt
= new MovingLeastSquaresTransform2();
984 mlt
.setModel( AffineModel2D
.class );
985 mlt
.setAlpha( 2.0f
);
986 mlt
.setMatches( meshes
.get( l
).getVA().keySet() );
988 applyTransformToLayer( layer
, mlt
, filter
);
990 if ( Thread
.interrupted() )
992 Utils
.log( "Interrupted during applying transformations to patches. No all patches have been updated. Re-generate mipmaps manually." );
995 IJ
.showProgress( l
+ 1, layerRange
.size() );
998 /* update patch mipmaps */
999 final int firstLayerIndex
;
1000 final int lastLayerIndex
;
1002 if ( propagateTransformBefore
)
1003 firstLayerIndex
= 0;
1006 firstLayerIndex
= first
.getParent().getLayerIndex( first
.getId() );
1008 if ( propagateTransformAfter
)
1009 lastLayerIndex
= layers
.size() - 1;
1012 final Layer last
= layerRange
.get( layerRange
.size() - 1 );
1013 lastLayerIndex
= last
.getParent().getLayerIndex( last
.getId() );
1016 for ( int i
= firstLayerIndex
; i
<= lastLayerIndex
; ++i
)
1018 final Layer layer
= layers
.get( i
);
1019 if ( !( emptyLayers
.contains( layer
) || fixedLayers
.contains( layer
) ) )
1021 for ( final Patch patch
: AlignmentUtils
.filterPatches( layer
, filter
) )
1022 patch
.updateMipMaps();
1026 Utils
.log( "Done." );
1029 final static protected MovingLeastSquaresTransform2
makeMLST2( final Set
< PointMatch
> matches
) throws Exception
1031 final MovingLeastSquaresTransform2 mlt
= new MovingLeastSquaresTransform2();
1032 mlt
.setModel( AffineModel2D
.class );
1033 mlt
.setAlpha( 2.0f
);
1034 mlt
.setMatches( matches
);
1038 final static protected void applyTransformToLayer( final Layer layer
, final MovingLeastSquaresTransform2 mlt
, final Filter
< Patch
> filter
) throws InterruptedException
1041 * Setting a transformation to a patch can take some time because
1042 * the new bounding box needs to be estimated which requires the
1043 * TransformMesh to be generated and all vertices iterated.
1045 * Therefore multithreading.
1047 final List
< Patch
> patches
= AlignmentUtils
.filterPatches( layer
, filter
);
1049 final ArrayList
< Thread
> applyThreads
= new ArrayList
< Thread
>( p
.maxNumThreads
);
1050 final AtomicInteger ai
= new AtomicInteger( 0 );
1051 for ( int t
= 0; t
< p
.maxNumThreads
; ++t
)
1053 final Thread thread
= new Thread(
1057 final public void run()
1061 for ( int i
= ai
.getAndIncrement(); i
< patches
.size() && !Thread
.interrupted(); i
= ai
.getAndIncrement() )
1062 mpicbg
.trakem2
.align
.Util
.applyLayerTransformToPatch( patches
.get( i
), mlt
.copy() );
1064 catch ( final Exception e
)
1066 e
.printStackTrace();
1070 applyThreads
.add( thread
);
1074 for ( final Thread thread
: applyThreads
)
1080 * Stateful. Changing the parameters of this instance. Do not use in parallel.
1083 * @param fixedLayers
1084 * @param propagateTransformBefore
1085 * @param propagateTransformAfter
1090 final public void exec(
1091 final Project project
,
1092 final List
< Layer
> layerRange
,
1093 final Set
< Layer
> fixedLayers
,
1094 final boolean propagateTransformBefore
,
1095 final boolean propagateTransformAfter
,
1096 final Rectangle fov
,
1097 final Filter
< Patch
> filter
) throws Exception
1099 Rectangle box
= null;
1100 final HashSet
< Layer
> emptyLayers
= new HashSet
< Layer
>();
1101 for ( final Iterator
< Layer
> it
= layerRange
.iterator(); it
.hasNext(); )
1103 /* remove empty layers */
1104 final Layer la
= it
.next();
1105 if ( !la
.contains( Patch
.class, true ) )
1107 emptyLayers
.add( la
);
1112 /* accumulate boxes */
1113 if ( null == box
) // The first layer:
1114 box
= la
.getMinimalBoundingBox( Patch
.class, true );
1116 box
= box
.union( la
.getMinimalBoundingBox( Patch
.class, true ) );
1121 box
= new Rectangle();
1124 box
= box
.intersection( fov
);
1126 if ( box
.width
<= 0 || box
.height
<= 0 )
1128 Utils
.log( "Bounding box empty." );
1132 if ( layerRange
.size() == emptyLayers
.size() )
1134 Utils
.log( "All layers in range are empty!" );
1138 /* do not work if there is only one layer selected */
1139 if ( layerRange
.size() - emptyLayers
.size() < 2 )
1141 Utils
.log( "All except one layer in range are empty!" );
1145 if ( !p
.setup( box
) ) return;
1147 exec( p
.clone(), project
, layerRange
, fixedLayers
, emptyLayers
, box
, propagateTransformBefore
, propagateTransformAfter
, filter
);
1152 * Stateful. Changing the parameters of this instance. Do not use in parallel.
1157 * @param propagateTransformBefore
1158 * @param propagateTransformAfter
1162 final public void exec(
1163 final LayerSet layerSet
,
1167 final boolean propagateTransformBefore
,
1168 final boolean propagateTransformAfter
,
1169 final Rectangle fov
,
1170 final Filter
< Patch
> filter
) throws Exception
1172 final int first
= Math
.min( firstIn
, lastIn
);
1173 final int last
= Math
.max( firstIn
, lastIn
);
1175 /* always first index first despite the method would return inverse order if last > first */
1176 final List
< Layer
> layerRange
= layerSet
.getLayers( first
, last
);
1177 final HashSet
< Layer
> fixedLayers
= new HashSet
< Layer
>();
1179 if ( ref
- firstIn
>= 0 )
1180 fixedLayers
.add( layerRange
.get( ref
- firstIn
) );
1182 Utils
.log( layerRange
.size() + "" );
1184 exec( layerSet
.getProject(), layerRange
, fixedLayers
, propagateTransformBefore
, propagateTransformAfter
, fov
, filter
);
1189 * Stateful. Changing the parameters of this instance. Do not use in parallel.
1195 * @param propagateTransform
1199 final public void exec(
1200 final LayerSet layerSet
,
1204 final boolean propagateTransform
,
1205 final Rectangle fov
,
1206 final Filter
< Patch
> filter
) throws Exception
1208 if ( firstIn
< lastIn
)
1209 exec( layerSet
, firstIn
, lastIn
, ref
, false, propagateTransform
, fov
, filter
);
1211 exec( layerSet
, firstIn
, lastIn
, ref
, propagateTransform
, false, fov
, filter
);
1215 * Stateful. Changing the parameters of this instance. Do not use in parallel.
1222 * @param propagateTransformAfter
1226 final public void exec(
1227 final LayerSet layerSet
,
1232 final boolean propagateTransformBefore
,
1233 final boolean propagateTransformAfter
,
1234 final Rectangle fov
,
1235 final Filter
< Patch
> filter
) throws Exception
1237 final int first
= Math
.min( firstIn
, lastIn
);
1238 final int last
= Math
.max( firstIn
, lastIn
);
1240 /* always first index first despite the method would return inverse order if last > first */
1241 final List
< Layer
> layerRange
= layerSet
.getLayers( first
, last
);
1242 final HashSet
< Layer
> fixedLayers
= new HashSet
< Layer
>();
1244 if ( ref1
- first
>= 0 )
1245 fixedLayers
.add( layerRange
.get( ref1
- first
) );
1246 if ( ref2
- first
>= 0 )
1247 fixedLayers
.add( layerRange
.get( ref2
- first
) );
1249 Utils
.log( layerRange
.size() + "" );
1251 exec( layerSet
.getProject(), layerRange
, fixedLayers
, propagateTransformBefore
, propagateTransformAfter
, fov
, filter
);