/** * License: GPL * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License 2 * as published by the Free Software Foundation. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package mpicbg.trakem2.transform; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import mpicbg.models.AffineModel2D; import mpicbg.models.AffineModel3D; import mpicbg.models.IllDefinedDataPointsException; import mpicbg.models.NotEnoughDataPointsException; import mpicbg.models.Point; import mpicbg.models.PointMatch; import mpicbg.models.RigidModel2D; import mpicbg.models.SimilarityModel2D; import mpicbg.models.TranslationModel2D; /** * * @author Stephan Saalfeld saalfelds@janelia.hhmi.org */ public class MovingLeastSquaresTransform extends mpicbg.models.MovingLeastSquaresTransform implements CoordinateTransform { private static final long serialVersionUID = 969438449108820224L; @Override final public void init( final String data ) throws NumberFormatException { matches.clear(); final String[] fields = data.split( "\\s+" ); if ( fields.length > 3 ) { final int d = Integer.parseInt( fields[ 1 ] ); if ( ( fields.length - 3 ) % ( 2 * d + 1 ) == 0 ) { if ( d == 2 ) { if ( fields[ 0 ].equals( "translation" ) ) model = new TranslationModel2D(); else if ( fields[ 0 ].equals( "rigid" ) ) model = new RigidModel2D(); else if ( fields[ 0 ].equals( "similarity" ) ) model = new SimilarityModel2D(); else if ( fields[ 0 ].equals( "affine" ) ) model = new AffineModel2D(); else throw new NumberFormatException( "Inappropriate parameters for " + this.getClass().getCanonicalName() ); } else if ( d == 3 ) { if ( fields[ 0 ].equals( "affine" ) ) model = new AffineModel3D(); else throw new NumberFormatException( "Inappropriate parameters for " + this.getClass().getCanonicalName() ); } else throw new NumberFormatException( "Inappropriate parameters for " + this.getClass().getCanonicalName() ); alpha = Double.parseDouble( fields[ 2 ] ); int i = 2; while ( i < fields.length - 1 ) { final double[] p1 = new double[ d ]; for ( int k = 0; k < d; ++k ) p1[ k ] = Double.parseDouble( fields[ ++i ] ); final double[] p2 = new double[ d ]; for ( int k = 0; k < d; ++k ) p2[ k ] = Double.parseDouble( fields[ ++i ] ); final double weight = Double.parseDouble( fields[ ++i ] ); final PointMatch m = new PointMatch( new Point( p1 ), new Point( p2 ), weight ); matches.add( m ); } } else throw new NumberFormatException( "Inappropriate parameters for " + this.getClass().getCanonicalName() ); } else throw new NumberFormatException( "Inappropriate parameters for " + this.getClass().getCanonicalName() ); } @Override public String toDataString() { final StringBuilder data = new StringBuilder(); toDataString( data ); return data.toString(); } static private final Comparator< PointMatch > SORTER = new Comparator< PointMatch >() { @Override public final int compare(final PointMatch o1, final PointMatch o2) { final double[] p1 = o1.getP1().getW(); final double[] p2 = o1.getP2().getW(); final double dx = p1[0] - p2[0]; if ( dx < 0) return -1; if ( 0 == dx) { final double dy = p1[1] - p2[1]; if ( dy < 0 ) return -1; if ( 0 == dy ) return 0; return 1; } return 1; } }; private final void toDataString( final StringBuilder data ) { if ( AffineModel2D.class.isInstance( model ) ) data.append("affine 2"); else if ( TranslationModel2D.class.isInstance( model ) ) data.append("translation 2"); else if ( RigidModel2D.class.isInstance( model ) ) data.append("rigid 2"); else if ( SimilarityModel2D.class.isInstance( model ) ) data.append("similarity 2"); else if ( AffineModel3D.class.isInstance( model ) ) data.append("affine 3"); else data.append("unknown"); data.append(' ').append(alpha); // Sort matches, so that they are always written the same way // Will help lots git and .zip to reduce XML file size final ArrayList< PointMatch > pms = new ArrayList< PointMatch >( matches ); Collections.sort( pms, SORTER ); for ( final PointMatch m : pms ) { final double[] p1 = m.getP1().getL(); final double[] p2 = m.getP2().getW(); for ( int k = 0; k < p1.length; ++k ) data.append(' ').append(p1[ k ]); for ( int k = 0; k < p2.length; ++k ) data.append(' ').append(p2[ k ]); data.append(' ').append(m.getWeight()); } } @Override final public String toXML( final String indent ) { final StringBuilder xml = new StringBuilder( 128 ); xml.append( indent ) .append( "<ict_transform class=\"" ) .append( this.getClass().getCanonicalName() ) .append( "\" data=\"" ); toDataString( xml ); return xml.append( "\"/>" ).toString(); } @Override /** * TODO Make this more efficient */ final public MovingLeastSquaresTransform copy() { final MovingLeastSquaresTransform t = new MovingLeastSquaresTransform(); t.init( toDataString() ); return t; } @Override final public void applyInPlace( final double[] location ) { final Collection< PointMatch > weightedMatches = new ArrayList< PointMatch >(); for ( final PointMatch m : matches ) { final double[] l = m.getP1().getL(); double s = 0; for ( int i = 0; i < location.length; ++i ) { final double dx = l[ i ] - location[ i ]; s += dx * dx; } if ( s <= 0 ) { final double[] w = m.getP2().getW(); for ( int i = 0; i < location.length; ++i ) location[ i ] = w[ i ]; return; } final double weight = m.getWeight() * weigh( s ); final PointMatch mw = new PointMatch( m.getP1(), m.getP2(), weight ); weightedMatches.add( mw ); } try { synchronized ( model ) { model.fit( weightedMatches ); model.applyInPlace( location ); } } catch ( final IllDefinedDataPointsException e ){} catch ( final NotEnoughDataPointsException e ){} } }