4 package mpicbg
.trakem2
.align
;
6 import java
.awt
.Rectangle
;
7 import java
.awt
.geom
.AffineTransform
;
8 import java
.util
.ArrayList
;
9 import java
.util
.Collection
;
10 import java
.util
.List
;
13 import mpicbg
.ij
.FeatureTransform
;
14 import mpicbg
.ij
.SIFT
;
15 import mpicbg
.imagefeatures
.Feature
;
16 import mpicbg
.imagefeatures
.FloatArray2DSIFT
;
17 import mpicbg
.models
.AbstractAffineModel2D
;
18 import mpicbg
.models
.AffineModel2D
;
19 import mpicbg
.models
.NotEnoughDataPointsException
;
20 import mpicbg
.models
.PointMatch
;
21 import mpicbg
.models
.SimilarityModel2D
;
22 import mpicbg
.models
.Tile
;
23 import mpicbg
.trakem2
.transform
.RigidModel2D
;
24 import mpicbg
.trakem2
.transform
.TranslationModel2D
;
28 import ij
.gui
.GenericDialog
;
29 import ini
.trakem2
.display
.Display
;
30 import ini
.trakem2
.display
.Displayable
;
31 import ini
.trakem2
.display
.Layer
;
32 import ini
.trakem2
.display
.Patch
;
33 import ini
.trakem2
.display
.Selection
;
34 import ini
.trakem2
.utils
.Worker
;
35 import ini
.trakem2
.utils
.Bureaucrat
;
36 import ini
.trakem2
.utils
.IJError
;
37 import ini
.trakem2
.utils
.Utils
;
40 * Methods collection to be called from the GUI for alignment tasks.
43 final public class AlignTask
45 static protected boolean tilesAreInPlace
= false;
46 static protected boolean largestGraphOnly
= false;
47 static protected boolean hideDisconnectedTiles
= false;
48 static protected boolean deleteDisconnectedTiles
= false;
50 final static public Bureaucrat
alignSelectionTask ( final Selection selection
)
52 Worker worker
= new Worker("Aligning selected images", false, true) {
56 alignSelection( selection
);
57 Display
.repaint(selection
.getLayer());
58 } catch (Throwable e
) {
64 public void cleanup() {
65 if (!selection
.isEmpty())
66 selection
.getLayer().getParent().undoOneStep();
69 return Bureaucrat
.createAndStart( worker
, selection
.getProject() );
73 final static public void alignSelection( final Selection selection
)
75 List
< Patch
> patches
= new ArrayList
< Patch
>();
76 for ( Displayable d
: Display
.getFront().getSelection().getSelected() )
77 if ( d
instanceof Patch
) patches
.add( ( Patch
)d
);
79 if ( patches
.size() < 2 )
81 Utils
.log("No images to align in the selection.");
85 //final Align.ParamOptimize p = Align.paramOptimize;
86 final GenericDialog gd
= new GenericDialog( "Align Selected Tiles" );
87 Align
.paramOptimize
.addFields( gd
);
89 gd
.addMessage( "Miscellaneous:" );
90 gd
.addCheckbox( "tiles are rougly in place", false );
91 gd
.addCheckbox( "consider largest graph only", false );
92 gd
.addCheckbox( "hide tiles from non-largest graph", false );
93 gd
.addCheckbox( "delete tiles from non-largest graph", false );
96 if ( gd
.wasCanceled() ) return;
98 Align
.paramOptimize
.readFields( gd
);
99 tilesAreInPlace
= gd
.getNextBoolean();
100 largestGraphOnly
= gd
.getNextBoolean();
101 hideDisconnectedTiles
= gd
.getNextBoolean();
102 deleteDisconnectedTiles
= gd
.getNextBoolean();
104 final Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
105 List
< Patch
> fixedPatches
= new ArrayList
< Patch
>();
106 final Displayable active
= selection
.getActive();
107 if ( active
!= null && active
instanceof Patch
)
108 fixedPatches
.add( ( Patch
)active
);
110 List
< AbstractAffineTile2D
< ?
> > tiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
111 List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
112 Align
.tilesFromPatches( p
, patches
, fixedPatches
, tiles
, fixedTiles
);
114 alignTiles( p
, tiles
, fixedTiles
);
118 final static public void alignTiles(
119 final Align
.ParamOptimize p
,
120 final List
< AbstractAffineTile2D
< ?
> > tiles
,
121 final List
< AbstractAffineTile2D
< ?
> > fixedTiles
)
123 final List
< AbstractAffineTile2D
< ?
>[] > tilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
124 if ( tilesAreInPlace
)
125 AbstractAffineTile2D
.pairOverlappingTiles( tiles
, tilePairs
);
127 AbstractAffineTile2D
.pairTiles( tiles
, tilePairs
);
129 Align
.connectTilePairs( p
, tiles
, tilePairs
, Runtime
.getRuntime().availableProcessors() );
131 if ( Thread
.currentThread().isInterrupted() ) return;
133 List
< Set
< Tile
< ?
> > > graphs
= AbstractAffineTile2D
.identifyConnectedGraphs( tiles
);
135 final List
< AbstractAffineTile2D
< ?
> > interestingTiles
;
136 if ( largestGraphOnly
)
138 /* find largest graph. */
140 Set
< Tile
< ?
> > largestGraph
= null;
141 for ( Set
< Tile
< ?
> > graph
: graphs
)
142 if ( largestGraph
== null || largestGraph
.size() < graph
.size() )
143 largestGraph
= graph
;
145 interestingTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
146 for ( Tile
< ?
> t
: largestGraph
)
147 interestingTiles
.add( ( AbstractAffineTile2D
< ?
> )t
);
149 if ( hideDisconnectedTiles
)
150 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
151 if ( !interestingTiles
.contains( t
) )
152 t
.getPatch().setVisible( false );
153 if ( deleteDisconnectedTiles
)
154 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
155 if ( !interestingTiles
.contains( t
) )
156 t
.getPatch().remove( false );
160 interestingTiles
= tiles
;
163 * virtually interconnect disconnected intersecting graphs
165 * TODO Not yet tested---Do we need these virtual connections?
168 // if ( graphs.size() > 1 && tilesAreInPlace )
170 // for ( AbstractAffineTile2D< ? >[] tilePair : tilePairs )
171 // for ( Set< Tile< ? > > graph : graphs )
172 // if ( graph.contains( tilePair[ 0 ] ) && !graph.contains( tilePair[ 1 ] ) )
173 // tilePair[ 0 ].makeVirtualConnection( tilePair[ 1 ] );
177 if ( Thread
.currentThread().isInterrupted() ) return;
179 Align
.optimizeTileConfiguration( p
, interestingTiles
, fixedTiles
);
181 for ( AbstractAffineTile2D
< ?
> t
: tiles
)
182 t
.getPatch().setAffineTransform( t
.getModel().createAffine() );
186 final static public Bureaucrat
alignLayersLinearlyTask ( final Layer l
)
188 Worker worker
= new Worker("Aligning layers", false, true) {
192 alignLayersLinearly(l
);
193 } catch (Throwable e
) {
200 return Bureaucrat
.createAndStart(worker
, l
.getProject());
203 final static public void alignLayersLinearly( final Layer l
)
205 final List
< Layer
> layers
= l
.getParent().getLayers();
206 final String
[] layerTitles
= new String
[ layers
.size() ];
207 for ( int i
= 0; i
< layers
.size(); ++i
)
208 layerTitles
[ i
] = layers
.get( i
).getTitle();
210 //Param p = Align.param;
211 Align
.param
.sift
.maxOctaveSize
= 1600;
213 final GenericDialog gd
= new GenericDialog( "Align Layers Linearly" );
215 gd
.addMessage( "Layer Range:" );
216 gd
.addChoice( "first :", layerTitles
, l
.getTitle() );
217 gd
.addChoice( "last :", layerTitles
, l
.getTitle() );
218 Align
.param
.addFields( gd
);
220 gd
.addMessage( "Miscellaneous:" );
221 gd
.addCheckbox( "propagate after last transform", false );
224 if ( gd
.wasCanceled() ) return;
226 final int first
= gd
.getNextChoiceIndex();
227 final int last
= gd
.getNextChoiceIndex();
228 final int d
= first
< last ?
1 : -1;
230 Align
.param
.readFields( gd
);
231 final boolean propagateTransform
= gd
.getNextBoolean();
233 final Align
.Param p
= Align
.param
.clone();
234 final Rectangle box
= layers
.get( 0 ).getParent().getMinimalBoundingBox( Patch
.class );
235 final float scale
= Math
.min( 1.0f
, Math
.min( ( float )p
.sift
.maxOctaveSize
/ ( float )box
.width
, ( float )p
.sift
.maxOctaveSize
/ ( float )box
.height
) );
236 p
.maxEpsilon
*= scale
;
238 final List
< Layer
> layerRange
= new ArrayList
< Layer
>();
239 for ( int i
= first
; i
!= last
+ d
; i
+= d
)
240 layerRange
.add( layers
.get( i
) );
242 final FloatArray2DSIFT sift
= new FloatArray2DSIFT( p
.sift
);
243 final SIFT ijSIFT
= new SIFT( sift
);
245 Rectangle box1
= null;
246 Rectangle box2
= null;
247 final Collection
< Feature
> features1
= new ArrayList
< Feature
>();
248 final Collection
< Feature
> features2
= new ArrayList
< Feature
>();
249 List
< PointMatch
> candidates
= new ArrayList
< PointMatch
>();
250 List
< PointMatch
> inliers
= new ArrayList
< PointMatch
>();
252 AffineTransform a
= new AffineTransform();
255 for ( final Layer layer
: layerRange
)
257 if ( Thread
.currentThread().isInterrupted() ) break;
259 long t
= System
.currentTimeMillis();
262 features1
.addAll( features2
);
265 final Rectangle box3
= layer
.getMinimalBoundingBox( Patch
.class );
267 if ( box3
== null || ( box
.width
== 0 && box
.height
== 0 ) ) continue;
272 ijSIFT
.extractFeatures(
273 layer
.getProject().getLoader().getFlatImage( layer
, box2
, scale
, 0xffffffff, ImagePlus
.GRAY8
, Patch
.class, true ).getProcessor(),
275 IJ
.log( features2
.size() + " features extracted in layer \"" + layer
.getTitle() + "\" (took " + ( System
.currentTimeMillis() - t
) + " ms)." );
277 if ( features1
.size() > 0 )
279 t
= System
.currentTimeMillis();
283 FeatureTransform
.matchFeatures(
289 final AbstractAffineModel2D
< ?
> model
;
290 switch ( p
.expectedModelIndex
)
293 model
= new TranslationModel2D();
296 model
= new RigidModel2D();
299 model
= new SimilarityModel2D();
302 model
= new AffineModel2D();
311 modelFound
= model
.filterRansac(
317 3 * model
.getMinNumMatches(),
320 catch ( NotEnoughDataPointsException e
)
327 IJ
.log( "Model found for layer \"" + layer
.getTitle() + "\" and its predecessor:\n correspondences " + inliers
.size() + " of " + candidates
.size() + "\n average residual error " + ( model
.getCost() / scale
) + " px\n took " + ( System
.currentTimeMillis() - t
) + " ms" );
328 final AffineTransform b
= new AffineTransform();
329 b
.translate( box1
.x
, box1
.y
);
330 b
.scale( 1.0f
/ scale
, 1.0f
/ scale
);
331 b
.concatenate( model
.createAffine() );
332 b
.scale( scale
, scale
);
333 b
.translate( -box2
.x
, -box2
.y
);
336 layer
.apply( Displayable
.class, a
);
337 Display
.repaint( layer
);
340 IJ
.log( "No model found for layer \"" + layer
.getTitle() + "\" and its predecessor." );
342 IJ
.showProgress( ++s
, layerRange
.size() );
344 if ( propagateTransform
)
346 for ( int i
= last
+ d
; i
>= 0 && i
< layers
.size(); i
+= d
)
347 layers
.get( i
).apply( Displayable
.class, a
);
353 * Align a multi-layer mosaic.
355 * @param l the current layer
357 final public static void alignMultiLayerMosaic( final Layer l
)
359 /* layer range and misc */
361 final List
< Layer
> layers
= l
.getParent().getLayers();
362 final String
[] layerTitles
= new String
[ layers
.size() ];
363 for ( int i
= 0; i
< layers
.size(); ++i
)
364 layerTitles
[ i
] = layers
.get( i
).getTitle();
366 final GenericDialog gd1
= new GenericDialog( "Align Multi-Layer Mosaic : Layer Range" );
368 gd1
.addMessage( "Layer Range:" );
369 gd1
.addChoice( "first :", layerTitles
, l
.getTitle() );
370 gd1
.addChoice( "last :", layerTitles
, l
.getTitle() );
372 gd1
.addMessage( "Miscellaneous:" );
373 gd1
.addCheckbox( "tiles are rougly in place", false );
374 gd1
.addCheckbox( "consider largest graph only", false );
375 gd1
.addCheckbox( "hide tiles from non-largest graph", false );
376 gd1
.addCheckbox( "delete tiles from non-largest graph", false );
379 if ( gd1
.wasCanceled() ) return;
381 final int first
= gd1
.getNextChoiceIndex();
382 final int last
= gd1
.getNextChoiceIndex();
383 final int d
= first
< last ?
1 : -1;
385 tilesAreInPlace
= gd1
.getNextBoolean();
386 largestGraphOnly
= gd1
.getNextBoolean();
387 hideDisconnectedTiles
= gd1
.getNextBoolean();
388 deleteDisconnectedTiles
= gd1
.getNextBoolean();
391 /* intra-layer parameters */
393 final GenericDialog gd2
= new GenericDialog( "Align Multi-Layer Mosaic : Intra-Layer" );
395 Align
.paramOptimize
.addFields( gd2
);
398 if ( gd2
.wasCanceled() ) return;
400 Align
.paramOptimize
.readFields( gd2
);
403 /* cross-layer parameters */
405 final GenericDialog gd3
= new GenericDialog( "Align Multi-Layer Mosaic : Cross-Layer" );
407 Align
.param
.addFields( gd3
);
410 if ( gd3
.wasCanceled() ) return;
412 Align
.param
.readFields( gd3
);
414 Align
.ParamOptimize p
= Align
.paramOptimize
.clone();
415 Align
.Param cp
= Align
.param
.clone();
416 Align
.ParamOptimize pcp
= p
.clone();
417 pcp
.desiredModelIndex
= cp
.desiredModelIndex
;
422 final List
< Layer
> layerRange
= new ArrayList
< Layer
>();
423 for ( int i
= first
; i
!= last
+ d
; i
+= d
)
424 layerRange
.add( layers
.get( i
) );
426 final List
< AbstractAffineTile2D
< ?
> > allTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
427 final List
< AbstractAffineTile2D
< ?
> > allFixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
428 final List
< AbstractAffineTile2D
< ?
> > previousLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
430 List
< Patch
> fixedPatches
= new ArrayList
< Patch
>();
431 final Displayable active
= Display
.getFront().getActive();
432 if ( active
!= null && active
instanceof Patch
)
433 fixedPatches
.add( ( Patch
)active
);
435 for ( final Layer layer
: layerRange
)
437 /* align all tiles in the layer */
439 List
< Patch
> patches
= new ArrayList
< Patch
>();
440 for ( Displayable a
: layer
.getDisplayables( Patch
.class ) )
441 if ( a
instanceof Patch
) patches
.add( ( Patch
)a
);
442 List
< AbstractAffineTile2D
< ?
> > currentLayerTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
443 List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
444 Align
.tilesFromPatches( p
, patches
, fixedPatches
, currentLayerTiles
, fixedTiles
);
446 /* add a fixed tile only if there was a Patch selected */
447 allFixedTiles
.addAll( fixedTiles
);
449 alignTiles( p
, currentLayerTiles
, fixedTiles
);
451 /* connect to the previous layer */
453 final List
< AbstractAffineTile2D
< ?
>[] > crossLayerTilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
454 AbstractAffineTile2D
.pairTiles( previousLayerTiles
, currentLayerTiles
, crossLayerTilePairs
);
456 Align
.connectTilePairs( cp
, currentLayerTiles
, crossLayerTilePairs
, Runtime
.getRuntime().availableProcessors() );
458 /* prepare the next loop */
460 allTiles
.addAll( currentLayerTiles
);
461 previousLayerTiles
.clear();
462 previousLayerTiles
.addAll( currentLayerTiles
);
463 currentLayerTiles
.clear();
466 Align
.optimizeTileConfiguration( pcp
, allTiles
, allFixedTiles
);
468 for ( AbstractAffineTile2D
< ?
> t
: allTiles
)
469 t
.getPatch().setAffineTransform( t
.getModel().createAffine() );
472 l
.getParent().setMinimumDimensions();