Multi-layer mosaicking with SIFT using the proper new mpicbg_.jar is almost
[trakem2.git] / mpicbg / trakem2 / align / AlignTask.java
blob3472a177d117b38b4b108fc75d290274ea0773a7
1 /**
2 *
3 */
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;
11 import java.util.Set;
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;
26 import ij.IJ;
27 import ij.ImagePlus;
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;
39 /**
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) {
53 public void run() {
54 startedWorking();
55 try {
56 alignSelection( selection );
57 Display.repaint(selection.getLayer());
58 } catch (Throwable e) {
59 IJError.print(e);
60 } finally {
61 finishedWorking();
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.");
82 return;
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 );
95 gd.showDialog();
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 );
126 else
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 );
158 else
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 )
169 // {
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 ] );
174 // }
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) {
189 public void run() {
190 startedWorking();
191 try {
192 alignLayersLinearly(l);
193 } catch (Throwable e) {
194 IJError.print(e);
195 } finally {
196 finishedWorking();
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 );
223 gd.showDialog();
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();
254 int s = 0;
255 for ( final Layer layer : layerRange )
257 if ( Thread.currentThread().isInterrupted() ) break;
259 long t = System.currentTimeMillis();
261 features1.clear();
262 features1.addAll( features2 );
263 features2.clear();
265 final Rectangle box3 = layer.getMinimalBoundingBox( Patch.class );
267 if ( box3 == null || ( box.width == 0 && box.height == 0 ) ) continue;
269 box1 = box2;
270 box2 = box3;
272 ijSIFT.extractFeatures(
273 layer.getProject().getLoader().getFlatImage( layer, box2, scale, 0xffffffff, ImagePlus.GRAY8, Patch.class, true ).getProcessor(),
274 features2 );
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();
281 candidates.clear();
283 FeatureTransform.matchFeatures(
284 features2,
285 features1,
286 candidates,
287 p.rod );
289 final AbstractAffineModel2D< ? > model;
290 switch ( p.expectedModelIndex )
292 case 0:
293 model = new TranslationModel2D();
294 break;
295 case 1:
296 model = new RigidModel2D();
297 break;
298 case 2:
299 model = new SimilarityModel2D();
300 break;
301 case 3:
302 model = new AffineModel2D();
303 break;
304 default:
305 return;
308 boolean modelFound;
311 modelFound = model.filterRansac(
312 candidates,
313 inliers,
314 1000,
315 p.maxEpsilon,
316 p.minInlierRatio,
317 3 * model.getMinNumMatches(),
318 3 );
320 catch ( NotEnoughDataPointsException e )
322 modelFound = false;
325 if ( modelFound )
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);
335 a.concatenate( b );
336 layer.apply( Displayable.class, a );
337 Display.repaint( layer );
339 else
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 );
378 gd1.showDialog();
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 );
397 gd2.showDialog();
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 );
409 gd3.showDialog();
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;
420 /* register */
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();
465 /* optimize */
466 Align.optimizeTileConfiguration( pcp, allTiles, allFixedTiles );
468 for ( AbstractAffineTile2D< ? > t : allTiles )
469 t.getPatch().setAffineTransform( t.getModel().createAffine() );
472 l.getParent().setMinimumDimensions();
475 return;