View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.io.FileWriter;
4   import java.io.PrintWriter;
5   
6   import com.github.celldynamics.quimp.geom.ExtendedVector2d;
7   
8   import ij.process.ImageProcessor;
9   
10  /**
11   * Calculate forces that affect the snake. Active Contour segmentation.
12   * 
13   * <p>This procedure is aware of possible overlapping and try to counteract them.
14   * 
15   * @author rtyson
16   */
17  public class Constrictor {
18  
19    /**
20     * Default constructor.
21     */
22    public Constrictor() {
23    }
24  
25    /**
26     * Compute force power and moves nodes by predefined step.
27     * 
28     * <p>This routine should be called in loop. Each call moves nodes by
29     * {@link BOAState.BOAp#delta_t}.
30     * 
31     * @param snake Processed snake
32     * @param ip Original image
33     * @return status of snake (true if it is frozen)
34     * @see BOA_#tightenSnake
35     */
36    public boolean constrict(final Snake snake, final ImageProcessor ip) {
37  
38      ExtendedVector2d tempF; // temp vectors for forces
39      ExtendedVector2dtendedVector2d.html#ExtendedVector2d">ExtendedVector2d tempV = new ExtendedVector2d();
40  
41      Node n = snake.getHead();
42      do { // compute F_total
43  
44        if (!n.isFrozen()) {
45  
46          // compute F_central
47          tempV.setX(n.getNormal().getX() * BOA_.qState.segParam.f_central);
48          tempV.setY(n.getNormal().getY() * BOA_.qState.segParam.f_central);
49          n.setF_total(tempV);
50  
51          // compute F_contract
52          tempF = contractionForce(n);
53          tempV.setX(tempF.getX() * BOA_.qState.segParam.f_contract);
54          tempV.setY(tempF.getY() * BOA_.qState.segParam.f_contract);
55          n.addF_total(tempV);
56  
57          // compute F_image and F_friction
58          tempF = imageForce(n, ip);
59          tempV.setX(tempF.getX() * BOA_.qState.segParam.f_image);// - n.getVel().getX() *
60          // boap.f_friction);
61          tempV.setY(tempF.getY() * BOA_.qState.segParam.f_image);// - n.getVel().getY() *
62          // boap.f_friction);
63          n.addF_total(tempV);
64  
65          // compute new velocities of the node
66          tempV.setX(BOA_.qState.boap.delta_t * n.getF_total().getX());
67          tempV.setY(BOA_.qState.boap.delta_t * n.getF_total().getY());
68          n.addVel(tempV);
69  
70          // store the prelimanary point to move the node to
71          tempV.setX(BOA_.qState.boap.delta_t * n.getVel().getX());
72          tempV.setY(BOA_.qState.boap.delta_t * n.getVel().getY());
73          n.setPrelim(tempV); // normal
74          // if (BOA_.qState.segParam.contractingDirection == true) {
75          // n.setPrelim(tempV); // normal
76          // } else {
77          // if (n.isFrozen()) {
78          // n.setPrelim(new ExtendedVector2d(0, 0)); // expanding
79          // } else {
80          // n.setPrelim(tempV); // normal
81          // }
82          // }
83  
84          // add some friction
85          n.getVel().multiply(BOA_.qState.boap.f_friction);
86  
87          // freeze node if vel is below velCrit
88          if (n.getVel().length() < BOA_.qState.segParam.vel_crit) {
89            snake.freezeNode(n);
90          }
91        }
92        n = n.getNext(); // move to next node
93      } while (!n.isHead()); // continue while have not reached tail
94  
95      // update all nodes to new positions
96      n = snake.getHead();
97      do {
98        n.update(); // use preliminary variables
99        n = n.getNext();
100     } while (!n.isHead());
101 
102     snake.updateNormals(BOA_.qState.segParam.expandSnake);
103 
104     return snake.isFrozen(); // true if all nodes frozen
105   }
106 
107   /**
108    * constrictWrite.
109    * 
110    * @param snake snake
111    * @param ip ip
112    * @return true on success
113    * @deprecated Strictly related to absolute paths on disk. Probably for testing only.
114    */
115   public boolean constrictWrite(final Snake snake, final ImageProcessor ip) {
116     // for writing forces at each frame
117     try {
118       PrintWriter pw = new PrintWriter(
119               new FileWriter("/Users/rtyson/Documents/phd/tmp/test/forcesWrite/forces.txt"), true);
120       ExtendedVector2d tempF; // temp vectors for forces
121       ExtendedVector2dtendedVector2d.html#ExtendedVector2d">ExtendedVector2d tempV = new ExtendedVector2d();
122 
123       Node n = snake.getHead();
124       do { // compute F_total
125 
126         // if (!n.isFrozen()) {
127 
128         // compute F_central
129         tempV.setX(n.getNormal().getX() * BOA_.qState.segParam.f_central);
130         tempV.setY(n.getNormal().getY() * BOA_.qState.segParam.f_central);
131         pw.print("\n" + n.getTrackNum() + "," + tempV.length() + ",");
132         n.setF_total(tempV);
133 
134         // compute F_contract
135         tempF = contractionForce(n);
136         if (n.getCurvatureLocal() > 0) {
137           pw.print(tempF.length() + ",");
138         } else {
139           pw.print((tempF.length() * -1) + ",");
140         }
141         tempV.setX(tempF.getX() * BOA_.qState.segParam.f_contract);
142         tempV.setY(tempF.getY() * BOA_.qState.segParam.f_contract);
143         n.addF_total(tempV);
144 
145         // compute F_image and F_friction
146         tempF = imageForce(n, ip);
147         pw.print((tempF.length() * -1) + ",");
148         tempV.setX(tempF.getX() * BOA_.qState.segParam.f_image);// - n.getVel().getX()*
149         // boap.f_friction);
150         tempV.setY(tempF.getY() * BOA_.qState.segParam.f_image);// - n.getVel().getY()*
151         // boap.f_friction);
152         n.addF_total(tempV);
153         pw.print(n.getF_total().length() + "");
154 
155         // compute new velocities of the node
156         tempV.setX(BOA_.qState.boap.delta_t * n.getF_total().getX());
157         tempV.setY(BOA_.qState.boap.delta_t * n.getF_total().getY());
158         n.addVel(tempV);
159 
160         // add some friction
161         n.getVel().multiply(BOA_.qState.boap.f_friction);
162 
163         // store the prelimanary point to move the node to
164         tempV.setX(BOA_.qState.boap.delta_t * n.getVel().getX());
165         tempV.setY(BOA_.qState.boap.delta_t * n.getVel().getY());
166         n.setPrelim(tempV);
167 
168         // freeze node if vel is below velCrit
169         if (n.getVel().length() < BOA_.qState.segParam.vel_crit) {
170           snake.freezeNode(n);
171         }
172         // }
173         n = n.getNext(); // move to next node
174       } while (!n.isHead()); // continue while have not reached tail
175 
176       // update all nodes to new positions
177       n = snake.getHead();
178       do {
179         n.update(); // use preliminary variables
180         n = n.getNext();
181       } while (!n.isHead());
182 
183       snake.updateNormals(BOA_.qState.segParam.expandSnake);
184 
185       pw.close();
186       return snake.isFrozen(); // true if all nodes frozen
187     } catch (Exception e) {
188       return false;
189     }
190   }
191 
192   /**
193    * Contraction force for node.
194    * 
195    * @param n Node
196    * @return force vector
197    */
198   ExtendedVector2d contractionForce(final Node n) {
199 
200     ExtendedVector2d resultR;
201     ExtendedVector2d resultL;
202     ExtendedVector2dtendedVector2d.html#ExtendedVector2d">ExtendedVector2d force = new ExtendedVector2d();
203 
204     // compute the unit vector pointing to the left neighbor (static method)
205     resultL = ExtendedVector2d.unitVector(n.getPoint(), n.getPrev().getPoint());
206 
207     // compute the unit vector pointing to the right neighbor
208     resultR = ExtendedVector2d.unitVector(n.getPoint(), n.getNext().getPoint());
209 
210     force.setX((resultR.getX() + resultL.getX()) * 0.5); // combine vector to left and right
211     force.setY((resultR.getY() + resultL.getY()) * 0.5);
212 
213     return (force);
214   }
215 
216   /**
217    * Calculate image force.
218    * 
219    * @param n node
220    * @param ip image
221    * @return image force at node
222    */
223   ExtendedVector2d imageForce(final Node n, final ImageProcessor ip) {
224     ExtendedVector2dendedVector2d.html#ExtendedVector2d">ExtendedVector2d result = new ExtendedVector2d();
225     ExtendedVector2d tan = n.getTangent(); // Tangent at node
226     int i;
227     int j; // loop vars
228 
229     double a = 0.75; // subsampling factor
230     double deltaI; // intensity contrast
231     double x;
232     double y; // co-ordinates of the norm
233     double xt;
234     double yt; // co-ordinates of the tangent
235     int insideI = 0;
236     int outsideI = 0; // Intensity of neighbourhood of a node (insde/outside of the chain)
237     int inI = 0;
238     int outI = 0; // number of pixels in the neighbourhood of a node
239 
240     // determine num pixels and total intensity of neighbourhood: a rectangle with sampleTan x
241     // sampleNorm
242     for (i = 0; i <= 1. / a * BOA_.qState.segParam.sample_tan; i++) {
243       // determine points on the tangent
244       xt = n.getPoint().getX() + (a * i - BOA_.qState.segParam.sample_tan / 2) * tan.getX();
245       yt = n.getPoint().getY() + (a * i - BOA_.qState.segParam.sample_tan / 2) * tan.getY();
246 
247       for (j = 0; j <= 1. / a * BOA_.qState.segParam.sample_norm / 2; ++j) {
248         x = xt + a * j * n.getNormal().getX();
249         y = yt + a * j * n.getNormal().getY();
250 
251         insideI += ip.getPixel((int) x, (int) y);
252         inI++;
253 
254         x = xt - a * j * n.getNormal().getX();
255         y = yt - a * j * n.getNormal().getY();
256 
257         outsideI += ip.getPixel((int) x, (int) y);
258         outI++;
259       }
260     }
261 
262     deltaI = ((double) insideI / inI - (double) outsideI / outI) / 255.;
263     if (deltaI > 0.) { // else remains at zero
264       result.setX(-Math.sqrt(deltaI) * n.getNormal().getX());
265       result.setY(-Math.sqrt(deltaI) * n.getNormal().getY());
266     }
267 
268     return (result);
269   }
270 
271   /**
272    * Expand all snakes while preventing overlaps adequately to blowup parameter.
273    * 
274    * <p>Dead snakes are ignored. Count snakes on frame.
275    * 
276    * <p>This method blows up live snakes (e.g. from previous frame) and prepare for contracting at
277    * current one next frame. snakes are expanded in small steps and at every step their nodes are
278    * tested for proximity {@link BOAState.BOAp#proxFreeze}. Only snakes whose centroids are closer
279    * than {@link BOAState.BOAp#proximity} are tested for overlapping. Note that this actions happen
280    * before contracting snakes around objects. This is preparatory step and if we assume contracting
281    * direction ({@link BOAState.SegParam#contractingDirection} is true) so then after this step
282    * snakes will not overlap during actual segmentation
283    * {@link Constrictor#constrict(Snake, ImageProcessor)}
284    * 
285    * @param nest nest
286    * @param frame frame
287    * @throws BoaException on snake scale
288    */
289   public void loosen(final Nest nest, int frame) throws BoaException {
290     int nestSize = nest.size();
291     Snake snakeA;
292     Snake snakeB;
293 
294     double[][] prox = computeProxMatrix(nest);
295     // will be negative if blowup is <0
296     double stepSize = 0.1 * Math.signum(BOA_.qState.segParam.blowup);
297     double steps = (double) BOA_.qState.segParam.blowup / stepSize; // always positive
298 
299     for (int i = 0; i < steps; i++) {
300       // check for contacts, freeze nodes in contact.
301       // Ignore snakes that begin after 'frame'
302       for (int si = 0; si < nestSize; si++) {
303         // if (nest.getHandler(si).isSnakeHandlerFrozen()) {
304         // continue;
305         // }
306         snakeA = nest.getHandler(si).getLiveSnake();
307         if (!snakeA.alive || frame < nest.getHandler(si).getStartFrame()) {
308           continue;
309         }
310         for (int sj = si + 1; sj < nestSize; sj++) {
311           snakeB = nest.getHandler(sj).getLiveSnake();
312           if (!snakeB.alive || frame < nest.getHandler(si).getStartFrame()) {
313             continue;
314           }
315           // proximity is computed for centroids, this is limit below we test for contact.
316           // if snake is big enough it can be not tested even if interact with other
317           if (prox[si][sj] > BOA_.qState.boap.proximity) {
318             continue; // snakes far away, assume no chance that they will interact
319           }
320           freezeProx(snakeA, snakeB);
321         }
322 
323       }
324 
325       // scale up all snakes by one step (if node not frozen, or dead) unless they start at this
326       // frame or after
327       for (int s = 0; s < nestSize; s++) {
328         // if (nest.getHandler(s).isSnakeHandlerFrozen()) {
329         // continue;
330         // }
331         snakeA = nest.getHandler(s).getLiveSnake();
332         if (snakeA.alive && frame > nest.getHandler(s).getStartFrame()) {
333           snakeA.scaleSnake(stepSize, Math.abs(stepSize), true);
334         }
335       }
336 
337     }
338   }
339 
340   /**
341    * Freeze snakes which are close to each other in nest.
342    * 
343    * @param nest nest to process.
344    * @param frame current frame
345    * @throws BoaException on error
346    */
347   public void freezeProxSnakes(final Nest nest, int frame) throws BoaException {
348     int nestSize = nest.size();
349     Snake snakeA;
350     Snake snakeB;
351 
352     double[][] prox = computeProxMatrix(nest);
353 
354     // check for contacts, freeze nodes in contact.
355     // Ignore snakes that begin after 'frame'
356     for (int si = 0; si < nestSize; si++) {
357       snakeA = nest.getHandler(si).getLiveSnake();
358       if (!snakeA.alive || frame < nest.getHandler(si).getStartFrame()) {
359         continue;
360       }
361       for (int sj = si + 1; sj < nestSize; sj++) {
362         snakeB = nest.getHandler(sj).getLiveSnake();
363         if (!snakeB.alive || frame < nest.getHandler(si).getStartFrame()) {
364           continue;
365         }
366         // proximity is computed for centroids, this is limit below we test for contact.
367         // if snake is big enough it can be not tested even if interact with other
368         if (prox[si][sj] > BOA_.qState.boap.proximity) {
369           continue; // snakes far away, assume no chance that they will interact
370         }
371         freezeProx(snakeA, snakeB);
372       }
373 
374     }
375   }
376 
377   /**
378    * Compute distance between centroids of all snakes in nest.
379    * 
380    * @param nest nest to process
381    * @return triangular matrix of centroids distances
382    */
383   private double[][] computeProxMatrix(final Nest nest) {
384     int nestSize = nest.size();
385     Snake snakeA;
386     Snake snakeB;
387     // dist between snake centroids, triangular
388     double[][] prox = new double[nestSize][nestSize];
389     for (int si = 0; si < nestSize; si++) {
390       snakeA = nest.getHandler(si).getLiveSnake();
391       snakeA.calcCentroid();
392       for (int sj = si + 1; sj < nestSize; sj++) {
393         snakeB = nest.getHandler(sj).getLiveSnake();
394         snakeB.calcCentroid();
395         prox[si][sj] = ExtendedVector2d.lengthP2P(snakeA.getCentroid(), snakeB.getCentroid());
396       }
397     }
398     return prox;
399   }
400 
401   /**
402    * Freeze nodes that are close to each other in two snakes.
403    * 
404    * <p>This method is called for two snakes whose centroids are closer than
405    * {@link BOAState.BOAp#proximity}
406    * 
407    * @param a snake
408    * @param b snake
409    * @see #loosen(Nest, int)
410    */
411   private void freezeProx(final Snake/quimp/Snake.html#Snake">Snake a, final Snake b) {
412 
413     Node bn;
414     Node an = a.getHead();
415     double prox;
416 
417     do {
418       bn = b.getHead();
419       do {
420         if (an.isFrozen() && bn.isFrozen()) {
421           // an = an.getNext();
422           bn = bn.getNext();
423           continue;
424         }
425         // test proximity and freeze
426         prox = ExtendedVector2d.distPointToSegment(an.getPoint(), bn.getPoint(),
427                 bn.getNext().getPoint());
428         if (prox < BOA_.qState.boap.proxFreeze) {
429           a.freezeNode(an);
430           b.freezeNode(bn);
431           b.freezeNode(bn.getNext());
432           // an.freeze();
433           // bn.freeze(); // FIXME using this will exclude Snake.FREEZE from updating. Use from
434           // Snake
435           // bn.getNext().freeze();
436           break;
437         }
438         bn = bn.getNext();
439       } while (!bn.isHead());
440 
441       an = an.getNext();
442     } while (!an.isHead());
443 
444   }
445 
446   /**
447    * Implode nest.
448    * 
449    * @param nest nest
450    * @param f frame number
451    * @throws BoaException on snake.implode
452    * @see BOAState.SegParam#expandSnake
453    */
454   public void implode(final Nest nest, int f) throws BoaException {
455     // System.out.println("imploding snake");
456     SnakeHandler snakeH;
457     Snake snake;
458     for (int s = 0; s < nest.size(); s++) {
459       snakeH = nest.getHandler(s);
460       if (nest.getHandler(s).isSnakeHandlerFrozen()) {
461         continue;
462       }
463       snake = snakeH.getLiveSnake();
464       if (snake.alive && f > snakeH.getStartFrame()) {
465         snake.implode();
466       }
467     }
468   }
469 }