/*- * #%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.optimizationtypes; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import mpicbg.models.AbstractModel; import mpicbg.models.Affine3D; import mpicbg.models.Model; import mpicbg.models.Point; import mpicbg.models.PointMatch; import mpicbg.models.Tile; import mpicbg.spim.data.registration.ViewRegistration; import mpicbg.spim.data.sequence.ViewDescription; import mpicbg.spim.data.sequence.ViewId; import mpicbg.spim.data.sequence.ViewSetup; import mpicbg.spim.io.IOFunctions; import net.imglib2.Dimensions; import net.imglib2.realtransform.AffineTransform3D; import spim.fiji.plugin.Apply_Transformation; import spim.fiji.spimdata.SpimData2; import spim.fiji.spimdata.ViewSetupUtils; import spim.process.interestpointregistration.ChannelProcess; import spim.process.interestpointregistration.GlobalOpt; import spim.process.interestpointregistration.PairwiseMatch; /** * Defines a subset of views that need to be matched and then a global optimization * be computed on. * * In case of a registration of individual timepoints, each timepoint would be one * {@link GlobalOptimizationSubset}, for an all-to-all matching there is only one object. * * @author Stephan Preibisch (stephan.preibisch@gmx.de) */ public class GlobalOptimizationSubset { final ArrayList< PairwiseMatch > viewPairs; final String description; // will be populated once getViews() is called ArrayList< ViewId > viewList; public GlobalOptimizationSubset( final ArrayList< PairwiseMatch > viewPairs, final String description ) { this.viewPairs = viewPairs; this.description = description; } /** * @param model * @param type * @param spimData * @param channelsToProcess - just to annotate the registration * @param description * @return */ public < M extends Model< M > > boolean computeGlobalOpt( final M model, final GlobalOptimizationType type, final SpimData2 spimData, final List< ChannelProcess > channelsToProcess, final String description ) { final HashMap< ViewId, Tile< M > > tiles = GlobalOpt.compute( model, type, this, type.considerTimePointsAsUnit() ); if ( tiles == null ) return false; String channelList = "["; for ( final ChannelProcess c : channelsToProcess ) channelList += c.getLabel() + " (c=" + c.getChannel().getName() + "), "; channelList = channelList.substring( 0, channelList.length() - 2 ) + "]"; final AffineTransform3D mapBackModel; // TODO: Map back first tile as good as possible to original location??? if ( type.getMapBackReferenceTile( this ) != null && type.getMapBackModel() != null ) mapBackModel = computeMapBackModel( tiles, type, spimData ); else mapBackModel = null; // update the view registrations for ( final ViewId viewId : this.getViews() ) { final Tile< M > tile = tiles.get( viewId ); // TODO: we assume that M is an Affine3D, which is not necessarily true final Affine3D< ? > tilemodel = (Affine3D< ? >)tile.getModel(); final double[][] m = new double[ 3 ][ 4 ]; tilemodel.toMatrix( m ); final AffineTransform3D t = new AffineTransform3D(); t.set( 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] ); if ( mapBackModel != null ) { t.preConcatenate( mapBackModel ); IOFunctions.println( "ViewId=" + viewId.getViewSetupId() + ": " + t ); } Apply_Transformation.preConcatenateTransform( spimData, viewId, t, description + " on " + channelList ); } return true; } protected < M extends Model< M > > AffineTransform3D computeMapBackModel( final HashMap< ViewId, Tile< M > > tiles, final GlobalOptimizationType type, final SpimData2 spimData ) { final AbstractModel< ? > mapBackModel = type.getMapBackModel(); if ( mapBackModel.getMinNumMatches() > 4 ) { IOFunctions.println( "Cannot map back using a model that needs more than 4 points: " + mapBackModel.getClass().getSimpleName() ); return null; } else { IOFunctions.println( "Mapping back to reference frame using a " + mapBackModel.getClass().getSimpleName() ); final ViewId referenceTile = type.getMapBackReferenceTile( this ); final ViewDescription referenceTileViewDescription = spimData.getSequenceDescription().getViewDescription( referenceTile ); final ViewSetup referenceTileSetup = referenceTileViewDescription.getViewSetup(); Dimensions size = ViewSetupUtils.getSizeOrLoad( referenceTileSetup, referenceTileViewDescription.getTimePoint(), spimData.getSequenceDescription().getImgLoader() ); long w = size.dimension( 0 ); long h = size.dimension( 1 ); final double[][] p = new double[][]{ { 0, 0, 0 }, { w, 0, 0 }, { 0, h, 0 }, { w, h, 0 } }; // original coordinates == pa final double[][] pa = new double[ 4 ][ 3 ]; // map coordinates to the actual input coordinates final ViewRegistration inputModel = spimData.getViewRegistrations().getViewRegistration( referenceTile ); for ( int i = 0; i < p.length; ++i ) inputModel.getModel().apply( p[ i ], pa[ i ] ); final M outputModel = tiles.get( referenceTile ).getModel(); // transformed coordinates == pb final double[][] pb = new double[ 4 ][ 3 ]; for ( int i = 0; i < p.length; ++i ) pb[ i ] = outputModel.apply( pa[ i ] ); // compute the model that maps pb >> pa try { final ArrayList< PointMatch > pm = new ArrayList< PointMatch >(); for ( int i = 0; i < p.length; ++i ) pm.add( new PointMatch( new Point( pb[ i ] ), new Point( pa[ i ] ) ) ); mapBackModel.fit( pm ); } catch ( Exception e ) { IOFunctions.println( "Could not compute model for mapping back: " + e ); e.printStackTrace(); return null; } final AffineTransform3D mapBack = new AffineTransform3D(); final double[][] m = new double[ 3 ][ 4 ]; ((Affine3D<?>)mapBackModel).toMatrix( m ); mapBack.set( 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] ); IOFunctions.println( "Model for mapping back: " + mapBack + "\n" ); return mapBack; } } /** * @return - all views that are part of the subset and will receive a transformation model */ public List< ViewId > getViews() { if ( viewList != null ) return viewList; final Set< ViewId > viewSet = new HashSet< ViewId >(); for ( final PairwiseMatch pair : getViewPairs() ) { viewSet.add( pair.getViewIdA() ); viewSet.add( pair.getViewIdB() ); } viewList = new ArrayList< ViewId >(); viewList.addAll( viewSet ); Collections.sort( viewList ); return viewList; } public List< PairwiseMatch > getViewPairs() { return viewPairs; } public String getDescription() { return description; } }