preparing release pom-trakem2-2.0.0, VectorString-2.0.0, TrakEM2_-1.0h
[trakem2.git] / VectorString / src / main / java / ini / trakem2 / vector / SkinMaker.java
blob50ed0d8759b5144e6e40262c5b4b516e866de2ee
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-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.List;
28 import org.scijava.vecmath.Point3f;
30 public class SkinMaker {
32 // service class (i.e. convenient namespace)
33 private SkinMaker() {}
35 /** Returns a weighted VectorString2D. @param alpha is the weight, between 0 and 1. */
36 static public double[][] getMorphedPerimeter(final VectorString2D vs1, final VectorString2D vs2, final double alpha, final Editions ed) {
37 final int n = vs1.length();
38 final int m = vs2.length();
39 final double[] v_x1 = vs1.getVectors(0);
40 final double[] v_y1 = vs1.getVectors(1);
41 final double[] v_x2 = vs2.getVectors(0);
42 final double[] v_y2 = vs2.getVectors(1);
43 final int[][] editions = ed.getEditions();
44 final int n_editions = ed.length();
45 // the points to create. There is one point for each edition, plus the starting point.
46 double[] x = new double[n_editions]; // +1];
47 double[] y = new double[n_editions]; // +1];
48 //starting point: a weighted average between both starting points
49 x[0] = (vs1.getPoint(0, 0) * (1-alpha) + vs2.getPoint(0, 0) * alpha);
50 y[0] = (vs1.getPoint(1, 0) * (1-alpha) + vs2.getPoint(1, 0) * alpha);
52 // the iterators
53 int next = 0;
54 int i;
55 int j;
57 // generate weighted vectors to make weighted points
59 //// PATCH to avoid displacement of the interpolated curves when edition[1][0] is a MUTATION. //////
60 int start = 0;
61 int end = n_editions; // was: -1
62 if (Editions.INSERTION == editions[0][0] || Editions.DELETION == editions[0][0]) {
63 start = 1;
64 end = n_editions; // that is how it works. I need to internalize this to understand it myself. It may be that an extra one is being added when creating the editions, by mistake.
67 // the weighted vectors to generate:
68 double vs_x = 0;
69 double vs_y = 0;
71 for (int e=start; e<end; e++) {
72 i = editions[e][1];
73 j = editions[e][2];
74 // check for deletions and insertions at the lower-right edges of the matrix:
75 if (i == n) {
76 i = 0; // zero, so the starting vector is applied.
78 // TODO both these if statements may be wrong for open curves!
79 if (j == m) {
80 j = 0;
82 // do:
83 switch (editions[e][0]) {
84 case Editions.INSERTION:
85 vs_x = v_x2[j] * alpha;
86 vs_y = v_y2[j] * alpha;
87 break;
88 case Editions.DELETION:
89 vs_x = v_x1[i] * (1.0 - alpha);
90 vs_y = v_y1[i] * (1.0 - alpha);
91 break;
92 case Editions.MUTATION:
93 vs_x = v_x1[i] * (1.0 - alpha) + v_x2[j] * alpha;
94 vs_y = v_y1[i] * (1.0 - alpha) + v_y2[j] * alpha;
95 break;
96 default:
97 System.out.println("\ngetMorphedPerimeter: Nothing added!");
98 break;
100 if (next+1 == n_editions) {
101 x = Util.copy(x, x.length+1);
102 y = Util.copy(y, y.length+1);
104 // store the point
105 x[next+1] = x[next] + vs_x;
106 y[next+1] = y[next] + vs_y;
108 // advance
109 next++;
112 //System.out.println("editions length: " + editions.length + "\nx,y length: " + x.length + ", " + y.length);
115 // return packed:
116 final double[][] d = new double[2][];
117 d[0] = x;
118 d[1] = y;
119 return d;
122 /** From two VectorString2D, return an array of x,y points ( in the form [2][n] ) defining all necessary intermediate, morphed perimeters that describe a skin between them (not including the two VectorString2D) */
123 static public double[][][] getMorphedPerimeters(final VectorString2D vs1, final VectorString2D vs2, int n_morphed_perimeters, final Editions ed) {
124 // check automatic mode (-1 is the flag; if less, then just survive it):
125 if (n_morphed_perimeters < 0) n_morphed_perimeters = (int)(Math.sqrt(Math.sqrt(ed.getDistance())));
127 final double alpha = 1.0 / (n_morphed_perimeters +1); // to have 10 subdivisions we need 11 boxes
128 final double[][][] p_list = new double[n_morphed_perimeters][][];
129 for (int a=0; a<n_morphed_perimeters; a++) {
130 final double aa = alpha * (a+1); // aa 0 would be vs1, aa 1 would be vs2.
131 p_list[a] = SkinMaker.getMorphedPerimeter(vs1, vs2, aa, ed);
134 return p_list;
137 /** From an array of VectorString2D, return a new array of VectorString2D defining all necessary intermediate, morphed perimeters that describe a skin between each consecutive pair; includes the originals inserted at the proper locations. The 'z' is interpolated. Returns the array of VectorString2D and the array of Editions (which is one item smaller, since it represents matches). */
138 static public ArrayList<SkinMaker.Match> getMorphedPerimeters(final VectorString2D[] vs, final int n_morphed_perimeters, final double delta_, final boolean closed) {
139 //check preconditions:
140 if (n_morphed_perimeters < -1 || vs.length <=0) {
141 System.out.println("\nERROR: args are not acceptable at getAllPerimeters:\n\t n_morphed_perimeters " + n_morphed_perimeters + ", n_perimeters " + vs.length);
142 return null;
145 final ArrayList<SkinMaker.Match> al_matches = new ArrayList<SkinMaker.Match>();
147 try {
148 // get all morphed curves and return them.
149 double delta = 0.0;
150 // calculate delta, or taken the user-given one if acceptable (TODO acceptable is not only delta > 0, but also smaller than half the curve length or so.
151 if (delta_ > 0) {
152 delta = delta_;
153 } else {
154 for (int i=0; i<vs.length; i++) {
155 delta += vs[i].getAverageDelta();
157 delta = delta / vs.length;
160 System.out.println("\nUsing delta=" + delta);
162 // fetch morphed ones and fill all_perimeters array:
163 for (int i=1; i<vs.length; i++) {
164 Editions ed = new Editions(vs[i-1], vs[i], delta, closed);
165 // correct for reverse order: choose the best scoring
166 final VectorString2D rev = ((VectorString2D)vs[i].clone());
167 rev.reverse();
168 final Editions ed_rev = new Editions(vs[i-1], rev, delta, closed);
169 if (ed_rev.getDistance() < ed.getDistance()) {
170 vs[i] = rev;
171 ed = ed_rev;
174 final double[][][] d = SkinMaker.getMorphedPerimeters(vs[i-1], vs[i], n_morphed_perimeters, ed);
175 // else, add them all
176 final double z_start = vs[i-1].getPoint(2, 0); // z
177 final double z_inc = (vs[i].getPoint(2, 0) - z_start) / (double)( 0 == d.length ? 1 : (d.length + 1)); // if zero, none are added anyway; '1' is a dummy number
178 //System.out.println("vs[i].z: " + vs[i].getPoint(2, 0) + " z_start: " + z_start + " z_inc is: " + z_inc);
179 final VectorString2D[] p = new VectorString2D[d.length];
180 for (int k=0; k<d.length; k++) {
181 p[k] = new VectorString2D(d[k][0], d[k][1], z_start + z_inc*(k+1), vs[0].isClosed()); // takes the closed value from the first one, ignoring the other
183 al_matches.add(new Match(vs[i-1], vs[i], ed, p));
186 return al_matches;
188 } catch (final Exception e) {
189 e.printStackTrace();
191 return null;
194 /** Tuple to avoid ugly castings. Java is disgusting. */
195 static public class Match {
196 private VectorString2D vs1;
197 private VectorString2D vs2;
198 private Editions ed;
199 /** The interpolated curves in between vs1 and vs2.*/
200 private VectorString2D[] p;
202 public Match(final VectorString2D vs1, final VectorString2D vs2, final Editions ed, final VectorString2D[] p) {
203 this.vs1 = vs1;
204 this.vs2 = vs2;
205 this.ed = ed;
206 this.p = p;
208 /** Generate a list of Point3f points, every three defining a triangle, between vs1 and vs2 using the given sequence of editions. */
209 public List<Point3f> generateTriangles(final boolean closed) {
210 final ArrayList<Point3f> triangles = new ArrayList<Point3f>();
211 if (null == p || 0 == p.length) {
212 triangles.addAll(makeSkin(vs1, vs2, closed, true, true));
213 } else {
214 triangles.addAll(makeSkin(vs1, p[0], closed, true, false));
215 for (int i=1; i<p.length; i++) {
216 triangles.addAll(makeSkin(p[i-1], p[i], closed, false, false));
218 triangles.addAll(makeSkin(p[p.length-1], vs2, closed, false, true));
220 return triangles;
222 private ArrayList<Point3f> makeSkin(final VectorString2D a, final VectorString2D b, final boolean closed, final boolean ao, final boolean bo) {
223 final double[] ax = a.getPoints(0);
224 final double[] ay = a.getPoints(1);
225 final float az = (float)a.getPoint(2, 0);
226 final int alength = a.length();
227 final double[] bx = b.getPoints(0);
228 final double[] by = b.getPoints(1);
229 final float bz = (float)b.getPoint(2, 0);
230 final int blength = b.length();
231 final ArrayList<Point3f> triangles = new ArrayList<Point3f>();
232 // the sequence of editions defines the edges
233 final int[][] editions = ed.editions;
234 final int e_start = 0; // was 1
235 //if (Editions.MUTATION == editions[0][0]) e_start = 0; // apparently I have fixed old errors elsewhere
236 int i1, j1;
237 int i=0,
238 j=0;
239 if (!(!ao && !bo)) { // if at least one is original, use editions for matching
240 int ei;
241 int lag = 0;
242 for (int e=e_start; e<editions.length; e++) {
243 ei = editions[e][0];
244 i1 = editions[e][1];
245 j1 = editions[e][2];
246 switch (ei) {
247 case Editions.INSERTION:
248 if (!ao) lag++;
249 break;
250 case Editions.DELETION:
251 if (!bo) lag++;
252 break;
254 if (!ao) i1 += lag; // if a is not original
255 if (!bo) j1 += lag; // if b is not original
256 // safety checks
257 if (i1 >= alength) {
258 if (closed) i1 = 0;
259 else i1 = alength - 1;
261 if (j1 >= blength) {
262 if (closed) j1 = 0;
263 else j1 = blength - 1;
265 if ( Editions.MUTATION == ei || ( (!ao || !bo) && (Editions.INSERTION == ei || Editions.DELETION == ei) ) ) {
266 // if it's a mutation, or one of the two curves is not original
267 // a quad, split into two triangles:
268 // i1, i, j
269 triangles.add(new Point3f((float)ax[i1], (float)ay[i1], az));
270 triangles.add(new Point3f((float)ax[i], (float)ay[i], az));
271 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
272 // i1, j, j1
273 triangles.add(new Point3f((float)ax[i1], (float)ay[i1], az));
274 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
275 triangles.add(new Point3f((float)bx[j1], (float)by[j1], bz));
276 } else {
277 // an INSERTION or a DELETION, whe both curves are original
278 // i, j, j1
279 triangles.add(new Point3f((float)ax[i], (float)ay[i], az));
280 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
281 triangles.add(new Point3f((float)bx[j1], (float)by[j1], bz));
283 i = i1;
284 j = j1;
286 } else {
287 // Orthogonal match: both are interpolated and thus have the same amount of points,
288 // which correspond to each other 1:1
289 for (int k=0; k<alength-1; k++) { // should be a.length-1, but for some reason the last point is set to 0,0,z and is superfluous
290 i1 = k+1;
291 j1 = i1;
292 // a quad, split into two triangles:
293 // i1, i, j
294 triangles.add(new Point3f((float)ax[i1], (float)ay[i1], az));
295 triangles.add(new Point3f((float)ax[i], (float)ay[i], az));
296 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
297 // i1, j, j1
298 triangles.add(new Point3f((float)ax[i1], (float)ay[i1], az));
299 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
300 triangles.add(new Point3f((float)bx[j1], (float)by[j1], bz));
301 i = i1;
302 j = j1;
305 if (closed) {
306 /* // for some reason this is not necessary (inspect why!)
307 // last point with first point: a quad
308 // 0_i, last_i, last_j
309 triangles.add(new Point3f((float)ax[0], (float)ay[0], az));
310 triangles.add(new Point3f((float)ax[alength-1], (float)ay[alength-1], az));
311 triangles.add(new Point3f((float)bx[blength-1], (float)by[blength-1], bz));
312 // 0_i, last_j, 0_j
313 triangles.add(new Point3f((float)ax[0], (float)ay[0], az));
314 triangles.add(new Point3f((float)bx[blength-1], (float)by[blength-1], bz));
315 triangles.add(new Point3f((float)bx[0], (float)by[0], bz));
318 return triangles;
322 /** From an array of VectorString2D, obtain a list of Point3f which define, every three, a triangle of a skin. */
323 static public List<Point3f> generateTriangles(final VectorString2D[] vs, final int n_morphed_perimeters, final double delta_, final boolean closed) {
324 final ArrayList<SkinMaker.Match> al_matches = SkinMaker.getMorphedPerimeters(vs, -1, -1, true); // automatic number of interpolated curves, automatic delta
325 final List<Point3f> triangles = new ArrayList<Point3f>(); // every three consecutive Point3f make a triangle
326 for (final SkinMaker.Match match : al_matches) {
327 triangles.addAll(match.generateTriangles(closed));
329 return triangles;