1 package mpi
.fruitfly
.registration
;
3 import ini
.trakem2
.utils
.Utils
;
5 import java
.util
.Iterator
;
6 import java
.util
.ArrayList
;
7 import java
.util
.Collection
;
9 import java
.util
.Random
;
10 import java
.awt
.geom
.AffineTransform
;
12 public class TRModel2D
extends Model
{
14 static final public int MIN_SET_SIZE
= 2;
16 final private AffineTransform affine
= new AffineTransform();
17 public AffineTransform
getAffine() { return affine
; }
20 public float[] apply( float[] point
)
22 float[] transformed
= new float[ 2 ];
23 affine
.transform( point
, 0, transformed
, 0, 1 );
28 public void applyInPlace( float[] point
)
30 affine
.transform( point
, 0, point
, 0, 1 );
34 public float[] applyInverse( float[] point
)
36 // the brilliant java.awt.geom.AffineTransform implements transform for float[] but inverseTransform for double[] only...
37 double[] double_point
= new double[]{ point
[ 0 ], point
[ 1 ] };
38 double[] transformed
= new double[ 2 ];
41 affine
.inverseTransform( double_point
, 0, transformed
, 0, 1 );
45 System
.err
.println( "Noninvertible transformation." );
47 return new float[]{ ( float )transformed
[ 0 ], ( float )transformed
[ 1 ] };
51 public void applyInverseInPlace( float[] point
)
53 float[] temp_point
= applyInverse( point
);
54 point
[ 0 ] = temp_point
[ 0 ];
55 point
[ 1 ] = temp_point
[ 1 ];
59 public boolean fit( PointMatch
[] min_matches
)
61 PointMatch m1
= min_matches
[ 0 ];
62 PointMatch m2
= min_matches
[ 1 ];
64 float[] m1_p1
= m1
.getP1().getL();
65 float[] m2_p1
= m2
.getP1().getL();
66 float[] m1_p2
= m1
.getP2().getW();
67 float[] m2_p2
= m2
.getP2().getW();
69 float x1
= m2_p1
[ 0 ] - m1_p1
[ 0 ];
70 float y1
= m2_p1
[ 1 ] - m1_p1
[ 1 ];
71 float x2
= m2_p2
[ 0 ] - m1_p2
[ 0 ];
72 float y2
= m2_p2
[ 1 ] - m1_p2
[ 1 ];
73 float l1
= ( float )Math
.sqrt( x1
* x1
+ y1
* y1
);
74 float l2
= ( float )Math
.sqrt( x2
* x2
+ y2
* y2
);
81 //! unrotate (x2,y2)^T to (x1,y1)^T = (1,0)^T getting the sinus and cosinus of the rotation angle
82 float cos
= x1
* x2
+ y1
* y2
;
83 float sin
= x1
* y2
- y1
* x2
;
85 //m.alpha = atan2( y, x );
87 float tx
= m1_p2
[ 0 ] - cos
* m1_p1
[ 0 ] + sin
* m1_p1
[ 1 ];
88 float ty
= m1_p2
[ 1 ] - sin
* m1_p1
[ 0 ] - cos
* m1_p1
[ 1 ];
89 affine
.setTransform( cos
, sin
, -sin
, cos
, tx
, ty
);
91 // System.out.println( this );
97 public String
toString()
99 return ( "[3,3](" + affine
+ ") " + error
);
102 public void minimize( Collection
< PointMatch
> matches
)
105 float xo1
= 0, yo1
= 0;
106 float xo2
= 0, yo2
= 0;
107 // Implementing Johannes Schindelin's squared error minimization formula
108 // tan(angle) = Sum(x1*y1 + x2y2) / Sum(x1*y2 - x2*y1)
109 int length
= matches
.size();
110 // 1 - compute centers of mass, for displacement and origin of rotation
112 if (0 == length
) return;
114 for ( PointMatch m
: matches
)
116 float[] m_p1
= m
.getP1().getL();
117 float[] m_p2
= m
.getP2().getW();
129 float dx
= xo1
- xo2
; // reversed, because the second will be moved relative to the first
130 float dy
= yo1
- yo2
;
131 float sum1
= 0, sum2
= 0;
132 float x1
, y1
, x2
, y2
;
133 for ( PointMatch m
: matches
)
135 float[] m_p1
= m
.getP1().getL();
136 float[] m_p2
= m
.getP2().getW();
138 // make points local to the center of mass of the first landmark set
139 x1
= m_p1
[ 0 ] - xo1
; // x1
140 y1
= m_p1
[ 1 ] - yo1
; // x2
141 x2
= m_p2
[ 0 ] - xo2
+ dx
; // y1
142 y2
= m_p2
[ 1 ] - yo2
+ dy
; // y2
143 sum1
+= x1
* y2
- y1
* x2
; // x1 * y2 - x2 * y1 // assuming p1 is x1,x2, and p2 is y1,y2
144 sum2
+= x1
* x2
+ y1
* y2
; // x1 * y1 + x2 * y2
146 float angle
= ( float )Math
.atan2( -sum1
, sum2
);
148 affine
.setToIdentity();
149 affine
.rotate( -angle
, xo2
, yo2
);
150 affine
.translate( -dx
, -dy
);
154 * change the model a bit
156 * estimates the necessary amount of shaking for each single dimensional
157 * distance in the set of matches
159 * @param matches point matches
160 * @param scale gives a multiplicative factor to each dimensional distance (increases the amount of shaking)
161 * @param center local pivot point
163 final public void shake(
164 Collection
< PointMatch
> matches
,
172 int num_matches
= matches
.size();
173 if ( num_matches
> 0 )
175 for ( PointMatch m
: matches
)
177 float[] m_p1
= m
.getP1().getW();
178 float[] m_p2
= m
.getP2().getW();
180 xd
+= Math
.abs( m_p1
[ 0 ] - m_p2
[ 0 ] );;
181 yd
+= Math
.abs( m_p1
[ 1 ] - m_p2
[ 1 ] );;
183 // shift relative to the center
184 float x1
= m_p1
[ 0 ] - center
[ 0 ];
185 float y1
= m_p1
[ 1 ] - center
[ 1 ];
186 float x2
= m_p2
[ 0 ] - center
[ 0 ];
187 float y2
= m_p2
[ 1 ] - center
[ 1 ];
189 float l1
= ( float )Math
.sqrt( x1
* x1
+ y1
* y1
);
190 float l2
= ( float )Math
.sqrt( x2
* x2
+ y2
* y2
);
197 //! unrotate (x1,y1)^T to (x2,y2)^T = (1,0)^T getting the sinus and cosinus of the rotation angle
198 float cos
= x1
* x2
+ y1
* y2
;
199 float sin
= y1
* x2
- x1
* y2
;
201 rd
+= Math
.abs( Math
.atan2( sin
, cos
) );
203 xd
/= matches
.size();
204 yd
/= matches
.size();
205 rd
/= matches
.size();
207 //System.out.println( rd );
210 affine
.rotate( rnd
.nextGaussian() * ( float )rd
* scale
, center
[ 0 ], center
[ 1 ] );
214 * estimate the transformation model for a set of feature correspondences
215 * containing a high number of outliers using RANSAC
217 * @param candidates set of correspondence candidates
218 * @param inliers set ot correspondences that fit the finally estimated model if any
219 * @param iterations number of iterations
220 * @param epsilon maximally allowed displacement
221 * @param min_inlier_ratio minimal amount of inliers
223 * @return TRModel2D or null
228 * @article{FischlerB81,
229 * author = {Martin A. Fischler and Robert C. Bolles},
230 * title = {Random sample consensus: a paradigm for model fitting with applications to image analysis and automated cartography},
231 * journal = {Communications of the ACM},
235 * pages = {381--395},
236 * publisher = {ACM Press},
237 * address = {New York, NY, USA},
238 * issn = {0001-0782},
239 * doi = {http://doi.acm.org/10.1145/358669.358692},
244 static public TRModel2D
estimateModel(
245 List
< PointMatch
> candidates
,
246 Collection
< PointMatch
> inliers
,
249 float min_inlier_ratio
)
253 if ( candidates
.size() < MIN_SET_SIZE
)
255 System
.err
.println( candidates
.size() + " correspondence candidates are not enough to estimate a model, at least " + TRModel2D
.MIN_SET_SIZE
+ " required." );
259 TRModel2D model
= new TRModel2D(); //!< the final model to be estimated
262 while ( i
< iterations
)
264 // choose T::MIN_SET_SIZE disjunctive matches randomly
265 PointMatch
[] min_matches
= new PointMatch
[ MIN_SET_SIZE
];
266 int[] keys
= new int[ MIN_SET_SIZE
];
268 for ( int j
= 0; j
< MIN_SET_SIZE
; ++j
)
271 boolean in_set
= false;
274 key
= ( int )( rnd
.nextDouble() * candidates
.size() );
277 // check if this key exists already
278 for ( int k
= 0; k
< j
; ++k
)
280 if ( key
== keys
[ k
] )
289 min_matches
[ j
] = candidates
.get( key
);
292 TRModel2D m
= new TRModel2D();
293 final ArrayList
< PointMatch
> temp_inliers
= new ArrayList
< PointMatch
>();
294 m
.fit( min_matches
);
296 boolean is_good
= m
.test( candidates
, temp_inliers
, epsilon
, min_inlier_ratio
);
297 while ( is_good
&& num_inliers
< temp_inliers
.size() )
299 num_inliers
= temp_inliers
.size();
300 m
.minimize( temp_inliers
);
301 is_good
= m
.test( candidates
, temp_inliers
, epsilon
, min_inlier_ratio
);
305 m
.betterThan( model
) &&
306 temp_inliers
.size() >= 3 * MIN_SET_SIZE
) // now at least 6 matches required
310 inliers
.addAll( temp_inliers
);
314 if ( inliers
.size() == 0 )
321 * estimate the transformation model for a set of feature correspondences
322 * containing a high number of outliers using RANSAC
324 * increase the error as long as not more inliers occur
326 static public TRModel2D
estimateBestModel(
327 List
< PointMatch
> candidates
,
328 Collection
< PointMatch
> inliers
,
331 float min_inlier_ratio
)
334 TRModel2D model
= null;
335 float epsilon
= 0.0f
;
336 if ( candidates
.size() > MIN_SET_SIZE
)
338 int highest_num_inliers
= 0;
339 int convergence_count
= 0;
343 final ArrayList
< PointMatch
> temp_inliers
= new ArrayList
< PointMatch
>();
344 epsilon
+= min_epsilon
;
345 // 1000 iterations lead to a probability of < 0.01% that only bad data values were found
347 candidates
, //!< point correspondence candidates
349 1000, //!< iterations
350 epsilon
, //!< maximal alignment error for a good point pair when fitting the model
351 min_inlier_ratio
); //!< minimal partition (of 1.0) of inliers
355 int num_inliers
= temp_inliers
.size();
356 if ( num_inliers
<= highest_num_inliers
)
364 inliers
.addAll( temp_inliers
);
365 convergence_count
= 0;
366 highest_num_inliers
= num_inliers
;
370 while ( ( m
== null || convergence_count
< 4 ) && epsilon
< max_epsilon
);
374 Utils
.log2( "No model found." );
378 Utils
.log2( "Model with epsilon <= " + epsilon
+ " for " + inliers
.size() + " inliers found." );
379 Utils
.log2( " Affine transform: " + model
.getAffine().toString() );
386 public TRModel2D
clone()
388 TRModel2D trm
= new TRModel2D();
389 trm
.affine
.setTransform( affine
);
394 public void preConcatenate(TRModel2D model
) {
395 this.affine
.preConcatenate(model
.affine
);
398 public void concatenate(TRModel2D model
) {
399 this.affine
.concatenate(model
.affine
);