3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2006, 2007 Albert Cardona.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.vector
;
25 import ini
.trakem2
.utils
.IJError
;
26 import ini
.trakem2
.utils
.Utils
;
27 import ini
.trakem2
.utils
.M
;
28 import ij
.measure
.Calibration
;
30 /** String of vectors. */
31 public class VectorString2D
implements VectorString
{
33 // all private to the package: accessible from Editions class
34 private double[] x
; // points
36 private double[] v_x
= null; // vectors, after resampling
37 private double[] v_y
= null;
38 /** The length of the x,y and v_x, v_y resampled points (the actual arrays may be a bit longer) */
40 /** The point interdistance after resampling. */
41 private double delta
= 0; // the delta used for resampling
42 /** The Z coordinate of the entire planar curve represented by this VectorString2D. */
44 private boolean closed
;
46 private Calibration cal
= null;
48 /** Construct a new String of Vectors from the given points and desired resampling point interdistance 'delta'. */
49 public VectorString2D(double[] x
, double[] y
, double z
, boolean closed
) throws Exception
{
50 if (!(x
.length
== y
.length
)) {
51 throw new Exception("x and y must be of the same length.");
53 this.length
= x
.length
;
60 /** Does NOT clone the vector arrays, which are initialized to NULL; only the x,y,delta,z. */
61 public Object
clone() {
62 double[] x2
= new double[length
];
63 double[] y2
= new double[length
];
64 System
.arraycopy(x
, 0, x2
, 0, length
);
65 System
.arraycopy(y
, 0, y2
, 0, length
);
67 return new VectorString2D(x2
, y2
, z
, closed
);
68 } catch (Exception e
) {
74 public int length() { return length
; }
76 /** If not resampled, returns zero. */
77 public double getDelta() { return delta
; }
79 public double[] getPoints(final int dim
) {
86 public double[] getVectors(final int dim
) {
93 public double getPoint(final int dim
, final int i
) {
101 public double getVector(final int dim
, final int i
) {
103 case 0: return v_x
[i
];
104 case 1: return v_y
[i
];
108 public boolean isClosed() { return closed
; }
110 public double getAverageDelta() { // equivalent to C's getAndSetAveragePointInterdistance function
112 for (int i
=x
.length
-1; i
>0; i
--) {
113 d
+= Math
.sqrt( (x
[i
] - x
[i
-1])*(x
[i
] - x
[i
-1]) + (y
[i
] - y
[i
-1])*(y
[i
] - y
[i
-1])); // pytagorian rule for 2D
118 /** Same as resample(delta). */
119 public void resample(double delta
, boolean with_source
) {
123 /** Homogenize the average point interdistance to 'delta'. */ // There are problems with the last point.
124 public void resample(double delta
) {
125 if (Math
.abs(delta
- this.delta
) < 0.0000001) {
129 this.delta
= delta
; // store for checking purposes
133 /** As in the resampling method for CurveMorphing_just_C.c but for 2D. Uses the assigned 'delta'. Will reorder to counter-clock wise if necessary. */
134 private void resample() {
135 final int MAX_AHEAD
= 6;
136 final double MAX_DISTANCE
= 2.5 * delta
;
137 double[] ps_x
= new double[length
];
138 double[] ps_y
= new double[length
];
139 double[] v_x
= new double[length
];
140 double[] v_y
= new double[length
];
141 final int p_length
= this.length
; // to keep my head cool
142 int ps_length
= this.length
; // the length of the resampled vectors
144 // reorder to CCW if needed: (so all curves have the same orientation)
145 // find bounding box:
146 double x_max
= 0; int x_max_i
= 0;
147 double y_max
= 0; int y_max_i
= 0;
148 double x_min
= Double
.MAX_VALUE
; int x_min_i
= 0;
149 double y_min
= Double
.MAX_VALUE
; int y_min_i
= 0;
150 for (int i
=0;i
<p_length
; i
++) {
151 if (x
[i
] > x_max
) { x_max
= x
[i
]; x_max_i
= i
; } // this lines could be optimized, the p->x etc. are catched below
152 if (y
[i
] > y_max
) { y_max
= y
[i
]; y_max_i
= i
; }
153 if (x
[i
] < x_min
) { x_min
= x
[i
]; x_min_i
= i
; }
154 if (y
[i
] < y_min
) { y_min
= y
[i
]; y_min_i
= i
; }
157 if (y_min_i
- x_max_i
>= 0) collect
++;
158 if (x_min_i
- y_min_i
>= 0) collect
++;
159 if (y_max_i
- x_min_i
>= 0) collect
++;
160 if (x_max_i
- y_max_i
>= 0) collect
++;
162 if (3 != collect
) { // this should be '3 == collect', but then we are looking at the curves from the other side relative to what ImageJ is showing. In any case as long as one or the other gets rearranged, they'll be fine.
163 // Clockwise! Reorder to CCW by reversing the arrays in place
166 for (int i
=0; i
< p_length
/2; i
++) {
178 // first resampled point is the same as 0
181 // the index over x,y
183 // the index over ps (the resampled points)
187 double angle
, angleXY
, dist1
, dist2
;
188 int[] ahead
= new int[MAX_AHEAD
];
189 double[] distances
= new double[MAX_AHEAD
];
196 double[] w
= new double[MAX_AHEAD
];
198 // start infinite loop
199 for (;prev_i
<= i
;) {
200 if (prev_i
> i
) {//the loop has completed one round, since 'i' can go up to MAX_POINTS ahead of the last point into the points at the beggining of the array. Whenever the next point 'i' to start exploring is set beyond the length of the array, then the condition is met.
203 // check ps and v array lengths
204 if (j
>= ps_length
) {
207 v_x
= Utils
.copy(v_x
, ps_length
);
208 v_y
= Utils
.copy(v_y
, ps_length
);
209 ps_x
= Utils
.copy(ps_x
, ps_length
);
210 ps_y
= Utils
.copy(ps_y
, ps_length
);
212 // get distances of MAX_POINTs ahead from the previous point
213 n_ahead
= 0; // reset
214 for (t
=0; t
<MAX_AHEAD
; t
++) {
215 s
= i
+ t
; // 'i' is the first to start inspecting from
216 // fix 's' if it goes over the end // TODO this is problematic for sure for open curves.
218 if (this.closed
) s
= s
- p_length
;
219 else s
= p_length
-1; // the last
221 dist_ahead
= Math
.sqrt((x
[s
] - ps_x
[j
-1])*(x
[s
] - ps_x
[j
-1]) + (y
[s
] - ps_y
[j
-1])*(y
[s
] - ps_y
[j
-1]));
222 if (dist_ahead
< MAX_DISTANCE
) {
224 distances
[n_ahead
] = dist_ahead
;
229 if (0 == n_ahead
) { // no points ahead found within MAX_DISTANCE
230 // all MAX_POINTS ahead lay under delta.
232 // simpler version: use just the next point
233 dist1
= Math
.sqrt((x
[i
] - ps_x
[j
-1])*(x
[i
] - ps_x
[j
-1]) + (y
[i
] - ps_y
[j
-1])*(y
[i
] - ps_y
[j
-1]));
234 angleXY
= M
.getAngle(x
[i
] - ps_x
[j
-1], y
[i
] - ps_y
[j
-1]);
236 dx
= Math
.cos(angleXY
) * delta
;
237 dy
= Math
.sin(angleXY
) * delta
;
238 ps_x
[j
] = ps_x
[j
-1] + dx
;
239 ps_y
[j
] = ps_y
[j
-1] + dy
;
243 //correct for point overtaking the not-close-enough point ahead in terms of 'delta_p' as it is represented in MAX_DISTANCE, but overtaken by the 'delta' used for subsampling:
244 if (dist1
<= delta
) {
245 //look for a point ahead that is over distance delta from the previous j, so that it will lay ahead of the current j
246 for (u
=i
; u
<=p_length
; u
++) {
247 dist2
= Math
.sqrt((x
[u
] - ps_x
[j
-1])*(x
[u
] - ps_x
[j
-1]) + (y
[u
] - ps_y
[j
-1])*(y
[u
] - ps_y
[j
-1]));
256 w
[0] = distances
[0] / MAX_DISTANCE
;
257 double largest
= w
[0];
258 for (u
=1; u
<n_ahead
; u
++) {
259 w
[u
] = 1 - (distances
[u
] / MAX_DISTANCE
);
260 if (w
[u
] > largest
) {
264 // normalize weights: divide by largest
266 for (u
=0; u
<n_ahead
; u
++) {
267 w
[u
] = w
[u
] / largest
;
270 // correct error. The closest point gets the extra
274 w
= recalculate(w
, n_ahead
, sum
);
276 // calculate the new point using the weights
279 for (u
=0; u
<n_ahead
; u
++) {
281 if (iu
>= p_length
) iu
-= p_length
;
282 angleXY
= M
.getAngle(x
[iu
] - ps_x
[j
-1], y
[iu
] - ps_y
[j
-1]);
283 dx
+= w
[u
] * Math
.cos(angleXY
);
284 dy
+= w
[u
] * Math
.sin(angleXY
);
286 // make vector and point:
289 ps_x
[j
] = ps_x
[j
-1] + dx
;
290 ps_y
[j
] = ps_y
[j
-1] + dy
;
294 // find the first point that is right ahead of the newly added point
295 // so: loop through points that lay within MAX_DISTANCE, and find the first one that is right past delta.
297 for (k
=0; k
<n_ahead
; k
++) {
298 if (distances
[k
] > delta
) {
303 // correct for the case of unseen point (because all MAX_POINTS ahead lay under delta):
306 i
= ahead
[n_ahead
-1] +1; //the one after the last.
307 if (i
>= p_length
) i
= i
- p_length
;
312 //advance index in the new points
314 } // end of for (;;) loop
316 // see whether the subsampling terminated too early, and fill with a line of points.
317 // TODO this is sort of a patch. Why didn't j overcome the last point is not resolved.
321 lastx
= x
[p_length
-1];
322 lasty
= y
[p_length
-1];
324 dist1
= Math
.sqrt((lastx
- ps_x
[j
-1])*(lastx
- ps_x
[j
-1]) + (lasty
- ps_y
[j
-1])*(lasty
- ps_y
[j
-1]));
325 if (dist1
> delta
*1.2) {
326 // TODO needs revision
327 // System.out.println("resampling terminated too early. Why?");
328 angleXY
= M
.getAngle(lastx
- ps_x
[j
-1], lasty
- ps_y
[j
-1]);
329 dx
= Math
.cos(angleXY
) * delta
;
330 dy
= Math
.sin(angleXY
) * delta
;
331 while (dist1
> delta
*1.2) {//added 1.2 to prevent last point from being generated too close to the first point
332 // check ps and v array lengths
333 if (j
>= ps_length
) {
336 v_x
= Utils
.copy(v_x
, ps_length
);
337 v_y
= Utils
.copy(v_y
, ps_length
);
338 ps_x
= Utils
.copy(ps_x
, ps_length
);
339 ps_y
= Utils
.copy(ps_y
, ps_length
);
342 ps_x
[j
] = ps_x
[j
-1] + dx
;
343 ps_y
[j
] = ps_y
[j
-1] + dy
;
347 dist1
= Math
.sqrt((lastx
- ps_x
[j
-1])*(lastx
- ps_x
[j
-1]) + (lasty
- ps_y
[j
-1])*(lasty
- ps_y
[j
-1]));
350 // set vector 0 to be the vector from the last point to the first // TODO also for non-closed?
351 angleXY
= M
.getAngle(lastx
- ps_x
[j
-1], lasty
- ps_y
[j
-1]);
352 //v_x[0] = Math.cos(angle) * delta;
353 //v_y[0] = Math.sin(angle) * delta; // can't use delta, it may be too long and thus overtake the first point!
354 v_x
[0] = Math
.cos(angleXY
) * dist1
;
355 v_y
[0] = Math
.sin(angleXY
) * dist1
;
357 // assign the new subsampled points
360 // assign the vectors
363 // j is now the length of the ps_x, ps_y, v_x and v_y arrays (i.e. the number of resampled points).
369 private final double[] recalculate(final double[] w
, final int length
, final double sum_
) {
372 for (q
=0; q
<length
; q
++) {
376 double error
= 1.0 - sum
;
377 // make it be an absolute value
383 } else if (sum
> 1.0) {
384 return recalculate(w
, length
, sum
);
389 public void reorder(final int new_zero
) { // this function is optimized for speed: no array duplications beyond minimally necessary, and no superfluous method calls.
392 double[] tmp
= new double[this.length
];
396 for (i
=0, j
=new_zero
; j
<length
; i
++, j
++) { tmp
[i
] = src
[j
]; }
397 for (j
=0; j
<new_zero
; i
++, j
++) { tmp
[i
] = src
[j
]; }
402 for (i
=0, j
=new_zero
; j
<length
; i
++, j
++) { tmp
[i
] = src
[j
]; }
403 for (j
=0; j
<new_zero
; i
++, j
++) { tmp
[i
] = src
[j
]; }
408 for (i
=0, j
=new_zero
; j
<length
; i
++, j
++) { tmp
[i
] = src
[j
]; }
409 for (j
=0; j
<new_zero
; i
++, j
++) { tmp
[i
] = src
[j
]; }
414 for (i
=0, j
=new_zero
; j
<length
; i
++, j
++) { tmp
[i
] = src
[j
]; }
415 for (j
=0; j
<new_zero
; i
++, j
++) { tmp
[i
] = src
[j
]; }
419 // equivalent would be:
420 //System.arraycopy(src, new_zero, tmp, 0, m - new_zero);
421 //System.arraycopy(src, 0, tmp, m - new_zero, new_zero);
429 /** Subtracts vs2 vector j to this vector i and returns its length, without changing any data. */
430 public double getDiffVectorLength(final int i
, final int j
, final VectorString vs2
) {
431 final VectorString2D vs
= (VectorString2D
)vs2
;
432 final double dx
= v_x
[i
] - vs
.v_x
[j
];
433 final double dy
= v_y
[i
] - vs
.v_y
[j
];
434 return Math
.sqrt(dx
*dx
+ dy
*dy
);
437 /** Distance from point i in this to point j in vs2. */
438 public double distance(int i
, VectorString vs
, int j
) {
439 VectorString2D vs2
= (VectorString2D
)vs
;
440 return distance(x
[i
], y
[i
],
444 static public double distance(double x1
, double y1
,
445 double x2
, double y2
) {
446 return Math
.sqrt(Math
.pow(x1
- x2
, 2)
447 + Math
.pow(y1
- y2
, 2));
450 /** Create a new VectorString for the given range. If last < first, it will be created as reversed. */
451 public VectorString
subVectorString(int first
, int last
) throws Exception
{
452 boolean reverse
= false;
459 int len
= last
- first
+ 1;
460 double[] x
= new double[len
];
461 double[] y
= new double[len
];
462 System
.arraycopy(this.x
, first
, x
, 0, len
);
463 System
.arraycopy(this.y
, first
, y
, 0, len
);
464 final VectorString2D vs
= new VectorString2D(x
, y
, this.z
, this.closed
);
465 if (reverse
) vs
.reverse();
466 if (null != this.v_x
) {
467 // this is resampled, so:
468 vs
.delta
= this.delta
;
470 vs
.v_x
= new double[len
];
471 vs
.v_y
= new double[len
];
472 for (int i
=1; i
<len
; i
++) {
473 vs
.v_x
[i
] = vs
.x
[i
] - vs
.x
[i
-1];
474 vs
.v_y
[i
] = vs
.y
[i
] - vs
.y
[i
-1];
480 /** Invert the order of points. Will clear all vector arrays if any! */
481 public void reverse() {
485 if (null != v_x
) v_x
= v_y
= null;
488 public int getDimensions() { return 2; }
490 /** Scale to match cal.pixelWidth, cal.pixelHeight and computed depth. If cal is null, returns immediately. Will make all vectors null, so you must call resample(delta) again after calibrating. So it brings all values to cal.units, such as microns. */
491 public void calibrate(final Calibration cal
) {
492 if (null == cal
) return;
494 final int sign
= cal
.pixelDepth
< 0 ?
- 1 :1;
495 for (int i
=0; i
<x
.length
; i
++) {
496 x
[i
] *= cal
.pixelWidth
;
497 y
[i
] *= cal
.pixelHeight
; // should be the same as pixelWidth
499 z
*= cal
.pixelWidth
* sign
; // since layer Z is in pixels.
501 if (null != v_x
) v_x
= v_y
= null;
505 public boolean isCalibrated() {
506 return null != this.cal
;
508 public Calibration
getCalibrationCopy() {
509 return null == this.cal ?
null : this.cal
.copy();