Major cleanup of Utils class.
[trakem2.git] / ini / trakem2 / vector / SkinMaker.java
blobc65e60bf4b03e55fa3a4df33e7e0c6ccca3bc705
1 /**
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.
21 **/
23 package ini.trakem2.vector;
25 import java.util.ArrayList;
26 import java.util.List;
27 import javax.vecmath.Point3f;
28 import ini.trakem2.utils.Utils;
29 import ini.trakem2.utils.IJError;
31 public class SkinMaker {
33 // service class (i.e. convenient namespace)
34 private SkinMaker() {}
36 /** Returns a weighted VectorString2D. @param alpha is the weight, between 0 and 1. */
37 static public double[][] getMorphedPerimeter(final VectorString2D vs1, final VectorString2D vs2, final double alpha, final Editions ed) {
38 final int n = vs1.length();
39 final int m = vs2.length();
40 final double[] v_x1 = vs1.getVectors(0);
41 final double[] v_y1 = vs1.getVectors(1);
42 final double[] v_x2 = vs2.getVectors(0);
43 final double[] v_y2 = vs2.getVectors(1);
44 final int[][] editions = ed.getEditions();
45 final int n_editions = ed.length();
46 // the points to create. There is one point for each edition, plus the starting point.
47 double[] x = new double[n_editions]; // +1];
48 double[] y = new double[n_editions]; // +1];
49 //starting point: a weighted average between both starting points
50 x[0] = (vs1.getPoint(0, 0) * (1-alpha) + vs2.getPoint(0, 0) * alpha);
51 y[0] = (vs1.getPoint(1, 0) * (1-alpha) + vs2.getPoint(1, 0) * alpha);
53 // the iterators
54 int next = 0;
55 int i;
56 int j;
58 // generate weighted vectors to make weighted points
60 //// PATCH to avoid displacement of the interpolated curves when edition[1][0] is a MUTATION. //////
61 int start = 0;
62 int end = n_editions; // was: -1
63 if (Editions.INSERTION == editions[0][0] || Editions.DELETION == editions[0][0]) {
64 start = 1;
65 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.
68 // the weighted vectors to generate:
69 double vs_x = 0;
70 double vs_y = 0;
72 for (int e=start; e<end; e++) {
73 i = editions[e][1];
74 j = editions[e][2];
75 // check for deletions and insertions at the lower-right edges of the matrix:
76 if (i == n) {
77 i = 0; // zero, so the starting vector is applied.
79 // TODO both these if statements may be wrong for open curves!
80 if (j == m) {
81 j = 0;
83 // do:
84 switch (editions[e][0]) {
85 case Editions.INSERTION:
86 vs_x = v_x2[j] * alpha;
87 vs_y = v_y2[j] * alpha;
88 break;
89 case Editions.DELETION:
90 vs_x = v_x1[i] * (1.0 - alpha);
91 vs_y = v_y1[i] * (1.0 - alpha);
92 break;
93 case Editions.MUTATION:
94 vs_x = v_x1[i] * (1.0 - alpha) + v_x2[j] * alpha;
95 vs_y = v_y1[i] * (1.0 - alpha) + v_y2[j] * alpha;
96 break;
97 default:
98 Utils.log2("\ngetMorphedPerimeter: Nothing added!");
99 break;
101 if (next+1 == n_editions) {
102 x = Utils.copy(x, x.length+1);
103 y = Utils.copy(y, y.length+1);
105 // store the point
106 x[next+1] = x[next] + vs_x;
107 y[next+1] = y[next] + vs_y;
109 // advance
110 next++;
113 //Utils.log2("editions length: " + editions.length + "\nx,y length: " + x.length + ", " + y.length);
116 // return packed:
117 double[][] d = new double[2][];
118 d[0] = x;
119 d[1] = y;
120 return d;
123 /** 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) */
124 static public double[][][] getMorphedPerimeters(final VectorString2D vs1, final VectorString2D vs2, int n_morphed_perimeters, final Editions ed) {
125 // check automatic mode (-1 is the flag; if less, then just survive it):
126 if (n_morphed_perimeters < 0) n_morphed_perimeters = (int)(Math.sqrt(Math.sqrt(ed.getDistance())));
128 final double alpha = 1.0 / (n_morphed_perimeters +1); // to have 10 subdivisions we need 11 boxes
129 double[][][] p_list = new double[n_morphed_perimeters][][];
130 for (int a=0; a<n_morphed_perimeters; a++) {
131 double aa = alpha * (a+1); // aa 0 would be vs1, aa 1 would be vs2.
132 p_list[a] = SkinMaker.getMorphedPerimeter(vs1, vs2, aa, ed);
135 return p_list;
138 /** 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). */
139 static public ArrayList<SkinMaker.Match> getMorphedPerimeters(final VectorString2D[] vs, int n_morphed_perimeters, double delta_, boolean closed) {
140 //check preconditions:
141 if (n_morphed_perimeters < -1 || vs.length <=0) {
142 Utils.log2("\nERROR: args are not acceptable at getAllPerimeters:\n\t n_morphed_perimeters " + n_morphed_perimeters + ", n_perimeters " + vs.length);
143 return null;
146 final ArrayList<SkinMaker.Match> al_matches = new ArrayList();
148 try {
149 // get all morphed curves and return them.
150 double delta = 0.0;
151 // 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.
152 if (delta_ > 0) {
153 delta = delta_;
154 } else {
155 for (int i=0; i<vs.length; i++) {
156 delta += vs[i].getAverageDelta();
158 delta = delta / vs.length;
161 Utils.log2("\nUsing delta=" + delta);
163 // fetch morphed ones and fill all_perimeters array:
164 for (int i=1; i<vs.length; i++) {
165 Editions ed = new Editions(vs[i-1], vs[i], delta, closed);
166 double[][][] d = SkinMaker.getMorphedPerimeters(vs[i-1], vs[i], n_morphed_perimeters, ed);
167 // else, add them all
168 double z_start = vs[i-1].getPoint(2, 0); // z
169 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
170 //Utils.log2("vs[i].z: " + vs[i].getPoint(2, 0) + " z_start: " + z_start + " z_inc is: " + z_inc);
171 VectorString2D[] p = new VectorString2D[d.length];
172 for (int k=0; k<d.length; k++) {
173 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
175 al_matches.add(new Match(vs[i-1], vs[i], ed, p));
178 return al_matches;
180 } catch (Exception e) {
181 IJError.print(e);
183 return null;
186 /** Tuple to avoid ugly castings. Java is disgusting. */
187 static public class Match {
188 private VectorString2D vs1;
189 private VectorString2D vs2;
190 private Editions ed;
191 /** The interpolated curves in between vs1 and vs2.*/
192 private VectorString2D[] p;
194 public Match(VectorString2D vs1, VectorString2D vs2, Editions ed, VectorString2D[] p) {
195 this.vs1 = vs1;
196 this.vs2 = vs2;
197 this.ed = ed;
198 this.p = p;
200 /** Generate a list of Point3f points, every three defining a triangle, between vs1 and vs2 using the given sequence of editions. */
201 public List<Point3f> generateTriangles(final boolean closed) {
202 ArrayList<Point3f> triangles = new ArrayList();
203 if (null == p || 0 == p.length) {
204 triangles.addAll(makeSkin(vs1, vs2, closed, true, true));
205 } else {
206 triangles.addAll(makeSkin(vs1, p[0], closed, true, false));
207 for (int i=1; i<p.length; i++) {
208 triangles.addAll(makeSkin(p[i-1], p[i], closed, false, false));
210 triangles.addAll(makeSkin(p[p.length-1], vs2, closed, false, true));
212 return triangles;
214 private ArrayList<Point3f> makeSkin(final VectorString2D a, final VectorString2D b, final boolean closed, final boolean ao, final boolean bo) {
215 final double[] ax = a.getPoints(0);
216 final double[] ay = a.getPoints(1);
217 final float az = (float)a.getPoint(2, 0);
218 final int alength = a.length();
219 final double[] bx = b.getPoints(0);
220 final double[] by = b.getPoints(1);
221 final float bz = (float)b.getPoint(2, 0);
222 final int blength = b.length();
223 final ArrayList<Point3f> triangles = new ArrayList();
224 // the sequence of editions defines the edges
225 final int[][] editions = ed.editions;
226 int e_start = 0; // was 1
227 //if (Editions.MUTATION == editions[0][0]) e_start = 0; // apparently I have fixed old errors elsewhere
228 int i1, j1;
229 int i=0,
230 j=0;
231 if (!(!ao && !bo)) { // if at least one is original, use editions for matching
232 int ei;
233 int lag = 0;
234 for (int e=e_start; e<editions.length; e++) {
235 ei = editions[e][0];
236 i1 = editions[e][1];
237 j1 = editions[e][2];
238 switch (ei) {
239 case Editions.INSERTION:
240 if (!ao) lag++;
241 break;
242 case Editions.DELETION:
243 if (!bo) lag++;
244 break;
246 if (!ao) i1 += lag; // if a is not original
247 if (!bo) j1 += lag; // if b is not original
248 // safety checks
249 if (i1 >= alength) {
250 if (closed) i1 = 0;
251 else i1 = alength - 1;
253 if (j1 >= blength) {
254 if (closed) j1 = 0;
255 else j1 = blength - 1;
257 if ( Editions.MUTATION == ei || ( (!ao || !bo) && (Editions.INSERTION == ei || Editions.DELETION == ei) ) ) {
258 // if it's a mutation, or one of the two curves is not original
259 // a quad, split into two triangles:
260 // i1, i, j
261 triangles.add(new Point3f((float)ax[i1], (float)ay[i1], az));
262 triangles.add(new Point3f((float)ax[i], (float)ay[i], az));
263 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
264 // i1, j, j1
265 triangles.add(new Point3f((float)ax[i1], (float)ay[i1], az));
266 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
267 triangles.add(new Point3f((float)bx[j1], (float)by[j1], bz));
268 } else {
269 // an INSERTION or a DELETION, whe both curves are original
270 // i, j, j1
271 triangles.add(new Point3f((float)ax[i], (float)ay[i], az));
272 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
273 triangles.add(new Point3f((float)bx[j1], (float)by[j1], bz));
275 i = i1;
276 j = j1;
278 } else {
279 // Orthogonal match: both are interpolated and thus have the same amount of points,
280 // which correspond to each other 1:1
281 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
282 i1 = k+1;
283 j1 = i1;
284 // a quad, split into two triangles:
285 // i1, i, j
286 triangles.add(new Point3f((float)ax[i1], (float)ay[i1], az));
287 triangles.add(new Point3f((float)ax[i], (float)ay[i], az));
288 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
289 // i1, j, j1
290 triangles.add(new Point3f((float)ax[i1], (float)ay[i1], az));
291 triangles.add(new Point3f((float)bx[j], (float)by[j], bz));
292 triangles.add(new Point3f((float)bx[j1], (float)by[j1], bz));
293 i = i1;
294 j = j1;
297 if (closed) {
298 /* // for some reason this is not necessary (inspect why!)
299 // last point with first point: a quad
300 // 0_i, last_i, last_j
301 triangles.add(new Point3f((float)ax[0], (float)ay[0], az));
302 triangles.add(new Point3f((float)ax[alength-1], (float)ay[alength-1], az));
303 triangles.add(new Point3f((float)bx[blength-1], (float)by[blength-1], bz));
304 // 0_i, last_j, 0_j
305 triangles.add(new Point3f((float)ax[0], (float)ay[0], az));
306 triangles.add(new Point3f((float)bx[blength-1], (float)by[blength-1], bz));
307 triangles.add(new Point3f((float)bx[0], (float)by[0], bz));
310 return triangles;
314 /** From an array of VectorString2D, obtain a list of Point3f which define, every three, a triangle of a skin. */
315 static public List<Point3f> generateTriangles(final VectorString2D[] vs, int n_morphed_perimeters, double delta_, boolean closed) {
316 final ArrayList<SkinMaker.Match> al_matches = SkinMaker.getMorphedPerimeters(vs, -1, -1, true); // automatic number of interpolated curves, automatic delta
317 final List triangles = new ArrayList(); // every three consecutive Point3f make a triangle
318 for (SkinMaker.Match match : al_matches) {
319 triangles.addAll(match.generateTriangles(closed));
321 return triangles;