preparing release pom-trakem2-2.0.0, VectorString-2.0.0, TrakEM2_-1.0h
[trakem2.git] / VectorString / src / main / java / ini / trakem2 / vector / VectorString3D.java
blob9bce21994f113d2605300f6bf04ed61810dfa300
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2007-2009 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.
21 **/
23 package ini.trakem2.vector;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Random;
29 import org.scijava.java3d.Transform3D;
30 import org.scijava.vecmath.Point3d;
31 import org.scijava.vecmath.Vector3d;
33 import Jama.Matrix;
34 import ij.measure.Calibration;
37 public class VectorString3D implements VectorString {
39 /** Points. */
40 private double[] x, y, z;
41 /** Vectors, after resampling. */
42 private double[] vx, vy, vz;
43 /** Relative vectors, after calling 'relative()'. */
44 private double[] rvx, rvy, rvz;
45 /** Length of points and vectors - since arrays may be a little longer. */
46 private int length = 0;
47 /** The point interdistance after resampling. */
48 private double delta = 0;
50 /** Bits: 000CRXYZ where C=closed, R=reversed, X=mirrored in X, Y=mirrored in Y, Z=mirrored in Z. */
51 private byte tags = 0;
52 // masks
53 static public final byte CLOSED = (1<<4);
54 static public final byte REVERSED = (1<<3);
55 static public final byte MIRROR_X = (1<<2);
56 static public final byte MIRROR_Y = (1<<1);
57 static public final byte MIRROR_Z = 1;
59 private Calibration cal = null;
61 /** Dependent arrays that will get resampled along. */ // TODO for calibration pruposes, they should be associated with a specific x, y, or z
62 private double[][] dep;
64 // DANGER: the orientation of the curve can't be checked like in 2D. There is no up and down in the 3D space.
65 /** A list of lists of Point3d, each one containing the points from which the points in this VectorString3D come from. */
66 private ArrayList<ArrayList<Point3d>> source = null;
67 /** The number of VectorString3D that have contributed to this one, via createInterpolated and others. */
68 private int n_sources = 1;
70 public VectorString3D(final double[] x, final double[] y, final double[] z, final boolean closed) throws Exception {
71 if (!(x.length == y.length && x.length == z.length)) throw new Exception("x,y,z must have the same length.");
72 this.length = x.length;
73 this.x = x;
74 this.y = y;
75 this.z = z;
76 if (closed) this.tags ^= CLOSED;
79 /** Add an array that will get resampled along; must be of the same length as the value returned by length() */
80 public void addDependent(final double[] a) throws Exception {
81 if (a.length != this.length) throw new Exception("Dependent array must be of the same size as thevalue returned by length()");
82 if (null == dep) {
83 dep = new double[1][];
84 dep[0] = a;
85 } else {
86 // resize and append
87 final double[][] dep2 = new double[dep.length + 1][];
88 for (int i=0; i<dep.length; i++) dep2[i] = dep[i];
89 dep2[dep.length] = a;
90 dep = dep2;
93 public double[] getDependent(final int i) {
94 return dep[i];
97 /** Return the average point interdistance. */
98 public double getAverageDelta() {
99 double d = 0;
100 for (int i=length -1; i>0; i--) {
101 d += Math.sqrt( Math.pow(x[i] - x[i-1], 2) + Math.pow(y[i] - y[i-1], 2) + Math.pow(z[i] - z[i-1], 2));
103 return d / length;
106 /** If not resampled, the returned delta is zero. */
107 @Override
108 public double getDelta() { return delta; }
111 @Override
112 public void resample(final double delta) {
113 resample(delta, false);
116 /** Homogenize the average point interdistance to 'delta'.
117 * If delta is the same as the desired, it WILL RETURN NULL even if with_source is true.
118 * If with_source, then returns the list of lists of points that contributed to each resampled point.
120 @Override
121 public void resample(final double delta, final boolean with_source) {
122 if (Math.abs(delta - this.delta) < 0.0000001) {
123 // delta is the same
124 return;
126 this.delta = delta; // store for checking purposes
127 this.resample(with_source);
130 /** The length of this string, that is, the number of points (and vectors) in it. */
131 @Override
132 public final int length() { return length; }
133 @Override
134 public double[] getPoints(final int dim) {
135 switch (dim) {
136 case 0: return x;
137 case 1: return y;
138 case 2: return z;
140 return null;
142 @Override
143 public double[] getVectors(final int dim) {
144 switch (dim) {
145 case 0: return vx;
146 case 1: return vy;
147 case 2: return vz;
149 return null;
151 @Override
152 public double getPoint(final int dim, final int i) {
153 switch (dim) {
154 case 0: return x[i];
155 case 1: return y[i];
156 case 2: return z[i];
158 return 0;
160 @Override
161 public double getVector(final int dim, final int i) {
162 switch (dim) {
163 case 0: return vx[i];
164 case 1: return vy[i];
165 case 2: return vy[i];
167 return 0;
169 public double getRelativeVector(final int dim, final int i) {
170 switch (dim) {
171 case 0: return rvx[i];
172 case 1: return rvy[i];
173 case 2: return rvy[i];
175 return 0;
178 @Override
179 public final boolean isClosed() {
180 return 0 != (tags & CLOSED);
183 public void debug() {
184 System.out.println("#### " + getClass().getName() + " ####");
185 for (int i=0; i<x.length; i++) {
186 System.out.println( i + ": " + x[i] + "," + y[i] + ", " + z[i]);
188 System.out.println("#### END ####");
192 private class ResamplingData {
194 private double[] rx, ry, rz,
195 vx, vy, vz;
196 private double[][] dep;
198 /** Initialize with a starting length. */
199 ResamplingData(final int length, final double[][] dep) {
200 // resampled points
201 rx = new double[length];
202 ry = new double[length];
203 rz = new double[length];
204 // vectors
205 vx = new double[length];
206 vy = new double[length];
207 vz = new double[length];
208 // dependents
209 if (null != dep) this.dep = new double[dep.length][length];
211 /** Arrays are enlarged if necessary.*/
212 final void setP(final int i, final double xval, final double yval, final double zval) {
213 if (i >= rx.length) resize(i+10);
214 this.rx[i] = xval;
215 this.ry[i] = yval;
216 this.rz[i] = zval;
218 /** Arrays are enlarged if necessary.*/
220 final void setV(final int i, final double xval, final double yval, final double zval) {
221 if (i >= rx.length) resize(i+10);
222 this.vx[i] = xval;
223 this.vy[i] = yval;
224 this.vz[i] = zval;
227 /** Arrays are enlarged if necessary.*/
228 final void setPV(final int i, final double rxval, final double ryval, final double rzval, final double xval, final double yval, final double zval) {
229 if (i >= rx.length) resize(i+10);
230 this.rx[i] = rxval;
231 this.ry[i] = ryval;
232 this.rz[i] = rzval;
233 this.vx[i] = xval;
234 this.vy[i] = yval;
235 this.vz[i] = zval;
237 final void resize(final int new_length) {
238 this.rx = Util.copy(this.rx, new_length);
239 this.ry = Util.copy(this.ry, new_length);
240 this.rz = Util.copy(this.rz, new_length);
241 this.vx = Util.copy(this.vx, new_length);
242 this.vy = Util.copy(this.vy, new_length);
243 this.vz = Util.copy(this.vz, new_length);
244 if (null != dep) {
245 // java doesn't have generators! ARGH
246 final double[][] dep2 = new double[dep.length][];
247 for (int i=0; i<dep.length; i++) dep2[i] = Util.copy(dep[i], new_length);
248 dep = dep2;
251 final double x(final int i) { return rx[i]; }
252 final double y(final int i) { return ry[i]; }
253 final double z(final int i) { return rz[i]; }
254 /** Distance from point rx[i],ry[i], rz[i] to point x[j],y[j],z[j] */
255 final double distance(final int i, final double x, final double y, final double z) {
256 return Math.sqrt(Math.pow(x - rx[i], 2)
257 + Math.pow(y - ry[i], 2)
258 + Math.pow(z - rz[i], 2));
260 final void put(final VectorString3D vs, final int length) {
261 vs.x = Util.copy(this.rx, length); // crop away empty slots
262 vs.y = Util.copy(this.ry, length);
263 vs.z = Util.copy(this.rz, length);
264 vs.vx = Util.copy(this.vx, length);
265 vs.vy = Util.copy(this.vy, length);
266 vs.vz = Util.copy(this.vz, length);
267 vs.length = length;
268 if (null != dep) {
269 vs.dep = new double[dep.length][];
270 for (int i=0; i<dep.length; i++) vs.dep[i] = Util.copy(dep[i], length);
273 final void setDeps(final int i, final double[][] src_dep, final int[] ahead, final double[] weight, final int len) {
274 if (null == dep) return;
275 if (i >= rx.length) resize(i+10);
277 for (int k=0; k<dep.length; k++) {
278 for (int j=0; j<len; j++) {
279 dep[k][i] += src_dep[k][ahead[j]] * weight[j];
281 } // above, the ahead and weight arrays (which have the same length) could be of larger length than the given 'len', thus len is used.
285 static class Vector {
286 private double x, y, z;
287 private double length;
288 // 0 coords and 0 length, virtue of the 'calloc'
289 Vector() {}
290 Vector(final double x, final double y, final double z) {
291 set(x, y, z);
293 Vector(final Vector v) {
294 this.x = v.x;
295 this.y = v.y;
296 this.z = v.z;
297 this.length = v.length;
299 @Override
300 final public Object clone() {
301 return new Vector(this);
303 final void set(final double x, final double y, final double z) {
304 this.x = x;
305 this.y = y;
306 this.z = z;
307 this.length = computeLength();
309 final void normalize() {
310 if (0 == length) return;
311 // check if length is already 1
312 if (Math.abs(1 - length) < 0.00000001) return; // already normalized
313 this.x /= length;
314 this.y /= length;
315 this.z /= length;
316 this.length = computeLength(); // should be 1
318 final double computeLength() {
319 return Math.sqrt(x*x + y*y + z*z);
321 final double length() {
322 return length;
324 final void scale(final double factor) {
325 this.x *= factor;
326 this.y *= factor;
327 this.z *= factor;
328 this.length = computeLength();
330 final void add(final Vector v, final boolean compute_length) {
331 this.x += v.x;
332 this.y += v.y;
333 this.z += v.z;
334 if (compute_length) this.length = computeLength();
336 final void setLength(final double len) {
337 normalize();
338 scale(len);
340 final void put(final int i, final ResamplingData r) {
341 r.setPV(i, r.x(i-1) + this.x, r.y(i-1) + this.y, r.z(i-1) + this.z, this.x, this.y, this.z);
343 /** As row. */
344 final void put(final double[] d) {
345 d[0] = x;
346 d[1] = y;
347 d[2] = z;
349 /** As column. */
350 final void put(final double[][] d, final int col) {
351 d[0][col] = x;
352 d[1][col] = y;
353 d[2][col] = z;
355 final void put(final int i, final double[] x, final double[] y, final double[] z) {
356 x[i] = this.x;
357 y[i] = this.y;
358 z[i] = this.z;
360 final Vector getCrossProduct(final Vector v) {
361 // (a1; a2; a3) x (b1; b2; b3) = (a2b3 - a3b2; a3b1 - a1b3; a1b2 - a2b1)
362 return new Vector(y * v.z - z * v.y,
363 z * v.x - x * v.z,
364 x * v.y - y * v.x);
366 final void setCrossProduct(final Vector v, final Vector w) {
367 this.x = v.y * w.z - v.z * w.y;
368 this.y = v.z * w.x - v.x * w.z;
369 this.z = v.x * w.y - v.y * w.x;
371 /** Change coordinate system. */
372 final void changeRef(final Vector v_delta, final Vector v_i1, final Vector v_new1) { // this vector works like new2
373 // ortogonal system 1: the target
374 // (a1'; a2'; a3')
375 final Vector a2 = new Vector( v_new1 ); // vL
376 a2.normalize();
377 final Vector a1 = a2.getCrossProduct(v_i1); // vQ
378 a1.normalize();
379 final Vector a3 = a2.getCrossProduct(a1);
380 // no need //a3.normalize();
382 final double[][] m1 = new double[3][3];
383 a1.put(m1, 0);
384 a2.put(m1, 1);
385 a3.put(m1, 2);
386 final Matrix mat1 = new Matrix(m1);
388 // ortogonal system 2: the current
389 // (a1'; b2'; b3')
390 final Vector b2 = new Vector( v_delta ); // vA
391 b2.normalize();
392 final Vector b3 = a1.getCrossProduct(b2); // vQ2
394 final double[][] m2 = new double[3][3];
395 a1.put(m2, 0);
396 b2.put(m2, 1);
397 b3.put(m2, 2);
398 final Matrix mat2 = new Matrix(m2).transpose();
400 final Matrix R = mat1.times(mat2);
401 final Matrix mthis = new Matrix(new double[]{this.x, this.y, this.z}, 1);
402 // The rotated vector as a one-dim matrix
403 // (i.e. the rescued difference vector as a one-dimensional matrix)
404 final Matrix v_rot = R.transpose().times(mthis.transpose()); // 3x3 times 3x1, hence the transposing of the 1x3
405 final double[][] arr = v_rot.getArray();
406 // done!
407 this.x = arr[0][0];
408 this.y = arr[1][0];
409 this.z = arr[2][0];
411 final Point3d asPoint3d() {
412 return new Point3d(x, y, z);
416 private final void recalculate(final double[] w, final int length, final double sum_) {
417 double sum = 0;
418 int q;
419 for (q=0; q<length; q++) {
420 w[q] = w[q] / sum_;
421 sum += w[q];
423 double error = 1.0 - sum;
424 // make it be an absolute value
425 if (error < 0.0) {
426 error = -error;
428 if (error < 0.005) {
429 w[0] += 1.0 - sum;
430 } else if (sum > 1.0) {
431 recalculate(w, length, sum);
435 /** If argument with_source is true, then returns an ArrayList of ArrayList of Point3d, with lists of all points that contributed to each point. */
436 private void resample(final boolean with_source) {
437 // parameters
438 final double MAX_DISTANCE = 2.5 * delta;
439 final int MA = (int)(MAX_DISTANCE / getAverageDelta());
440 final int MAX_AHEAD = MA < 6 ? 6 : MA;
442 // convenient data carrier and editor
443 final ResamplingData r = new ResamplingData(this.length, this.dep);
444 final Vector vector = new Vector();
446 // The source points that generate each point:
447 if (with_source && null == this.source) {
448 // add all original points to a new list of lists (so that many VectorString3D may be combined, while keeping all source points for each final point)
449 this.source = new ArrayList<ArrayList<Point3d>>();
450 for (int g=0; g<length; g++) {
451 final ArrayList<Point3d> ap = new ArrayList<Point3d>();
452 ap.add(new Point3d(x[g], y[g], z[g]));
453 this.source.add(ap);
457 final ArrayList<ArrayList<Point3d>> new_source = with_source ?
458 new ArrayList<ArrayList<Point3d>>() : null;
461 // first resampled point is the same as point zero
462 r.setP(0, x[0], y[0], z[0]);
463 r.setDeps(0, dep, new int[]{0}, new double[]{1.0}, 1);
464 // the first vector is 0,0,0 unless the path is closed, in which case it contains the vector from last-to-first.
466 if (with_source) new_source.add(new ArrayList<Point3d>(this.source.get(0)));
468 // index over x,y,z
469 int i = 1;
470 // index over rx,ry,rz (resampled points)
471 int j = 1;
472 // some vars
473 int t, s, ii, u, iu, k;
474 int prev_i = i;
475 double dist_ahead, dist1, dist2, sum;
476 final double[] w = new double[MAX_AHEAD];
477 final double[] distances = new double[MAX_AHEAD];
478 final Vector[] ve = new Vector[MAX_AHEAD];
479 int next_ahead;
480 for (next_ahead = 0; next_ahead < MAX_AHEAD; next_ahead++) ve[next_ahead] = new Vector();
481 final int[] ahead = new int[MAX_AHEAD];
483 try {
485 // start infinite loop
486 for (;prev_i <= i;) {
487 if (prev_i > i || (!isClosed() && i == this.length -1)) break;
488 // get distances of MAX_POINTs ahead from the previous point
489 next_ahead = 0;
490 for (t=0; t<MAX_AHEAD; t++) {
491 s = i + t;
492 // fix 's' if it goes over the end
493 if (s >= this.length) {
494 if (isClosed()) s -= this.length;
495 else break;
497 dist_ahead = r.distance(j-1, x[s], y[s], z[s]);
498 if (dist_ahead < MAX_DISTANCE) {
499 ahead[next_ahead] = s;
500 distances[next_ahead] = dist_ahead;
501 next_ahead++;
504 if (0 == next_ahead) {
505 // No points (ahead of the i point) are found within MAX_DISTANCE
506 // Just use the next point as target towards which create a vector of length delta
507 vector.set(x[i] - r.x(j-1), y[i] - r.y(j-1), z[i] - r.z(j-1));
508 dist1 = vector.length();
509 vector.setLength(delta);
510 vector.put(j, r);
512 if (with_source) {
513 // shallow clone: shared Point3d instances (but no shared lists)
514 new_source.add(new ArrayList<Point3d>(this.source.get(i))); // the next point is 'i'
517 if (null != dep) r.setDeps(j, dep, new int[]{i}, new double[]{1.0}, 1);
519 //System.out.println("j: " + j + " (ZERO) " + vector.computeLength() + " " + vector.length());
521 //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:
522 if (dist1 <= delta) {
523 //look for a point ahead that is over distance delta from the previous j, so that it will lay ahead of the current j
524 for (u=i; u<this.length; u++) {
525 dist2 = Math.sqrt(Math.pow(x[u] - r.x(j-1), 2)
526 + Math.pow(y[u] - r.y(j-1), 2)
527 + Math.pow(z[u] - r.z(j-1), 2));
528 if (dist2 > delta) {
529 prev_i = i;
530 i = u;
531 break;
535 } else {
536 // Compose a point ahead out of the found ones.
538 // First, adjust weights for the points ahead
539 w[0] = distances[0] / MAX_DISTANCE;
540 double largest = w[0];
541 for (u=1; u<next_ahead; u++) {
542 w[u] = 1 - (distances[u] / MAX_DISTANCE);
543 if (w[u] > largest) {
544 largest = w[u];
547 // normalize weights: divide by largest
548 sum = 0;
549 for (u=0; u<next_ahead; u++) {
550 w[u] = w[u] / largest;
551 sum += w[u];
553 // correct error. The closest point gets the extra
554 if (sum < 1.0) {
555 w[0] += 1.0 - sum;
556 } else {
557 recalculate(w, next_ahead, sum);
559 // Now, make a vector for each point with the corresponding weight
560 vector.set(0, 0, 0);
562 final ArrayList<Point3d> ap = with_source ? new ArrayList<Point3d>() : null;
564 for (u=0; u<next_ahead; u++) {
565 iu = i + u;
566 if (iu >= this.length) iu -= this.length;
567 ve[u].set(x[iu] - r.x(j-1), y[iu] - r.y(j-1), z[iu] - r.z(j-1));
568 ve[u].setLength(w[u] * delta);
569 vector.add(ve[u], u == next_ahead-1); // compute the length only on the last iteration
570 if (with_source) {
571 ap.addAll(this.source.get(iu));
575 if (with_source) new_source.add(ap);
577 // correct potential errors
578 if (Math.abs(vector.length() - delta) > 0.00000001) {
579 vector.setLength(delta);
581 // set
582 vector.put(j, r);
583 if (null != dep) r.setDeps(j, dep, ahead, w, next_ahead);
585 //System.out.println("j: " + j + " (" + next_ahead + ") " + vector.computeLength() + " " + vector.length());
588 // find the first point that is right ahead of the newly added point
589 // so: loop through points that lay within MAX_DISTANCE, and find the first one that is right past delta.
590 ii = i;
591 for (k=0; k<next_ahead; k++) {
592 if (distances[k] > delta) {
593 ii = ahead[k];
594 break;
597 // correct for the case of unseen point (because all MAX_POINTS ahead lay under delta):
598 prev_i = i;
599 if (i == ii) {
600 i = ahead[next_ahead-1] +1; //the one after the last.
601 if (i >= this.length) {
602 if (isClosed()) i = i - this.length; // this.length is the length of the x,y,z, the original points
603 else i = this.length -1;
605 } else {
606 i = ii;
609 //advance index in the new points
610 j += 1;
611 } // end of for loop
613 } catch (final Exception e) {
614 e.printStackTrace();
615 System.out.println("Some data: x,y,z .length = " + x.length + "," + y.length + "," + z.length
616 + "\nj=" + j + ", i=" + i + ", prev_i=" + prev_i
621 dist_ahead = r.distance(j-1, x[this.length-1], y[this.length-1], z[this.length-1]);
623 //System.out.println("delta: " + delta + "\nlast point: " + x[x.length-1] + ", " + y[y.length-1] + ", " + z[z.length-1]);
624 //System.out.println("last resampled point: x,y,z " + r.x(j-1) + ", " + r.y(j-1) + ", " + r.z(j-1));
625 //System.out.println("distance: " + dist_ahead);
627 // see whether the subsampling terminated too early, and fill with a line of points.
628 final int last_i = isClosed() ? 0 : this.length -1;
629 if (dist_ahead > delta*1.2) {
630 //TODO//System.out.println("resampling terminated too early. Why?");
631 while (dist_ahead > delta*1.2) {
632 // make a vector from the last resampled point to the last point
633 vector.set(x[last_i] - r.x(j-1), y[last_i] - r.y(j-1), z[last_i] - r.z(j-1));
634 // resize it to length delta
635 vector.setLength(delta);
636 vector.put(j, r);
637 if (with_source) new_source.add(new ArrayList<Point3d>(this.source.get(last_i)));
638 j++;
639 dist_ahead = r.distance(j-1, x[last_i], y[last_i], z[last_i]);
642 // done!
643 r.put(this, j); // j acts as length of resampled points and vectors
644 // Above, 'r' sets the length to j as well.
646 // vector at zero is left as 0,0 which makes no sense. Should be the last point that has no vector, or has it only in the event that the list of points is declared as closed: a vector to the first point. Doesn't really matter though, as long as it's clear: as of right now, the first point has no vector unless the path is closed, in which case it contains the vector from the last-to-first.
648 if (with_source) this.source = new_source;
650 //System.out.println("resampling with_source: " + with_source + " n_sources = " + n_sources);
652 // debug:
653 //if (with_source && j != new_source.size()) System.out.println("WARNING: len: " + j + ", sources length: " + new_source.size());
654 //if (with_source && n_sources > 1) System.out.println("n_sources=" + n_sources + " lengths: " + j + ", " + new_source.size());
657 /** Returns a list of lists of Point3d, one list of lists for each point in this VectorString3D; may be null. */
658 public ArrayList<ArrayList<Point3d>> getSource() { return this.source; }
659 /** Returns the number of VectorString3D that have contributed to this VectorString3D, via merging with createInterpolated(...). */
660 public int getNSources() { return n_sources; }
662 /** Reorder the arrays so that the index zero becomes new_zero -the arrays are circular. */
663 @Override
664 public void reorder(final int new_zero) {
665 int i, j;
666 // copying
667 double[] tmp = new double[this.length];
668 double[] src;
669 // x
670 src = x;
671 for (i=0, j=new_zero; j<length; i++, j++) { tmp[i] = src[j]; }
672 for (j=0; j<new_zero; i++, j++) { tmp[i] = src[j]; }
673 x = tmp;
674 tmp = src;
675 // y
676 src = y;
677 for (i=0, j=new_zero; j<length; i++, j++) { tmp[i] = src[j]; }
678 for (j=0; j<new_zero; i++, j++) { tmp[i] = src[j]; }
679 y = tmp;
680 tmp = src;
681 // z
682 src = z;
683 for (i=0, j=new_zero; j<length; i++, j++) { tmp[i] = src[j]; }
684 for (j=0; j<new_zero; i++, j++) { tmp[i] = src[j]; }
685 z = tmp;
686 tmp = src;
687 // vx
688 src = vx;
689 for (i=0, j=new_zero; j<length; i++, j++) { tmp[i] = src[j]; }
690 for (j=0; j<new_zero; i++, j++) { tmp[i] = src[j]; }
691 vx = tmp;
692 tmp = src;
693 // vy
694 src = vy;
695 for (i=0, j=new_zero; j<length; i++, j++) { tmp[i] = src[j]; }
696 for (j=0; j<new_zero; i++, j++) { tmp[i] = src[j]; }
697 vy = tmp;
698 tmp = src;
699 // vz
700 src = vz;
701 for (i=0, j=new_zero; j<length; i++, j++) { tmp[i] = src[j]; }
702 for (j=0; j<new_zero; i++, j++) { tmp[i] = src[j]; }
703 vz = tmp;
704 tmp = src;
707 /** Subtracts vs2 vector j to this vector i and returns its length, without changing any data. */
708 @Override
709 public double getDiffVectorLength(final int i, final int j, final VectorString vsb) {
710 final VectorString3D vs = (VectorString3D)vsb;
711 if (null == rvx || null == rvy || null == rvz) {
712 // use absolute vectors
713 final double dx = vx[i] - vs.vx[j];
714 final double dy = vy[i] - vs.vy[j];
715 final double dz = vz[i] - vs.vz[j];
716 return Math.sqrt(dx*dx + dy*dy + dz*dz);
717 } else {
718 // use relative vectors
719 final double dx = rvx[i] - vs.rvx[j];
720 final double dy = rvy[i] - vs.rvy[j];
721 final double dz = rvz[i] - vs.rvz[j];
722 return Math.sqrt(dx*dx + dy*dy + dz*dz);
726 @Override
727 public Object clone() {
728 try {
729 final VectorString3D vs = new VectorString3D(Util.copy(x, length), Util.copy(y, length), Util.copy(z, length), isClosed());
730 vs.delta = delta;
731 if (null != vx) vs.vx = Util.copy(vx, length);
732 if (null != vy) vs.vy = Util.copy(vy, length);
733 if (null != vz) vs.vz = Util.copy(vz, length);
734 if (null != rvx) vs.rvx = Util.copy(rvx, length);
735 if (null != rvy) vs.rvy = Util.copy(rvy, length);
736 if (null != rvz) vs.rvz = Util.copy(rvz, length);
737 if (null != source) {
738 // shallow clone the source points
739 vs.source = new ArrayList<ArrayList<Point3d>>();
740 for (final ArrayList<Point3d> ap : this.source) {
741 vs.source.add(new ArrayList<Point3d>(ap));
744 vs.tags = this.tags;
745 vs.cal = null == this.cal ? null : this.cal.copy();
746 return vs;
747 } catch (final Exception e) {
748 e.printStackTrace();
750 return null;
753 private ArrayList<ArrayList<Point3d>> cloneSource(final int first, final int last) {
754 if (null == source) return null;
755 final ArrayList<ArrayList<Point3d>> s = new ArrayList<ArrayList<Point3d>>();
756 int i = 0;
757 for (final ArrayList<Point3d> ap : source) { // looping with get(i) would be paint-bucket problem
758 if (i < first) continue;
759 if (i > last) break;
760 s.add(new ArrayList<Point3d>(ap));
761 i++;
763 return s;
766 public VectorString3D makeReversedCopy() {
767 try {
768 final VectorString3D vs = new VectorString3D(Util.copy(x, length), Util.copy(y, length), Util.copy(z, length), isClosed());
769 vs.source = cloneSource(0, length-1);
770 vs.reverse();
771 if (delta != 0 && null != vx) {
772 vs.delta = this.delta;
773 vs.resample(false);
774 if (null != rvx) {
775 vs.relative();
778 return vs;
779 } catch (final Exception e) {
780 e.printStackTrace();
782 return null;
785 /** Create the relative vectors, that is, the differences of one vector to the next. In this way, vectors represent changes in the path and are independent of the actual path orientation in space. */
786 public void relative() {
787 // create vectors if not there yet
788 if (null == vx || null == vy || null == vz) {
789 resample(getAverageDelta());
791 rvx = new double[length];
792 rvy = new double[length];
793 rvz = new double[length];
796 // TODO 2: also, it should test both orientations and select the best
798 // TODO: there is a mistake. Both vectors, i and i-1, have to be rotated so that vector i becomes aligned with whatever axis, as long as all vectors get aligned to the same. In this way then one truly stores differences only. An easy approach is to store the angle difference, since then it will be a scalar value independent of the orientation of the vectors. As long as the angle is represented with a vector, fine.
799 // Another alternative is, if both vectors are rotated, then the first is always on the axis and the second has the "difference" information already as is.
800 // So for a vector c1,c2,c3 of length L, I want it as L,0,0 (for example) and then the second vector, as rotated, is the one to use as relative.
801 // So: v[i-1], vL, and v[i]; then v[i-1] + W = vL, so W = vL - v[i-1], and then the diff vector is v[i] + W
803 //double wx, wy, wz; // the W
804 //double vLx; // vLy = vLz = 0
806 // the first vector:
807 if (isClosed()) { // last to first
808 rvx[0] = vx[0] + delta - vx[length-1];
809 rvy[0] = vy[0] - vy[length-1];
810 rvz[0] = vz[0] - vz[length-1];
811 } // else, as open curve the first vector remains 0,0,0
812 // fill in the rest:
815 final Vector vL = new Vector();
816 final Vector vA = new Vector();
817 final Vector vQ = new Vector();
818 final Vector vQQ = new Vector();
819 final Vector vQ2 = new Vector();
820 final double[][] m1 = new double[3][3];
821 final double[][] m2 = new double[3][3];
823 for (int i=1; i<length; i++) {
824 // So: Johannes Schindelin said: vector Y is the rotated, X is the original
825 // Y = A + R * (X - A) where R is a rotation matrix (A is the displacement vector, 0,0,0 in this case)
826 // so the axis of rotation is the cross product of {L,0,0} and i-1
827 // (a1; a2; a3) x (b1; b2; b3) = (a2b3 - a3b2; a3b1 - a1b3; a1b2 - a2b1)
828 //Vector axis = new Vector(0 - 0, 0 - delta * vz[i-1], delta * vy[i-1] - 0);
829 vL.set(delta, 0, 0);
830 vL.normalize(); // this line can be reduced to new Vector(1,0,0);
831 vA.set(vx[i-1], vy[i-1], vz[i-1]);
832 vA.normalize();
833 vQ.setCrossProduct(vA, vL);
834 //vQ.normalize(); // no need: the cross product of two normalized vectors is also a normalized vector
835 vQQ.setCrossProduct(vQ, vL);
836 //vQQ.normalize(); // no need
838 // the matrices expect the vectors as columns, not as rows! ARGH! Solved.
840 // now, vQ,vL,vQQ define an orthogonal system
841 // (vQ'; vL'; vQ' x vL')
842 vQ.put(m1, 0);
843 vL.put(m1, 1);
844 vQQ.put(m1, 2);
845 final Matrix mat1 = new Matrix(m1);
847 // (vQ'; vA'; vQ' x vA')^T // Johannes wrote vB to mean vA. For me vB is the second vector
848 vQ.put(m2, 0);
849 vA.put(m2, 1);
850 vQ2.setCrossProduct(vQ, vA);
851 //vQ2.normalize(); // no need
852 vQ2.put(m2, 2);
853 final Matrix mat2 = new Matrix(m2).transpose();
855 final Matrix R = mat1.times(mat2);
856 // the difference vector as a one-dimensional matrix
857 final Matrix vB = new Matrix(new double[]{vx[i] - vx[i-1], vy[i] - vy[i-1], vz[i] - vz[i-1]}, 1);
858 final Matrix vB_rot = R.transpose().times(vB.transpose()); // 3x3 times 3x1, hence the transposing of the 1x3 vector so that the inner lengths are the same
859 final double[][] arr = vB_rot.getArray();
860 rvx[i] = arr[0][0];
861 rvy[i] = arr[1][0];
862 rvz[i] = arr[2][0];
863 //System.out.println("rv{x,y,z}[" + i + "]= " + rvx[i] + ", " + rvy[i] + ", " + rvz[i]);
867 /** Expand or shrink the points in this 3D path so that the average point interdistance becomes delta.
869 * This method is intended to enhance the comparison of two paths in 3D space which may be similar but differ greatly in dimensions; for example, secondary lineage tracts in 3rd instar Drosophila larvae versus adult.
871 public void equalize(final double target_delta) {
872 if (length < 2) return;
873 final double scale = target_delta / getAverageDelta();
874 for (int i=0; i<length; i++) {
875 x[i] *= scale;
876 y[i] *= scale;
877 z[i] *= scale;
881 static public VectorString3D createRandom(final int length, final double delta, final boolean closed) throws Exception {
882 System.out.println("Creating random with length " + length + " and delta " + delta);
883 final double[] x = new double[length];
884 final double[] y = new double[length];
885 final double[] z = new double[length];
886 final Random rand = new Random(69997); // fixed seed, so that when length is equal the generated sequence is also equal
887 final Vector v = new Vector();
888 for (int i=0; i<length; i++) {
889 v.set(rand.nextDouble()*2 -1, rand.nextDouble()*2 -1, rand.nextDouble()*2 -1); // random values in the range [-1,1]
890 v.setLength(delta);
891 v.put(i, x, y, z);
893 final VectorString3D vs = new VectorString3D(x, y, z, closed);
894 vs.delta = delta;
895 vs.vx = new double[length];
896 vs.vy = new double[length];
897 vs.vz = new double[length];
898 for (int i=1; i<length; i++) {
899 vs.vx[i] = vs.x[i] - vs.x[i-1];
900 vs.vy[i] = vs.y[i] - vs.y[i-1];
901 vs.vz[i] = vs.z[i] - vs.z[i-1];
903 if (closed) {
904 vs.vx[0] = vs.x[0] - vs.x[length-1];
905 vs.vy[0] = vs.y[0] - vs.y[length-1];
906 vs.vz[0] = vs.z[0] - vs.z[length-1];
908 return vs;
911 /** 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.
912 * Beware: if calibrated, then the Calibration has been applied to the points, but the callibration's pixelWidth and pixelHeight remain the same, not set to 1 and 1 respectively. So they can be used to uncalibrate, or to read out the units.
914 public void calibrate(final Calibration cal) {
915 if (null != this.cal) {
916 System.out.println("WARNING calibrating VectorString3D more than one time!");
918 if (null == cal) return;
919 this.cal = cal;
920 final int sign = cal.pixelDepth < 0 ? -1 : 1;
921 for (int i=0; i<x.length; i++) {
922 x[i] *= cal.pixelWidth;
923 y[i] *= cal.pixelHeight; // should be the same as pixelWidth
924 z[i] *= cal.pixelWidth * sign; // not pixelDepth, see day notes 20080227
925 // it's pixelWidth because that is what is used to generate the pixel Z coordinates of the layers, multiplying by pixelDepth. So, layer Z coords are: pixelDepth / pixelWidth (i.e. microns divided by microns/px gives px)
927 // reset vectors
928 vx = vy = vz = null;
929 rvx = rvy = rvz = null;
930 delta = 0;
933 /** Sets but does NOT apply the given calibration. */
934 public void setCalibration(final Calibration cal) {
935 this.cal = cal;
938 public boolean isCalibrated() {
939 return null != this.cal;
941 public Calibration getCalibrationCopy() {
942 return null == this.cal ? null : this.cal.copy();
945 /** Invert the order of points. Will clear all vector arrays if any! */
946 @Override
947 public void reverse() {
948 tags ^= REVERSED; // may be re-reversed
949 // reverse point arrays
950 Util.reverse(x);
951 Util.reverse(y);
952 Util.reverse(z);
953 // eliminate vector data
954 delta = 0;
955 if (null != vx || null != vy || null != vz) {
956 vx = vy = vz = null;
958 if (null != rvx || null != rvy || null != rvz) {
959 rvx = rvy = rvz = null;
961 if (null != source) Collections.reverse(source);
964 public boolean isReversed() {
965 return 0 != (this.tags & REVERSED);
968 static public VectorString3D createInterpolated(final Editions ed, final double alpha) throws Exception {
969 final VectorString3D vs1 = (VectorString3D)ed.getVS1();
970 final VectorString3D vs2 = (VectorString3D)ed.getVS2();
971 return vs1.createInterpolated(vs2, ed, alpha);
974 /** Create an interpolated VectorString3D between this and the given one, with the proper weight 'alpha' which must be 0 &lt;= weight &lt;= 1 (otherwise returns null) */
975 public VectorString3D createInterpolated(final VectorString3D other, final Editions ed, final double alpha) throws Exception {
978 System.out.println("THIS METHOD IS BROKEN IN SOME UNKNOWN HORRIBLE WAY.\nFor now, use VectorString3D.createInterpolatedPoints(...) method, which is known to work -- even if conceptually not fully accurate.");
982 // check deltas: must be equal
983 if (Math.abs(this.delta - other.delta) > 0.00000001) {
984 throw new Exception("deltas are not the same: this.delta=" + this.delta + " other.delta=" + other.delta);
986 // check nonsense weight
987 if (alpha < 0 || alpha > 1) return null;
988 // if 0 or 1, the limits, the returned VectorString3D will be equal to this one or to the other, but relative to this one in position.
990 // else, make a proper intermediate curve
991 double[] vx1 = this.vx;
992 double[] vy1 = this.vy;
993 double[] vz1 = this.vz;
994 double[] vx2 = other.vx;
995 double[] vy2 = other.vy;
996 double[] vz2 = other.vz;
997 boolean relative = false;
998 if (null != this.rvx && null != other.rvx) {
999 // both are relative
1000 vx1 = this.rvx;
1001 vy1 = this.rvy;
1002 vz1 = this.rvz;
1003 vx2 = other.rvx;
1004 vy2 = other.rvy;
1005 vz2 = other.rvz;
1006 relative = true;
1009 final int[][] editions = ed.getEditions();
1010 final int n_editions = ed.length(); // == Math.max(this.length, other.length)
1011 final double[] px = new double[n_editions];
1012 final double[] py = new double[n_editions];
1013 final double[] pz = new double[n_editions];
1015 // first point: an average of both starting points
1016 px[0] = (this.x[0] * (1-alpha) + other.x[0] * alpha);
1017 py[0] = (this.y[0] * (1-alpha) + other.y[0] * alpha);
1018 pz[0] = (this.z[0] * (1-alpha) + other.z[0] * alpha);
1020 int start = 0;
1021 final int end = n_editions;
1022 if (Editions.INSERTION == editions[0][0] || Editions.DELETION == editions[0][0]) {
1023 start = 1;
1026 final int n = this.length();
1027 final int m = other.length();
1029 int i=0,j=0;
1030 int next=0;
1031 double vs_x=0, vs_y=0, vs_z=0;
1032 final Vector v = new Vector();
1033 final Vector v_newref = new Vector();
1034 final Vector v_delta = new Vector(this.delta, 0, 0);
1035 final Vector v_i1 = new Vector();
1036 final double[] d = new double[3];
1038 final boolean with_source = (null != this.source
1039 && null != other.source);
1040 final ArrayList<ArrayList<Point3d>> the_source = with_source ?
1041 new ArrayList<ArrayList<Point3d>>()
1042 : null;
1043 try {
1045 for (int e=start; e<end; e++) {
1046 i = editions[e][1];
1047 j = editions[e][2];
1048 // check for deletions and insertions at the lower-right edges of the matrix:
1049 if (isClosed()) {
1050 if (i == n) i = 0; // zero, so the starting vector is applied.
1051 if (j == m) j = 0;
1052 } else {
1053 if (i == n) i -= 1;
1054 if (j == m) j -= 1;
1057 if (with_source) {
1058 // merge j, i without deep cloning
1059 final ArrayList<Point3d> ap = new ArrayList<Point3d>();
1060 ap.addAll(this.source.get(i));
1061 ap.addAll(other.source.get(j));
1062 the_source.add(ap);
1065 // TODO: add dependents!
1068 // do:
1069 switch (editions[e][0]) {
1070 case Editions.INSERTION:
1071 vs_x = vx2[j] * alpha;
1072 vs_y = vy2[j] * alpha;
1073 vs_z = vz2[j] * alpha;
1074 break;
1075 case Editions.DELETION:
1076 vs_x = vx1[i] * (1.0 - alpha);
1077 vs_y = vy1[i] * (1.0 - alpha);
1078 vs_z = vz1[i] * (1.0 - alpha);
1079 break;
1080 case Editions.MUTATION:
1081 vs_x = vx1[i] * (1.0 - alpha) + vx2[j] * alpha;
1082 vs_y = vy1[i] * (1.0 - alpha) + vy2[j] * alpha;
1083 vs_y = vz1[i] * (1.0 - alpha) + vz2[j] * alpha;
1084 break;
1085 default:
1086 // using same vectors as last edition
1087 System.out.println("\nIgnoring unknown edition " + editions[e][0]);
1088 break;
1090 // store the point
1091 if (relative) {
1092 if (0 == i) {
1093 // ?
1094 } else {
1095 // must inverse rotate the vector, so that instead of the difference relative to (delta,0,0)
1096 // it becomes the difference relative to (vx1,vy1,vz1)-1, i.e. absolute again in relation to this VectorString3D
1097 v_newref.set(vx1[i], vy1[i], vz1[i]);
1098 v_i1.set(vx1[i-1], vy1[i-1], vz1[i-1]);
1099 v.set(vs_x, vs_y, vs_z);
1100 v.changeRef(v_delta, v_i1, v_newref);
1101 v.put(d);
1102 vs_x = vx1[i-1] + d[0]; // making the difference vector be an absolute vector relative to this (uf!)
1103 vs_y = vy1[i-1] + d[1];
1104 vs_z = vz1[i-1] + d[2];
1107 if (next+1 == px.length) {
1108 System.out.println("breaking early");
1109 break;
1112 px[next+1] = px[next] + vs_x;
1113 py[next+1] = py[next] + vs_y;
1114 py[next+1] = py[next] + vs_z;
1115 // advance
1116 next++;
1120 } catch (final Exception e) {
1121 e.printStackTrace();
1122 System.out.println("next: " + next + " length: " + px.length + " i,j: " + i + ", " + j);
1126 final VectorString3D vs_interp = new VectorString3D(px, py, pz, isClosed());
1127 vs_interp.source = the_source;
1128 vs_interp.n_sources = this.n_sources + other.n_sources;
1129 return vs_interp;
1132 /** Where axis is any of VectorString.X_AXIS, .Y_AXIS or .Z_AXIS,
1133 and the mirroring is done relative to the local 0,0 of this VectorString. */
1134 public void mirror(final int axis) {
1135 final Transform3D t = new Transform3D();
1136 switch(axis) {
1137 case VectorString.X_AXIS:
1138 t.setScale(new Vector3d(-1, 1, 1));
1139 tags ^= MIRROR_X;
1140 break;
1141 case VectorString.Y_AXIS:
1142 t.setScale(new Vector3d(1, -1, 1));
1143 tags ^= MIRROR_Y;
1144 break;
1145 case VectorString.Z_AXIS:
1146 t.setScale(new Vector3d(1, 1, -1));
1147 tags ^= MIRROR_Z;
1148 break;
1149 default:
1150 return;
1152 final Point3d p = new Point3d();
1153 transform(x, y, z, t, p);
1154 if (null != this.vx) transform(vx, vy, vz, t, p);
1155 if (null != this.rvx) transform(rvx, rvy, rvz, t, p);
1158 private final void transform(final double[] x, final double[] y, final double[] z, final Transform3D t, final Point3d p) {
1159 for (int i=0; i<this.length; i++) {
1160 p.x = x[i]; p.y = y[i]; p.z = z[i];
1161 t.transform(p);
1162 x[i] = p.x; y[i] = p.y; z[i] = p.z;
1166 public boolean isMirroredX() { return 0 != (tags & MIRROR_X); }
1167 public boolean isMirroredY() { return 0 != (tags & MIRROR_Y); }
1168 public boolean isMirroredZ() { return 0 != (tags & MIRROR_Z); }
1169 public byte getTags() { return tags; }
1171 /** The physical length of this sequence of points. */
1172 public double computeLength() {
1173 double len = 0;
1174 for (int i=1; i<length; i++) {
1175 len += Math.sqrt(Math.pow(x[i] - x[i-1], 2) + Math.pow(y[i] - y[i-1], 2) + Math.pow(z[i] - z[i-1], 2));
1177 return len;
1180 /** Returns a normalized average vector, or null if not resampled. */
1182 private Vector3d makeAverageNormalizedVector() {
1183 if (null == vx || null == vy || null == vz) return null;
1184 final Vector3d v = new Vector3d();
1185 for (int i=0; i<length; i++) {
1186 v.x += vx[i];
1187 v.y += vy[i];
1188 v.z += vz[i];
1190 v.normalize();
1191 return v;
1195 /** The sum of all vectors, or what is the same: a vector from first to last points. */
1196 public Vector3d sumVector() {
1197 return new Vector3d(x[length-1] - x[0], y[length-1] - y[0], z[length-1] - z[0]);
1200 static public final double distance(final double x1, final double y1, final double z1,
1201 final double x2, final double y2, final double z2) {
1202 return Math.sqrt(Math.pow(x1 - x2, 2)
1203 + Math.pow(y1 - y2, 2)
1204 + Math.pow(z1 - z2, 2));
1207 static public final double sqDistance(final double x1, final double y1, final double z1,
1208 final double x2, final double y2, final double z2) {
1209 return Math.pow(x1 - x2, 2)
1210 + Math.pow(y1 - y2, 2)
1211 + Math.pow(z1 - z2, 2);
1214 static public final double distance(final VectorString3D vs1, final int i, final VectorString3D vs2, final int j) {
1215 return distance(vs1.x[i], vs1.y[i], vs1.z[i],
1216 vs2.x[j], vs2.y[j], vs2.z[j]);
1219 /** Distance from point i in this to point j in vs2. */
1220 @Override
1221 public double distance(final int i, final VectorString vs, final int j) {
1222 final VectorString3D vs2 = (VectorString3D)vs;
1223 return distance(x[i], y[i], z[i],
1224 vs2.x[j], vs2.y[j], vs2.z[j]);
1227 /** Distance from the point at i to p.*/
1228 public final double distance(final int i, final Point3d p) {
1229 return distance(p.x, p.y, p.z,
1230 x[i], y[i], z[i]);
1233 public void translate(final Vector3d v) {
1234 for (int i=0; i<length; i++) {
1235 x[i] += v.x;
1236 y[i] += v.y;
1237 z[i] += v.z;
1239 // vx, vy, vz not affected by translations, of course.
1242 public void transform(final Transform3D t) {
1243 final Point3d p = new Point3d();
1244 if (null != x) transform(t, x, y, z, length, p);
1245 if (null != vx) transform(t, vx, vy, vz, length, p);
1246 if (null != rvx) transform(t, rvx, rvy, rvz, length, p);
1249 static private void transform(final Transform3D t, final double[] x, final double[] y, final double[] z, final int length, final Point3d p) {
1250 for (int i=0; i<length; i++) {
1251 p.x = x[i];
1252 p.y = y[i];
1253 p.z = z[i];
1254 t.transform(p);
1255 x[i] = p.x;
1256 y[i] = p.y;
1257 z[i] = p.z;
1262 static private void transform(Transform3D trans, Transform3D rot, Vector3d v) {
1263 trans.transform(v);
1264 rot.transform(v);
1268 public Point3d computeCenterOfMass() {
1269 final Point3d v = new Point3d();
1270 for (int i=0; i<length; i++) {
1271 v.x += x[i]/length;
1272 v.y += y[i]/length;
1273 v.z += z[i]/length;
1275 return v;
1277 /** Create a new VectorString for the given range. If last &lt; first, it will be created as reversed. */
1278 @Override
1279 public VectorString subVectorString(int first, int last) throws Exception {
1280 final boolean reverse = last < first;
1281 if (reverse) {
1282 final int tmp = first;
1283 first = last;
1284 last = tmp;
1286 final int len = last - first + 1;
1287 final double[] x = new double[len];
1288 final double[] y = new double[len];
1289 final double[] z = new double[len];
1290 System.arraycopy(this.x, first, x, 0, len);
1291 System.arraycopy(this.y, first, y, 0, len);
1292 System.arraycopy(this.z, first, z, 0, len);
1293 final VectorString3D vs = new VectorString3D(x, y, z, this.isClosed());
1294 vs.source = cloneSource(first, last);
1295 if (reverse) vs.reverse();
1296 vs.cal = null == this.cal ? null : this.cal.copy();
1297 vs.tags = this.tags;
1298 vs.delta = this.delta;
1300 if (null != this.vx) {
1301 // this is resampled, so:
1302 vs.delta = this.delta;
1303 // create vectors
1304 vs.vx = new double[len];
1305 vs.vy = new double[len];
1306 vs.vz = new double[len];
1307 for (int i=1; i<len; i++) {
1308 vs.vx[i] = vs.x[i] - vs.x[i-1];
1309 vs.vy[i] = vs.y[i] - vs.y[i-1];
1310 vs.vz[i] = vs.z[i] - vs.z[i-1];
1312 if (null != this.rvx) {
1313 // it's relative
1314 vs.relative();
1317 return vs;
1320 /** Create a new VectorString3D which is the weighted average between the two VectorString3D that make the Editions.
1321 * @param alpha is the weight, between 0 and 1.
1322 * */
1323 static public VectorString3D createInterpolatedPoints(final Editions ed, final double alpha) {
1324 return createInterpolatedPoints(ed, alpha, 0, ed.editions.length -1);
1326 /** Create a new VectorString3D which is the weighted average between the two VectorString3D that make the Editions.
1327 * @param alpha is the weight, between 0 and 1.
1328 * @param first is the first index to consider for the interpolated VectorString3D.
1329 * @param last is the last index to consider for the interpolated VectorString3D.
1330 * */
1331 static public VectorString3D createInterpolatedPoints(final Editions ed, final double alpha, final int first, final int last) {
1332 try {
1333 final VectorString3D vs1 = (VectorString3D)ed.vs1;
1334 if (alpha <= 0) return (VectorString3D)vs1.clone();
1335 final VectorString3D vs2 = (VectorString3D)ed.vs2;
1336 if (alpha >= 1) return (VectorString3D)vs2.clone();
1337 // else make weighted average
1338 final double[] x = new double[last - first + 1];
1339 final double[] y = new double[x.length];
1340 final double[] z = new double[x.length];
1341 final int len1 = vs1.length();
1342 final int len2 = vs2.length();
1344 final boolean with_source = (null != vs1.source
1345 && null != vs2.source);
1346 final ArrayList<ArrayList<Point3d>> the_source = with_source ?
1347 new ArrayList<ArrayList<Point3d>>()
1348 : null;
1350 for (int k=first, next=0; k<=last; k++, next++) {
1351 final int[] e = ed.editions[k];
1352 int i = e[1]; if (i >= len1) i = len1 -1; // patching error that I don't understand TODO
1353 int j = e[2]; if (j >= len2) j = len2 -1;
1354 x[next] = vs1.x[i] * alpha + vs2.x[j] * (1 - alpha);
1355 y[next] = vs1.y[i] * alpha + vs2.y[j] * (1 - alpha);
1356 z[next] = vs1.z[i] * alpha + vs2.z[j] * (1 - alpha);
1358 if (with_source) {
1359 // merge i, j without deep cloning
1360 final ArrayList<Point3d> ap = new ArrayList<Point3d>();
1361 ap.addAll(vs1.source.get(i));
1362 ap.addAll(vs2.source.get(j));
1363 the_source.add(ap);
1367 if (with_source) {
1368 System.out.println("createInterpolatedPoints: lengths " + ed.editions.length + ", " + the_source.size() + " first,last: " + first + ", " + last);
1371 final VectorString3D vs = new VectorString3D(x, y, z, ed.vs1.isClosed());
1372 vs.source = the_source;
1373 vs.n_sources = vs1.n_sources + vs2.n_sources;
1374 vs.cal = null == vs1.cal ? null : vs1.cal.copy();
1375 vs.resample(vs1.delta, with_source);
1377 return vs;
1379 } catch (final Exception e) {
1380 e.printStackTrace();
1381 return null;
1385 /** Returns a new VectorString3D which is the result of the optimal chaining of this and the given VectorString.
1386 * The ordering of this VectorString3D is preserved; the other is thus appended at the end or prepended at te beginning, reversed as necessary.
1387 * */
1388 public VectorString3D chain(final VectorString3D vs) {
1389 if (this.isClosed() || vs.isClosed()) {
1390 System.out.println("Can't chain closed VectorString3D instances.");
1391 return null;
1393 // check both ends, find the two ends that are closest
1394 final double d1 = distance(x[0], y[0], z[0],
1395 vs.x[0], vs.y[0], vs.z[0]);
1396 final double d2 = distance(x[length-1], y[length-1], z[length-1],
1397 vs.x[0], vs.y[0], vs.z[0]);
1398 final double d3 = distance(x[0], y[0], z[0],
1399 vs.x[vs.length-1], vs.y[vs.length-1], vs.z[vs.length-1]);
1400 final double d4 = distance(x[length-1], y[length-1], z[length-1],
1401 vs.x[vs.length-1], vs.y[vs.length-1], vs.z[vs.length-1]);
1403 final double min = Math.min(d1, Math.min(d2, Math.min(d3, d4)));
1404 if (d1 == min) {
1405 final VectorString3D vsr = (VectorString3D)vs.clone();
1406 vsr.reverse();
1407 return concat(vsr, this);
1408 } else if (d2 == min) {
1409 return concat(this, vs);
1410 } else if (d3 == min) {
1411 return concat(vs, this);
1412 } else { // if (d4 == min)
1413 final VectorString3D vsr = (VectorString3D)vs.clone();
1414 vsr.reverse();
1415 return concat(this, vsr);
1419 static private final VectorString3D concat(final VectorString3D vs1, final VectorString3D vs2) {
1420 final int len = vs1.length + vs2.length;
1421 final double[] x = Util.copy(vs1.x, len);
1422 final double[] y = Util.copy(vs1.y, len);
1423 final double[] z = Util.copy(vs1.z, len);
1424 System.arraycopy(vs2.x, 0, x, vs1.length, vs2.length);
1425 System.arraycopy(vs2.y, 0, y, vs1.length, vs2.length);
1426 System.arraycopy(vs2.z, 0, z, vs1.length, vs2.length);
1427 try {
1428 return new VectorString3D(x, y, z, false);
1429 } catch (final Exception e) {
1430 return null;
1434 /** Makes a substring starting at @param first (inclusive) and finishing at @param last (non-inclusive, aka last-1). */
1435 public VectorString3D substring(final int first, final int last) {
1436 if (first < 0 || last > length) return null;
1437 final int len = last - first; // no +1 because last is non-inclusive
1438 try {
1439 final VectorString3D vs = new VectorString3D(Util.copy(x, first, len), Util.copy(y, first, len), Util.copy(z, first, len), false);
1440 vs.delta = delta;
1441 if (null != vx) vs.vx = Util.copy(vx, first, len);
1442 if (null != vy) vs.vy = Util.copy(vy, first, len);
1443 if (null != vz) vs.vz = Util.copy(vz, first, len);
1444 if (null != rvx) vs.rvx = Util.copy(rvx, first, len);
1445 if (null != rvy) vs.rvy = Util.copy(rvy, first, len);
1446 if (null != rvz) vs.rvz = Util.copy(rvz, first, len);
1447 if (null != source) {
1448 // shallow clone the source points
1449 vs.source = new ArrayList<ArrayList<Point3d>>();
1450 for (final ArrayList<Point3d> ap : this.source) {
1451 vs.source.add(new ArrayList<Point3d>(ap));
1454 vs.tags = this.tags;
1455 vs.cal = null == this.cal ? null : this.cal.copy();
1456 return vs;
1457 } catch (final Exception e) {
1458 e.printStackTrace();
1459 return null;
1463 @Override
1464 public int getDimensions() { return 3; }
1466 static public final double getAverageVectorLength(final int[] i, final VectorString3D[] vs) {
1467 double len = 0;
1468 for (int k=vs.length; k>-1; k--) {
1469 final VectorString3D v = vs[k]; // java cannot even optimize this .. pitiful
1470 final int j = i[k];
1471 len += Math.sqrt(Math.pow(v.x[j], 2) + Math.pow(v.y[j], 2) + Math.pow(v.z[j], 2));
1473 return len / vs.length;
1476 /** Determine if any point of the given VectorString3D falls within a radius of any of the points in this VectorString3D. */
1477 public boolean isNear(final VectorString3D vs, final double radius) {
1478 final double sq_radius = radius * radius;
1479 for (int k=0; k<this.length; k++) {
1480 for (int i=0; i<vs.length; i++) {
1481 final double sqd = sqDistance(x[k], y[k], z[k], vs.x[i], vs.y[i], vs.z[i]);
1482 //Util.log("radius: " + sq_radius + " sqd: " + sqd);
1483 if (sqd <= sq_radius) {
1484 System.out.println("Found nearby " + vs + " at " + Math.sqrt(sqd));
1485 return true;
1489 return false;
1492 /** If null != source, compute the StdDev at each point in this VectorString3D, by comparing it with its associated source points. Returns null if there is no source.
1493 * What is meant for stdDev is the stdDev of the points from which any given point in this VectorString3D was generated, returned as a distance to use as radius around that point. */
1494 public double[] getStdDevAtEachPoint() {
1495 if (null == source) return null;
1496 final double[] stdDev = new double[length];
1497 int i = 0;
1498 System.out.println("len x: " + length + " len source: " + source.size());
1499 for (final ArrayList<Point3d> ap : source) {
1500 // 1 - Expected: the current position
1501 final Point3d expected = new Point3d(x[i], y[i], z[i]);
1502 // 2 - Sum of squares of differences of the distances to the expected position
1503 double sd = 0;
1504 for (final Point3d p : ap) sd += Math.pow(p.distance(expected), 2);
1505 // 3 - stdDev
1506 stdDev[i] = Math.sqrt(sd / ap.size());
1508 // Test separately for each dimension : it's the same
1510 double sdx = 0,
1511 sdy = 0,
1512 sdz = 0;
1513 for (Point3d p : ap) {
1514 sdx += Math.pow(p.x - expected.x, 2);
1515 sdy += Math.pow(p.y - expected.y, 2);
1516 sdz += Math.pow(p.z - expected.z, 2);
1518 sdx = Math.sqrt(sdx / ap.size());
1519 sdy = Math.sqrt(sdy / ap.size());
1520 sdz = Math.sqrt(sdz / ap.size());
1522 sd = stdDev[i]; // from above
1524 // stdDev as radial distance:
1525 stdDev[i] = Math.sqrt(sdx*sdx + sdy*sdy + sdz*sdz);
1527 System.out.println("check: sd = " + sd +
1528 " sd indep = " + stdDev[i]);
1531 i++;
1533 return stdDev;