/*- * #%L * Fiji distribution of ImageJ for the life sciences. * %% * Copyright (C) 2007 - 2017 Fiji developers. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-2.0.html>. * #L% */ package spim.process.interestpointregistration; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import mpicbg.models.AbstractAffineModel3D; import mpicbg.models.Affine3D; import mpicbg.models.AffineModel3D; import mpicbg.models.IllDefinedDataPointsException; import mpicbg.models.Model; import mpicbg.models.NotEnoughDataPointsException; import mpicbg.models.PointMatch; import mpicbg.models.RigidModel3D; import mpicbg.models.Tile; import mpicbg.models.TileConfiguration; import mpicbg.spim.data.sequence.ViewId; import mpicbg.spim.io.IOFunctions; import mpicbg.spim.mpicbg.PointMatchGeneric; import spim.process.interestpointregistration.optimizationtypes.GlobalOptimizationSubset; import spim.process.interestpointregistration.optimizationtypes.GlobalOptimizationType; import spim.vecmath.Matrix4f; import spim.vecmath.Quat4f; import spim.vecmath.Transform3D; import spim.vecmath.Vector3d; import spim.vecmath.Vector3f; /** * * @author Stephan Preibisch (stephan.preibisch@gmx.de) * */ public class GlobalOpt { /** * Computes a global optimization based on the corresponding points * * @param registrationType - to determine which tiles are fixed * @param subset - to get the correspondences * @return - list of Tiles containing the final transformation models */ public static < M extends Model< M > > HashMap< ViewId, Tile< M > > compute( final M model, final GlobalOptimizationType registrationType, final GlobalOptimizationSubset subset, final boolean considerTimePointsAsUnit ) { // assemble all views and corresponding points final List< PairwiseMatch > pairs = subset.getViewPairs(); final List< ViewId > views = subset.getViews(); // assign ViewIds to the individual Tiles (either one tile per view or one tile per timepoint) final HashMap< ViewId, Tile< M > > map = assignViewsToTiles( model, views, considerTimePointsAsUnit ); // assign the pointmatches to all the tiles for ( final PairwiseMatch pair : pairs ) GlobalOpt.addPointMatches( pair.getInliers(), map.get( pair.getViewIdA() ), map.get( pair.getViewIdB() ) ); // add and fix tiles as defined in the GlobalOptimizationType final TileConfiguration tc = addAndFixTiles( views, map, registrationType, subset, considerTimePointsAsUnit ); if ( tc.getTiles().size() == 0 ) { IOFunctions.println( "There are no connected tiles, cannot do an optimization. Quitting." ); return null; } // now perform the global optimization try { int unaligned = tc.preAlign().size(); if ( unaligned > 0 ) IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): pre-aligned all tiles but " + unaligned ); else IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): prealigned all tiles" ); tc.optimize( 10, 10000, 200 ); if ( considerTimePointsAsUnit ) IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Global optimization of " + tc.getTiles().size() + " timepoint-tiles (Model=" + model.getClass().getSimpleName() + "):" ); else IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Global optimization of " + tc.getTiles().size() + " view-tiles (Model=" + model.getClass().getSimpleName() + "):" ); IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Avg Error: " + tc.getError() + "px" ); IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Min Error: " + tc.getMinError() + "px" ); IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Max Error: " + tc.getMaxError() + "px" ); } catch (NotEnoughDataPointsException e) { IOFunctions.println( "Global optimization failed: " + e ); e.printStackTrace(); } catch (IllDefinedDataPointsException e) { IOFunctions.println( "Global optimization failed: " + e ); e.printStackTrace(); } IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Transformation Models:" ); // TODO: We assume it is Affine3D here for ( final ViewId viewId : views ) { final Tile< M > tile = map.get( viewId ); String output = "ViewId=" + viewId.getViewSetupId() + ": " + printAffine3D( (Affine3D<?>)tile.getModel() ); if ( (Model)tile.getModel() instanceof RigidModel3D ) { String rotA = "Java3D is MISSING!"; try { rotA = getRotationAxis( (RigidModel3D)(Model)tile.getModel() ); } catch( NoClassDefFoundError e ) {} IOFunctions.println( output + ", Rotation Axis=" + rotA ); } else { String scaling = "Java3D is MISSING!"; try { scaling = getScaling( (Affine3D<?>)tile.getModel() ); } catch( NoClassDefFoundError e ) {} IOFunctions.println( output + ", Scaling=" + scaling ); } } return map; } /** * WARNING: This fails on older MACs, in this case remove: * * Check if Apple's out-dated Java 3D version 1.3 is installed in System/Library/Java/Extensions/ on your Mac. * Remove all Java 3D 1.3 related files including vecmath.jar (jar, jnilib), they are useless. * * @param model * @return */ public static String getRotationAxis( final RigidModel3D model ) { try { final Matrix4f matrix = new Matrix4f(); getTransform3D( model ).get( matrix ); final Quat4f qu = new Quat4f(); qu.set( matrix ); final Vector3f n = new Vector3f(qu.getX(),qu.getY(), qu.getZ()); n.normalize(); return "Approx. axis: " + n + ", approx. angle: " + Math.toDegrees( Math.acos( qu.getW() ) * 2 ); } catch ( Exception e ) { return "Check if Apple's out-dated Java 3D version 1.3 is installed in System/Library/Java/Extensions/ on your Mac." + "Remove all Java 3D 1.3 related files including vecmath.jar (jar, jnilib), they are useless."; } } public static String getScaling( final Affine3D< ? > affine ) { final Transform3D t = getTransform3D( affine ); final Vector3d v = new Vector3d(); t.getScale( v ); return "Scaling: " + v.x + ", " + v.y + ", " + v.z; } public static Transform3D getTransform3D( final Affine3D< ? > affine ) { final double[][] m = new double[ 3 ][ 4 ]; ((Affine3D<?>)affine).toMatrix( m ); final Transform3D transform = new Transform3D(); final double[] m2 = new double[ 16 ]; transform.get( m2 ); m2[ 0 ] = m[0][0]; m2[ 1 ] = m[0][1]; m2[ 2 ] = m[0][2]; m2[ 3 ] = m[0][3]; m2[ 4 ] = m[1][0]; m2[ 5 ] = m[1][1]; m2[ 6 ] = m[1][2]; m2[ 7 ] = m[1][3]; m2[ 8 ] = m[2][0]; m2[ 9 ] = m[2][1]; m2[ 10] = m[2][2]; m2[ 11] = m[2][3]; transform.set( m2 ); return transform; } public static <M extends AbstractAffineModel3D<M>> Transform3D getTransform3D( final M model ) { final Transform3D transform = new Transform3D(); final double[] m = model.getMatrix( null ); final double[] m2 = new double[ 16 ]; transform.get( m2 ); for ( int i = 0; i < m.length; ++i ) m2[ i ] = m[ i ]; transform.set( m2 ); return transform; } public static String printAffine3D( final Affine3D< ? > model ) { final double[][] m = new double[ 3 ][ 4 ]; model.toMatrix( m ); return m[0][0] + "," + m[0][1] + "," + m[0][2] + "," + m[0][3] + "," + m[1][0] + "," + m[1][1] + "," + m[1][2] + "," + m[1][3] + "," + m[2][0] + "," + m[2][1] + "," + m[2][2] + "," + m[2][3]; } protected static < M extends Model< M > > TileConfiguration addAndFixTiles( final List< ViewId > views, final HashMap< ViewId, Tile< M > > map, final GlobalOptimizationType registrationType, final GlobalOptimizationSubset subset, final boolean considerTimePointsAsUnit ) { // create a new tileconfiguration organizing the global optimization final TileConfiguration tc = new TileConfiguration(); // assemble a list of all tiles and set them fixed if desired final HashSet< Tile< M > > tiles = new HashSet< Tile< M > >(); for ( final ViewId viewId : views ) { final Tile< M > tile = map.get( viewId ); // if one of the views that maps to this tile is fixed, fix this tile if it is not already fixed if ( registrationType.isFixedTile( viewId ) && !tc.getFixedTiles().contains( tile ) ) { if ( considerTimePointsAsUnit ) IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Fixing timepoint-tile (timepointId = " + viewId.getTimePointId() + ")" ); else IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Fixing view-tile (viewSetupId = " + viewId.getViewSetupId() + ")" ); tc.fixTile( tile ); } // add it if it is not already there tiles.add( tile ); } // now add connected tiles to the tileconfiguration for ( final Tile< M > tile : tiles ) if ( tile.getConnectedTiles().size() > 0 ) tc.addTile( tile ); return tc; } protected static < M extends Model< M > > HashMap< ViewId, Tile< M > > assignViewsToTiles( final M model, final List< ViewId > views, final boolean considerTimePointsAsUnit ) { final HashMap< ViewId, Tile< M > > map = new HashMap< ViewId, Tile< M > >(); if ( considerTimePointsAsUnit ) { // // there is one tile per timepoint // // figure out all timepoints involved final HashSet< Integer > timepoints = new HashSet< Integer >(); for ( final ViewId view : views ) timepoints.add( view.getTimePointId() ); // for all timepoints find the viewIds that belong to this timepoint for ( final int t : timepoints ) { // one tile per timepoint final Tile< M > tileTimepoint = new Tile< M >( model.copy() ); // all viewIds of one timepoint map to the same tile (see main method for test, that works) for ( final ViewId viewId : views ) if ( viewId.getTimePointId() == t ) map.put( viewId, tileTimepoint ); } } else { // there is one tile per view for ( final ViewId viewId : views ) map.put( viewId, new Tile< M >( model.copy() ) ); } return map; } protected static void addPointMatches( final ArrayList< PointMatchGeneric< Detection > > correspondences, final Tile<?> tileA, final Tile<?> tileB ) { final ArrayList<PointMatch> pm = new ArrayList<PointMatch>(); pm.addAll( correspondences ); if ( correspondences.size() > 0 ) { tileA.addMatches( pm ); tileB.addMatches( PointMatch.flip( pm ) ); tileA.addConnectedTile( tileB ); tileB.addConnectedTile( tileA ); } } public static void main( String[] args ) { // multiple keys can map to the same value final HashMap< ViewId, Tile< AffineModel3D > > map = new HashMap<ViewId, Tile<AffineModel3D>>(); final AffineModel3D m1 = new AffineModel3D(); final AffineModel3D m2 = new AffineModel3D(); final Tile< AffineModel3D > tile1 = new Tile<AffineModel3D>( m1 ); final Tile< AffineModel3D > tile2 = new Tile<AffineModel3D>( m2 ); final ViewId v11 = new ViewId( 1, 1 ); final ViewId v21 = new ViewId( 2, 1 ); final ViewId v12 = new ViewId( 1, 2 ); final ViewId v22 = new ViewId( 2, 2 ); map.put( v11, tile1 ); map.put( v21, tile2 ); map.put( v12, tile1 ); map.put( v22, tile2 ); m1.set( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ); m2.set( 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ); System.out.println( map.get( v11 ).getModel() ); System.out.println( map.get( v21 ).getModel() ); System.out.println( map.get( v12 ).getModel() ); System.out.println( map.get( v22 ).getModel() ); } }