4 package lenscorrection
;
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
.HashMap
;
11 import java
.util
.List
;
13 import java
.util
.concurrent
.atomic
.AtomicInteger
;
15 import lenscorrection
.Distortion_Correction
.BasicParam
;
16 import lenscorrection
.Distortion_Correction
.PointMatchCollectionAndAffine
;
17 import mpicbg
.models
.Point
;
18 import mpicbg
.models
.PointMatch
;
19 import mpicbg
.models
.Tile
;
20 import mpicbg
.trakem2
.align
.AbstractAffineTile2D
;
21 import mpicbg
.trakem2
.align
.Align
;
22 import mpicbg
.trakem2
.transform
.CoordinateTransform
;
25 import ij
.gui
.GenericDialog
;
26 import ini
.trakem2
.display
.Display
;
27 import ini
.trakem2
.display
.Displayable
;
28 import ini
.trakem2
.display
.Layer
;
29 import ini
.trakem2
.display
.Patch
;
30 import ini
.trakem2
.display
.Selection
;
31 import ini
.trakem2
.utils
.Worker
;
32 import ini
.trakem2
.utils
.Bureaucrat
;
33 import ini
.trakem2
.utils
.IJError
;
34 import ini
.trakem2
.utils
.Utils
;
37 * Methods collection to be called from the GUI for alignment tasks.
40 final public class DistortionCorrectionTask
42 static public class CorrectDistortionFromSelectionParam
extends BasicParam
44 public int firstLayerIndex
;
45 public int lastLayerIndex
;
46 public boolean clearTransform
= false;
47 public boolean visualize
= false;
48 public boolean tilesAreInPlace
= false;
50 public void addFields( final GenericDialog gd
, final Selection selection
)
54 gd
.addMessage( "Miscellaneous:" );
55 gd
.addCheckbox( "tiles are rougly in place", tilesAreInPlace
);
57 gd
.addMessage( "Apply Distortion Correction :" );
59 Utils
.addLayerRangeChoices( selection
.getLayer(), gd
);
60 gd
.addCheckbox( "clear_present_transforms", clearTransform
);
61 gd
.addCheckbox( "visualize_distortion_model", visualize
);
65 public boolean readFields( final GenericDialog gd
)
67 super.readFields( gd
);
68 tilesAreInPlace
= gd
.getNextBoolean();
69 firstLayerIndex
= gd
.getNextChoiceIndex();
70 lastLayerIndex
= gd
.getNextChoiceIndex();
71 clearTransform
= gd
.getNextBoolean();
72 visualize
= gd
.getNextBoolean();
73 return !gd
.invalidNumber();
76 public boolean setup( final Selection selection
)
78 final GenericDialog gd
= new GenericDialog( "Distortion Correction" );
79 addFields( gd
, selection
);
83 if ( gd
.wasCanceled() ) return false;
85 while ( !readFields( gd
) );
91 public CorrectDistortionFromSelectionParam
clone()
93 final CorrectDistortionFromSelectionParam p
= new CorrectDistortionFromSelectionParam();
95 p
.dimension
= dimension
;
96 p
.expectedModelIndex
= expectedModelIndex
;
98 p
.maxEpsilon
= maxEpsilon
;
99 p
.minInlierRatio
= minInlierRatio
;
101 p
.tilesAreInPlace
= tilesAreInPlace
;
102 p
.firstLayerIndex
= firstLayerIndex
;
103 p
.lastLayerIndex
= lastLayerIndex
;
104 p
.clearTransform
= clearTransform
;
105 p
.visualize
= visualize
;
113 * Sets a {@link CoordinateTransform} to a list of patches.
115 final static protected class SetCoordinateTransformThread
extends Thread
117 final protected List
< Patch
> patches
;
118 final protected CoordinateTransform transform
;
119 final protected AtomicInteger ai
;
121 public SetCoordinateTransformThread(
122 final List
< Patch
> patches
,
123 final CoordinateTransform transform
,
124 final AtomicInteger ai
)
126 this.patches
= patches
;
127 this.transform
= transform
;
132 final public void run()
134 for ( int i
= ai
.getAndIncrement(); i
< patches
.size() && !isInterrupted(); i
= ai
.getAndIncrement() )
136 final Patch patch
= patches
.get( i
);
137 // Utils.log( "Setting transform \"" + transform + "\" for patch \"" + patch.getTitle() + "\"." );
138 patch
.setCoordinateTransform( transform
);
139 patch
.updateMipMaps();
140 patch
.getProject().getLoader().decacheImagePlus( patch
.getId() );
142 IJ
.showProgress( i
, patches
.size() );
147 final static protected void setCoordinateTransform(
148 final List
< Patch
> patches
,
149 final CoordinateTransform transform
,
150 final int numThreads
)
152 final AtomicInteger ai
= new AtomicInteger( 0 );
153 final List
< SetCoordinateTransformThread
> threads
= new ArrayList
< SetCoordinateTransformThread
>();
155 for ( int i
= 0; i
< numThreads
; ++i
)
157 final SetCoordinateTransformThread thread
= new SetCoordinateTransformThread( patches
, transform
, ai
);
158 threads
.add( thread
);
163 for ( final Thread thread
: threads
)
166 catch ( InterruptedException e
)
168 Utils
.log( "Setting CoordinateTransform failed.\n" + e
.getMessage() + "\n" + e
.getStackTrace() );
174 * Appends a {@link CoordinateTransform} to a list of patches.
176 final static protected class AppendCoordinateTransformThread
extends Thread
178 final protected List
< Patch
> patches
;
179 final protected CoordinateTransform transform
;
180 final protected AtomicInteger ai
;
182 public AppendCoordinateTransformThread(
183 final List
< Patch
> patches
,
184 final CoordinateTransform transform
,
185 final AtomicInteger ai
)
187 this.patches
= patches
;
188 this.transform
= transform
;
193 final public void run()
195 for ( int i
= ai
.getAndIncrement(); i
< patches
.size() && !isInterrupted(); i
= ai
.getAndIncrement() )
197 final Patch patch
= patches
.get( i
);
198 patch
.appendCoordinateTransform( transform
);
199 patch
.updateMipMaps();
201 IJ
.showProgress( i
, patches
.size() );
206 final static protected void appendCoordinateTransform(
207 final List
< Patch
> patches
,
208 final CoordinateTransform transform
,
209 final int numThreads
)
211 final AtomicInteger ai
= new AtomicInteger( 0 );
212 final List
< AppendCoordinateTransformThread
> threads
= new ArrayList
< AppendCoordinateTransformThread
>();
214 for ( int i
= 0; i
< numThreads
; ++i
)
216 final AppendCoordinateTransformThread thread
= new AppendCoordinateTransformThread( patches
, transform
, ai
);
217 threads
.add( thread
);
222 for ( final Thread thread
: threads
)
225 catch ( InterruptedException e
)
227 Utils
.log( "Appending CoordinateTransform failed.\n" + e
.getMessage() + "\n" + e
.getStackTrace() );
231 final static public CorrectDistortionFromSelectionParam correctDistortionFromSelectionParam
= new CorrectDistortionFromSelectionParam();
233 final static public Bureaucrat
correctDistortionFromSelectionTask ( final Selection selection
)
235 Worker worker
= new Worker("Distortion Correction", false, true) {
239 correctDistortionFromSelection( selection
);
240 Display
.repaint(selection
.getLayer());
241 } catch (Throwable e
) {
247 public void cleanup() {
248 if (!selection
.isEmpty())
249 selection
.getLayer().getParent().undoOneStep();
252 return Bureaucrat
.createAndStart( worker
, selection
.getProject() );
255 final static public Bureaucrat
correctDistortionFromSelection( final Selection selection
)
257 final List
< Patch
> patches
= new ArrayList
< Patch
>();
258 for ( Displayable d
: Display
.getFront().getSelection().getSelected() )
259 if ( d
instanceof Patch
) patches
.add( ( Patch
)d
);
261 if ( patches
.size() < 2 )
263 Utils
.log("No images in the selection.");
267 final Worker worker
= new Worker( "Lens correction" )
269 final public void run()
275 if ( !correctDistortionFromSelectionParam
.setup( selection
) ) return;
277 final CorrectDistortionFromSelectionParam p
= correctDistortionFromSelectionParam
.clone();
278 final Align
.ParamOptimize ap
= Align
.paramOptimize
.clone();
279 ap
.sift
.set( p
.sift
);
280 ap
.desiredModelIndex
= ap
.expectedModelIndex
= p
.expectedModelIndex
;
281 ap
.maxEpsilon
= p
.maxEpsilon
;
282 ap
.minInlierRatio
= p
.minInlierRatio
;
285 /** Get all patches that will be affected. */
286 final List
< Patch
> allPatches
= new ArrayList
< Patch
>();
287 for ( final Layer l
: selection
.getLayer().getParent().getLayers().subList( p
.firstLayerIndex
, p
.lastLayerIndex
+ 1 ) )
288 for ( Displayable d
: l
.getDisplayables( Patch
.class ) )
289 allPatches
.add( ( Patch
)d
);
291 /** Unset the coordinate transforms of all patches if desired. */
292 if ( p
.clearTransform
)
294 setTaskName( "Clearing present transforms" );
295 setCoordinateTransform( allPatches
, null, Runtime
.getRuntime().availableProcessors() );
299 setTaskName( "Establishing SIFT correspondences" );
301 List
< AbstractAffineTile2D
< ?
> > tiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
302 List
< AbstractAffineTile2D
< ?
> > fixedTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> > ();
303 List
< Patch
> fixedPatches
= new ArrayList
< Patch
>();
304 final Displayable active
= selection
.getActive();
305 if ( active
!= null && active
instanceof Patch
)
306 fixedPatches
.add( ( Patch
)active
);
307 Align
.tilesFromPatches( ap
, patches
, fixedPatches
, tiles
, fixedTiles
);
309 final List
< AbstractAffineTile2D
< ?
>[] > tilePairs
= new ArrayList
< AbstractAffineTile2D
< ?
>[] >();
311 if ( p
.tilesAreInPlace
)
312 AbstractAffineTile2D
.pairOverlappingTiles( tiles
, tilePairs
);
314 AbstractAffineTile2D
.pairTiles( tiles
, tilePairs
);
316 final AbstractAffineTile2D
< ?
> fixedTile
= fixedTiles
.iterator().next();
318 Align
.connectTilePairs( ap
, tiles
, tilePairs
, Runtime
.getRuntime().availableProcessors() );
321 /** Shift all local coordinates into the original image frame */
322 for ( final AbstractAffineTile2D
< ?
> tile
: tiles
)
324 final Rectangle box
= tile
.getPatch().getCoordinateTransformBoundingBox();
325 for ( final PointMatch m
: tile
.getMatches() )
327 final float[] l
= m
.getP1().getL();
328 final float[] w
= m
.getP1().getW();
336 if ( Thread
.currentThread().isInterrupted() ) return;
338 List
< Set
< Tile
< ?
> > > graphs
= AbstractAffineTile2D
.identifyConnectedGraphs( tiles
);
339 if ( graphs
.size() > 1 )
340 Utils
.log( "Could not interconnect all images with correspondences. " );
342 final List
< AbstractAffineTile2D
< ?
> > interestingTiles
;
344 /** Find largest graph. */
345 Set
< Tile
< ?
> > largestGraph
= null;
346 for ( Set
< Tile
< ?
> > graph
: graphs
)
347 if ( largestGraph
== null || largestGraph
.size() < graph
.size() )
348 largestGraph
= graph
;
350 interestingTiles
= new ArrayList
< AbstractAffineTile2D
< ?
> >();
351 for ( Tile
< ?
> t
: largestGraph
)
352 interestingTiles
.add( ( AbstractAffineTile2D
< ?
> )t
);
354 if ( Thread
.currentThread().isInterrupted() ) return;
356 Utils
.log( "Estimating lens model:" );
358 /* initialize with pure affine */
359 Align
.optimizeTileConfiguration( ap
, interestingTiles
, fixedTiles
);
361 /* measure the current error */
364 for ( final AbstractAffineTile2D
< ?
> t
: interestingTiles
)
365 for ( final PointMatch pm
: t
.getMatches() )
367 e
+= pm
.getDistance();
372 double dEpsilon_i
= 0;
373 double epsilon_i
= e
;
374 double dEpsilon_0
= 0;
375 NonLinearTransform lensModel
= null;
377 Utils
.log( "0: epsilon = " + e
);
379 /* Store original point locations */
380 final HashMap
< Point
, Point
> originalPoints
= new HashMap
< Point
, Point
>();
381 for ( final AbstractAffineTile2D
< ?
> t
: interestingTiles
)
382 for ( final PointMatch pm
: t
.getMatches() )
383 originalPoints
.put( pm
.getP1(), pm
.getP1().clone() );
385 for ( int i
= 1; i
< 20 && ( i
< 2 || dEpsilon_i
<= dEpsilon_0
/ 1000 ); ++i
)
387 if ( Thread
.currentThread().isInterrupted() ) return;
389 /* Some data shuffling for the lens correction interface */
390 final List
< PointMatchCollectionAndAffine
> matches
= new ArrayList
< PointMatchCollectionAndAffine
>();
391 for ( AbstractAffineTile2D
< ?
>[] tilePair
: tilePairs
)
393 final AffineTransform a
= tilePair
[ 0 ].createAffine();
394 a
.preConcatenate( tilePair
[ 1 ].getModel().createInverseAffine() );
395 final Collection
< PointMatch
> commonMatches
= new ArrayList
< PointMatch
>();
396 tilePair
[ 0 ].commonPointMatches( tilePair
[ 1 ], commonMatches
);
397 final Collection
< PointMatch
> originalCommonMatches
= new ArrayList
< PointMatch
>();
398 for ( final PointMatch pm
: commonMatches
)
399 originalCommonMatches
.add( new PointMatch(
400 originalPoints
.get( pm
.getP1() ),
401 originalPoints
.get( pm
.getP2() ) ) );
402 matches
.add( new PointMatchCollectionAndAffine( a
, originalCommonMatches
) );
405 setTaskName( "Estimating lens distortion correction" );
407 lensModel
= Distortion_Correction
.createInverseDistortionModel(
411 ( int )fixedTile
.getWidth(),
412 ( int )fixedTile
.getHeight() );
414 /* update local points */
415 for ( final AbstractAffineTile2D
< ?
> t
: interestingTiles
)
416 for ( final PointMatch pm
: t
.getMatches() )
418 final Point currentPoint
= pm
.getP1();
419 final Point originalPoint
= originalPoints
.get( currentPoint
);
420 final float[] l
= currentPoint
.getL();
421 final float[] lo
= originalPoint
.getL();
424 lensModel
.applyInPlace( l
);
428 Align
.optimizeTileConfiguration( ap
, interestingTiles
, fixedTiles
);
430 /* measure the current error */
433 for ( final AbstractAffineTile2D
< ?
> t
: interestingTiles
)
434 for ( final PointMatch pm
: t
.getMatches() )
436 e
+= pm
.getDistance();
441 dEpsilon_i
= e
- epsilon_i
;
443 if ( i
== 1 ) dEpsilon_0
= dEpsilon_i
;
445 Utils
.log( i
+ ": epsilon = " + e
);
446 Utils
.log( i
+ ": epsilon = " + dEpsilon_i
);
449 if ( lensModel
!= null )
453 if ( Thread
.currentThread().isInterrupted() ) return;
455 setTaskName( "Visualizing lens distortion correction" );
456 lensModel
.visualizeSmall( p
.lambda
);
459 setTaskName( "Applying lens distortion correction" );
461 appendCoordinateTransform( allPatches
, lensModel
, Runtime
.getRuntime().availableProcessors() );
463 Utils
.log( "Done." );
466 Utils
.log( "No lens model found." );
470 catch ( Exception e
) { IJError
.print( e
); }
471 finally { finishedWorking(); }
475 return Bureaucrat
.createAndStart( worker
, selection
.getProject() );