View Javadoc
1   package com.github.celldynamics.quimp.plugin.qanalysis;
2   
3   import java.io.File;
4   import java.io.IOException;
5   
6   import org.slf4j.Logger;
7   import org.slf4j.LoggerFactory;
8   
9   import com.github.celldynamics.quimp.Outline;
10  import com.github.celldynamics.quimp.OutlineHandler;
11  import com.github.celldynamics.quimp.QColor;
12  import com.github.celldynamics.quimp.QuimpException;
13  import com.github.celldynamics.quimp.Vert;
14  import com.github.celldynamics.quimp.filesystem.FileExtensions;
15  import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
16  import com.github.celldynamics.quimp.filesystem.converter.FormatConverter;
17  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
18  import com.github.celldynamics.quimp.geom.filters.OutlineProcessor;
19  import com.github.celldynamics.quimp.utils.QuimPArrayUtils;
20  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
21  
22  import ij.IJ;
23  import ij.ImagePlus;
24  import ij.process.ColorProcessor;
25  import ij.process.ImageProcessor;
26  
27  /**
28   * Create spatial temporal maps from ECMM and ANA data.
29   * 
30   * <p>This class can be serialized but only as container of maps. Data required for creation of
31   * those maps are not serialized, thus restored object is not fully functional. As this is last step
32   * in QuimP workflow it may not be necessary to load this JSON anymore.
33   * 
34   * @author rtyson
35   * @author baniuk
36   */
37  public class STmap implements IQuimpSerialize {
38  
39    /**
40     * The Constant LOGGER. It is public because {@link FormatConverter#saveMaps(int)} can replace it
41     */
42    public static Logger LOGGER = LoggerFactory.getLogger(STmap.class.getName());
43  
44    /**
45     * Motility map code.
46     * 
47     * @see #saveMaps(int)
48     */
49    public static final int MOTILITY = 1;
50    /**
51     * Convexity map code.
52     * 
53     * @see #saveMaps(int)
54     */
55    public static final int CONVEXITY = 2;
56    /**
57     * Origin map code.
58     * 
59     * @see #saveMaps(int)
60     */
61    public static final int ORIGIN = 4;
62    /**
63     * Coordinates map code.
64     * 
65     * @see #saveMaps(int)
66     */
67    public static final int COORD = 8;
68    /**
69     * X coordinates map code.
70     * 
71     * @see #saveMaps(int)
72     */
73    public static final int XMAP = 16;
74    /**
75     * Y coordinates map code.
76     * 
77     * @see #saveMaps(int)
78     */
79    public static final int YMAP = 32;
80    /**
81     * Fluorescence channel 1 map code.
82     * 
83     * @see #saveMaps(int)
84     */
85    public static final int FLU1 = 64;
86    /**
87     * Fluorescence channel 2 map code.
88     * 
89     * @see #saveMaps(int)
90     */
91    public static final int FLU2 = 128;
92    /**
93     * Fluorescence channel 3 map code.
94     * 
95     * @see #saveMaps(int)
96     */
97    public static final int FLU3 = 256;
98    /**
99     * Combine all FLU maps.
100    * 
101    * @see #saveMaps(int)
102    */
103   public static final int ALLFLU = FLU1 | FLU2 | FLU3;
104   /**
105    * Combine all maps.
106    */
107   public static final int ALLMAPS = MOTILITY | CONVEXITY | ORIGIN | COORD | XMAP | YMAP | ALLFLU;
108 
109   /**
110    * Coordinates map.
111    * 
112    * <p>Each node has an associated position. The co-ordinate map, rather than contain values
113    * regarding motility, fluorescence or convexity, instead contains the position values of nodes.
114    * The main purpose of the co-ordinate map, along with the origin map, is for tracking positions
115    * through time. Size is Map[getT()][getRes()]. Setter is recommended for proper initialisation.
116    * 
117    * @see <a href=
118    *      "http://pilip.lnx.warwick.ac.uk/docs/develop/QuimP_Guide.html#x1-320005">Manual</a>
119    */
120   private double[][] coordMap;
121   /**
122    * Each node has an origin, the position a node originated from on the previous frame. The
123    * origin map contains origin values and can be used, along with the co-ordinate map, to track
124    * positions through time. Size is Map[getT()][getRes()]. Setter is recommended for proper
125    * initialisation.
126    * 
127    * @see <a href=
128    *      "http://pilip.lnx.warwick.ac.uk/docs/develop/QuimP_Guide.html#x1-320005">Manual</a>
129    */
130   private double[][] originMap;
131   /**
132    * Contains horizontal image pixel co-ordinates (those on the image used for segmentation)
133    * relating to map pixels. Size is Map[getT()][getRes()]. Setter is recommended for proper
134    * initialisation.
135    * 
136    * @see <a href=
137    *      "http://pilip.lnx.warwick.ac.uk/docs/develop/QuimP_Guide.html#x1-320005">Manual</a>
138    */
139   private double[][] xMap;
140   /**
141    * Contains vertical image pixel co-ordinates (those on the image used for segmentation)
142    * relating to map pixels. Size is Map[getT()][getRes()]. Setter is recommended for proper
143    * initialisation.
144    * 
145    * @see <a href=
146    *      "http://pilip.lnx.warwick.ac.uk/docs/develop/QuimP_Guide.html#x1-320005">Manual</a>
147    */
148   private double[][] yMap;
149   /**
150    * Motility map.
151    * 
152    * <p>Pixels are coloured according to node speed, as calculated by ECMM. Red shades represent
153    * expanding regions, blue shades contracting regions. Pixel values within the tiff image are
154    * scaled to fill the colour spectrum. The map file extended _motilityMap.maPQ contains
155    * un-scaled values, in microns per second. Size is Map[getT()][getRes()]. Setter is recommended
156    * for proper initialisation.
157    * 
158    * @see <a href=
159    *      "http://pilip.lnx.warwick.ac.uk/docs/develop/QuimP_Guide.html#x1-320005">Manual</a>
160    */
161   private double[][] motMap;
162 
163   /**
164    * The mig color.
165    */
166   transient int[] migColor;
167 
168   /**
169    * The mig pixels.
170    */
171   transient float[] migPixels;
172   /**
173    * Fluoroscence maps for channels.
174    */
175   public FluoMap[] fluoMaps;
176   /**
177    * Convexity map.
178    * 
179    * <p>Size is Map[getT()][getRes()]. Setter is recommended for proper initialisation.
180    */
181   private double[][] convMap;
182 
183   /**
184    * The conv color.
185    */
186   transient int[] convColor;
187 
188   /**
189    * The conv im P.
190    */
191   transient ImagePlus migImP;
192   transient ImagePlus fluImP;
193   transient ImagePlus convImP;
194   /**
195    * Contain OutlineHandler used for generating maps.
196    * 
197    * <p><b>warning</b>
198    * 
199    * <p>It is not serialized
200    */
201   transient OutlineHandler oh;
202   /**
203    * Resolution of maps.
204    * 
205    * <p>This field together with <tt>T</tt> stands for the dimensions of 2D arrays for storing maps.
206    * For this reason they are serialized. Map[T][res]
207    */
208   private int res;
209   /**
210    * Number of timeframes.
211    */
212   private int T;
213   private double mapPixelHeight = 1;
214   private double mapPixelWidth = 1;
215 
216   private transient Qp params; // configuration parameters
217 
218   /**
219    * Default constructor to satisfy GSon builder. Should not be used for proper object
220    * Initialisation.
221    */
222   public STmap() {
223     this.fluoMaps = new FluoMap[3];
224     this.params = new Qp();
225   }
226 
227   /**
228    * Copy constructor.
229    * 
230    * <p><b>warning</b>
231    * 
232    * <p>Make a copy of serializable fields only
233    * 
234    * @param src source object
235    */
236   public STmapldynamics/quimp/plugin/qanalysis/STmap.html#STmap">STmap(final STmap src) {
237     this();
238     this.coordMap = QuimPArrayUtils.copy2darray(src.coordMap, null);
239     this.originMap = QuimPArrayUtils.copy2darray(src.originMap, null);
240     this.xMap = QuimPArrayUtils.copy2darray(src.xMap, null);
241     this.yMap = QuimPArrayUtils.copy2darray(src.yMap, null);
242     this.motMap = QuimPArrayUtils.copy2darray(src.motMap, null);
243     this.convMap = QuimPArrayUtils.copy2darray(src.convMap, null);
244     this.res = src.res;
245     this.T = src.T;
246     this.mapPixelHeight = src.mapPixelHeight;
247     this.mapPixelWidth = src.mapPixelWidth;
248     this.fluoMaps = new FluoMap[src.fluoMaps.length];
249     for (int i = 0; i < src.fluoMaps.length; i++) {
250       this.fluoMaps[i] = new FluoMap(src.fluoMaps[i]);
251     }
252     try {
253       this.params = (Qp) src.params.clone();
254     } catch (CloneNotSupportedException e) {
255       e.printStackTrace(); // should not happen
256     }
257 
258   }
259 
260   /**
261    * Build object for given.
262    * 
263    * @param o Outline from ECMM
264    * @param r Map resolution in pixels
265    * @param params configuration of Qanalysis
266    * @see com.github.celldynamics.quimp.plugin.qanalysis.Qp
267    */
268   public STmap(OutlineHandler o, int r, Qp params) {
269     this();
270     this.params = params;
271     mapPixelHeight = 1;
272     mapPixelWidth = 1.0d / r;
273     res = r;
274     oh = o;
275     T = oh.getSize();
276 
277     coordMap = new double[T][res];
278     originMap = new double[T][res];
279     xMap = new double[T][res]; // interpolated pixel coordinates
280     yMap = new double[T][res];
281 
282     motMap = new double[T][res];
283     migColor = new int[T * res];
284     migPixels = new float[T * res];
285 
286     // fluMap = new double[T][res];
287     // fluColor = new byte[T * res];
288 
289     convMap = new double[T][res];
290     convColor = new int[T * res];
291 
292     // flu maps
293     Vert v = oh.indexGetOutline(0).getHead();
294     for (int i = 0; i < 3; i++) {
295       fluoMaps[i] = new FluoMap(T, res, i + 1);
296       System.out.println("flou in v: " + v.fluores[i].intensity);
297       if (v.fluores[i].intensity == -2) { // disable if no data
298         IJ.log("No fluorescence data for channel " + (i + 1));
299         fluoMaps[i].setEnabled(false);
300       }
301     }
302 
303     generate();
304     saveConvMotImages(); // save images that are opened already
305   }
306 
307   /**
308    * Generate all maps saved by Q Analysis Fill internal class fields.
309    */
310   private void generate() {
311 
312     this.calcCurvature();
313     Vert zeroVert;
314     Vert v;
315     String migColorMap = "rwb";
316 
317     double fraction;
318     double intMig;
319     double intFlu;
320     double intConv;
321     double target;
322     double actualTarget;
323     QColor color;
324     int pn;
325     Vert fhead;
326     Vert chead;
327 
328     double step = 1.0d / res;
329 
330     // ------debug----
331     // System.out.println("210.in generate: min:"+ oh.migLimits[0]+",
332     // max"+oh.migLimits[1]);
333     // zeroVert = closestFloor(oh.getOutline(28), 0.9138373074502235, 'f');
334     // fHead = findFirstNode(oh.getOutline(28),'f');
335     // cHead = findFirstNode(oh.getOutline(28),'c');
336     // if(true)return;
337     // -------------------------
338 
339     double origin = 0; // co-ord for zeroVert to move to next
340     int frame;
341 
342     for (int tt = 0; tt < T; tt++) {
343 
344       frame = tt + oh.getStartFrame();
345       // System.out.println("frame " + t);
346       pn = tt * res; // pixel index
347 
348       // find the first node in terms of coord and fcoord (not the head)
349       fhead = oh.getStoredOutline(frame).findFirstNode('f');
350       chead = oh.getStoredOutline(frame).findFirstNode('c');
351       // fHead.print();
352       // cHead.print();
353 
354       if (tt == 0) {
355         // for the first time point the head coord node is our starting point
356         zeroVert = chead;
357         fraction = 0;
358         origin = 0;
359       } else {
360         // vert closest below zero (zero being tracked over time from
361         // frame 1!)
362         zeroVert = closestFloor(oh.getStoredOutline(frame), origin, 'f', fhead);
363         // System.out.println("zerovert: " + zeroVert.fCoord +", origin:
364         // " + origin + ", fHead: " + fHead.fCoord);
365         // position of origin between zeroVert and zeroVert.getNext
366         fraction = ffraction(zeroVert, origin, fhead);
367         // System.out.println("resulting fraction: " + fraction);
368 
369         // System.out.print("\nzeroVert.fCoord:"+zeroVert.coord+",
370         // fraction:"+fraction +"\n");
371         origin = interpCoord(zeroVert, fraction, chead); // the new origin
372         // System.out.println("new origin: " + origin);
373       }
374       target = origin; // coord to fill in map next
375 
376       intMig = interpolate(zeroVert.distance, zeroVert.getNext().distance, fraction);
377       motMap[tt][0] = intMig;
378       color = QColor.erColorMap2(migColorMap, intMig, oh.migLimits[0], oh.migLimits[1]);
379       migColor[pn] = color.getColorInt();
380       migPixels[pn] = (float) intMig;
381 
382       // fill fluo maps
383       for (int i = 0; i < 3; i++) {
384         if (fluoMaps[i].isEnabled()) {
385           if (zeroVert.fluores[i].intensity == -2) {
386             IJ.log("ERROR: There are missing fluoresecne values! Run ANA");
387             return;
388           }
389           intFlu = interpolate(zeroVert.fluores[i].intensity,
390                   zeroVert.getNext().fluores[i].intensity, fraction);
391           fluoMaps[i].fill(tt, 0, pn, intFlu, oh.fluLims[i][1]);
392         }
393       }
394 
395       /*
396        * if (zeroVert.floures == -1) { fluMap[t][0] = 0; fluColor[pN] = (byte)
397        * QColor.bwScale(0, 256, oh.maxFlu, 0); } else { intFlu = interpolate(zeroVert.floures,
398        * zeroVert.getNext().floures, fraction); fluMap[t][0] = intFlu; fluColor[pN] = (byte)
399        * QColor.bwScale(intFlu, 256, oh.maxFlu, 0); }
400        */
401 
402       intConv = interpolate(zeroVert.curvatureSum, zeroVert.getNext().curvatureSum, fraction);
403       convMap[tt][0] = intConv;
404       color = QColor.erColorMap2("rbb", intConv, oh.curvLimits[0], oh.curvLimits[1]);
405       convColor[pn] = color.getColorInt();
406 
407       coordMap[tt][0] = origin;
408       originMap[tt][0] = interpFCoord(zeroVert, fraction, fhead);
409       xMap[tt][0] = interpolate(zeroVert.getX(), zeroVert.getNext().getX(), fraction);
410       yMap[tt][0] = interpolate(zeroVert.getY(), zeroVert.getNext().getY(), fraction);
411 
412       if (target >= 1 || target < 0) {
413         System.out.println("target out of range: " + target);
414       }
415 
416       for (int p = 1; p < res; p++) {
417         pn = (tt * res) + p; // pixel index
418         target += step;
419         actualTarget = (target >= 1) ? target - 1 : target; // wraps around to zero
420         // System.out.println("\tactualtarget:"+actualTarget);
421         coordMap[tt][p] = actualTarget;
422 
423         v = closestFloor(oh.getStoredOutline(frame), actualTarget, 'c', chead); // should this be g
424         fraction = cfraction(v, actualTarget, chead);
425 
426         originMap[tt][p] = interpFCoord(v, fraction, fhead);
427         xMap[tt][p] = interpolate(v.getX(), v.getNext().getX(), fraction);
428         yMap[tt][p] = interpolate(v.getY(), v.getNext().getY(), fraction);
429 
430         intMig = interpolate(v.distance, v.getNext().distance, fraction);
431         motMap[tt][p] = intMig;
432         color = QColor.erColorMap2(migColorMap, intMig, oh.migLimits[0], oh.migLimits[1]);
433         migColor[pn] = color.getColorInt();
434         migPixels[pn] = (float) intMig;
435 
436         for (int i = 0; i < 3; i++) {
437           if (fluoMaps[i].isEnabled()) {
438             intFlu = interpolate(v.fluores[i].intensity, v.getNext().fluores[i].intensity,
439                     fraction);
440             fluoMaps[i].fill(tt, p, pn, intFlu, oh.fluLims[i][1]);
441           }
442         }
443         /*
444          * if (zeroVert.floures == -1) { fluMap[t][p] = 0; fluColor[pN] = (byte)
445          * QColor.bwScale(0, 256, oh.maxFlu, 0); } else { intFlu = interpolate(v.floures,
446          * v.getNext().floures, fraction); fluMap[t][p] = intFlu; fluColor[pN] = (byte)
447          * QColor.bwScale(intFlu, 256, oh.maxFlu, 0); }
448          */
449 
450         intConv = interpolate(v.curvatureSum, v.getNext().curvatureSum, fraction);
451         convMap[tt][p] = intConv;
452         color = QColor.erColorMap2("rbb", intConv, oh.curvLimits[0], oh.curvLimits[1]);
453         convColor[pn] = color.getColorInt();
454 
455       }
456 
457     }
458 
459     migImP = map2ImagePlus("motility_map", new ColorProcessor(res, T, migColor));
460     convImP = map2ImagePlus("convexity_map", new ColorProcessor(res, T, convColor));
461     migImP.show();
462     convImP.show();
463     // fluImP.show();
464     // IJ.doCommand("Red");
465 
466     if (params.Build3D) {
467       // create 3D of motility
468       STMap3Ds/quimp/plugin/qanalysis/STMap3D.html#STMap3D">STMap3D map3d = new STMap3D(xMap, yMap, migColor);
469       map3d.build();
470       map3d.toOrigin(oh.indexGetOutline(0).getCentroid());
471       map3d.scale(0.05f);
472       map3d.write(new File("/tmp/cell_02.wrl"));
473 
474       // create 3D of curvature
475       STMap3Duimp/plugin/qanalysis/STMap3D.html#STMap3D">STMap3D map3dCur = new STMap3D(xMap, yMap, convColor);
476       map3dCur.build();
477       map3dCur.toOrigin(oh.indexGetOutline(0).getCentroid());
478       map3dCur.scale(0.05f);
479       map3dCur.write(new File("/tmp/cell_02_cur.wrl"));
480     }
481 
482     // create fluo images
483 
484     for (int i = 0; i < 3; i++) {
485       if (!fluoMaps[i].isEnabled()) {
486         continue;
487       }
488 
489       fluImP = IJ.createImage(params.filename + "_fluoCH" + fluoMaps[i].getChannel(), "8-bit black",
490               res, T, 1);
491       fluImP.getProcessor().setPixels(fluoMaps[i].getColours());
492       resize(fluImP);
493       setCalibration(fluImP);
494       fluImP.show();
495 
496       try {
497         Thread.sleep(500); // needed to let imageJ set the right colour maps
498       } catch (Exception e) {
499         ;
500       }
501 
502       IJ.doCommand("Red"); // this don't always work. dun know why
503       String tmpfilename = FileExtensions.fluomapFileExt.replaceFirst("%",
504               Integer.toString(fluoMaps[i].getChannel()));
505       IJ.saveAs(fluImP, "tiff",
506               params.outFile.getParent() + File.separator + params.filename + tmpfilename);
507     }
508 
509     // saveMaps(); // save maQP files
510 
511     if (QuimPArrayUtils.sumArray(migColor) == 0) {
512       IJ.showMessage("ECMM data is missing (or corrupt), and is needed for building accurate maps.+"
513               + "\nPlease run ECMM (fluorescence data will be lost)");
514     }
515     // test making LUT images
516     /*
517      * ImagePlus migImPLut = IJ.createImage("mig_32", "32-bit", res, T,1); ImageProcessor
518      * ipFloat = new FloatProcessor(res, T, migPixels, null); LUT lut = new LUT();
519      * ipFloat.setLut(lut) migImPLut.setProcessor(ipFloat); resize(migImPLut); migImPLut.show();
520      */
521   }
522 
523   /**
524    * Save map files (maQP) on disk.
525    * 
526    * @param maps Map to be saved, defined in this class. Use {@link STmap#ALLMAPS} for save all
527    * @throws QuimpException any error with saving maps (except IO)
528    */
529   public void saveMaps(int maps) throws QuimpException {
530     try {
531       if ((maps & MOTILITY) == MOTILITY) {
532         File f = new File(params.outFile.getPath() + FileExtensions.motmapFileExt);
533         QuimPArrayUtils.arrayToFile(motMap, ",", f);
534         LOGGER.info("\tSaved motility map at: " + f.getAbsolutePath());
535       }
536       if ((maps & CONVEXITY) == CONVEXITY) {
537         File f = new File(params.outFile.getPath() + FileExtensions.convmapFileExt);
538         QuimPArrayUtils.arrayToFile(convMap, ",", f);
539         LOGGER.info("\tSaved convexity map at: " + f.getAbsolutePath());
540       }
541       if ((maps & ORIGIN) == ORIGIN) {
542         File f = new File(params.outFile.getPath() + FileExtensions.originmapFileExt);
543         QuimPArrayUtils.arrayToFile(originMap, ",", f);
544         LOGGER.info("\tSaved origin map at: " + f.getAbsolutePath());
545       }
546       if ((maps & COORD) == COORD) {
547         File f = new File(params.outFile.getPath() + FileExtensions.coordmapFileExt);
548         QuimPArrayUtils.arrayToFile(coordMap, ",", f);
549         LOGGER.info("\tSaved coord map at: " + f.getAbsolutePath());
550       }
551       if ((maps & XMAP) == XMAP) {
552         File f = new File(params.outFile.getPath() + FileExtensions.xmapFileExt);
553         QuimPArrayUtils.arrayToFile(xMap, ",", f);
554         LOGGER.info("\tSaved x map at: " + f.getAbsolutePath());
555       }
556       if ((maps & YMAP) == YMAP) {
557         File f = new File(params.outFile.getPath() + FileExtensions.ymapFileExt);
558         QuimPArrayUtils.arrayToFile(yMap, ",", f);
559         LOGGER.info("\tSaved y map at: " + f.getAbsolutePath());
560       }
561       if ((maps & FLU1) == FLU1) {
562         saveFluoroMap(0);
563       }
564       if ((maps & FLU2) == FLU2) {
565         saveFluoroMap(1);
566       }
567       if ((maps & FLU3) == FLU3) {
568         saveFluoroMap(2);
569       }
570     } catch (NullPointerException np) {
571       LOGGER.debug(np.getMessage(), np);
572       throw new QuimpException("Can not save map. Input array does not exist: " + np.getMessage());
573     } catch (IOException e1) {
574       IJ.error("Could not write Map file:\n " + e1.getMessage());
575       LOGGER.debug(e1.getMessage(), e1);
576       throw new QuimpException(e1);
577     } catch (Exception e) {
578       LOGGER.debug(e.getMessage(), e);
579       throw new QuimpException(e);
580     }
581   }
582 
583   /**
584    * Saves selected fluoro map.
585    * 
586    * @param index map index in {@link #fluoMaps} array.
587    * 
588    * @throws IOException on file save
589    */
590   private void saveFluoroMap(int index) throws IOException {
591     if (!fluoMaps[index].isEnabled()) {
592       LOGGER.debug("Selected map " + (index + 1) + " is not enabled");
593     } else {
594       String tmpfilename = FileExtensions.fluomapFileExt.replaceFirst("%",
595               Integer.toString(fluoMaps[index].getChannel()));
596       File f = new File(params.outFile.getPath() + tmpfilename);
597       QuimPArrayUtils.arrayToFile(fluoMaps[index].getMap(), ",", f);
598       LOGGER.info("\tSaved fluoro map at: " + f.getAbsolutePath());
599     }
600   }
601 
602   /**
603    * Saves already opened generated motility and convexity images.
604    */
605   private void saveConvMotImages() {
606     // save images
607     IJ.saveAs(migImP, "tiff", params.outFile.getParent() + File.separator + params.filename
608             + FileExtensions.motimageFileExt);
609     IJ.saveAs(convImP, "tiff", params.outFile.getParent() + File.separator + params.filename
610             + FileExtensions.convimageFileExt);
611   }
612 
613   /**
614    * Convert ImageProcessor created from Map to ImagePlus.
615    * 
616    * <p>Usually maps are non-square and need to be rescaled for correct presentation, what this
617    * method does.
618    * 
619    * <p><b>Note</b>
620    * 
621    * <p>Take care about what <tt>ImageProcessor</tt> is used. The <tt>ColorProcessor</tt> is created
622    * from 1D array, whereas e.g. <tt>FloatProcessor</tt> from 2D arrays. This causes that the same
623    * map will be displayed with different orientation. QuimP natively uses maps presented by
624    * <tt>ColorProcessor</tt>, if one uses <tt>FloatProcessor</tt> it must be rotated and flip to
625    * maintain correct orientation. See the following example:
626    * 
627    * <pre>
628    * {
629    *   &#64;code
630    *   float[][] motMap = QuimPArrayUtils.double2float(mapCell.motMap);
631    *   ImageProcessor imp = new FloatProcessor(motMap).rotateRight();
632    *   imp.flipHorizontal();
633    *   mapCell.map2ImagePlus("motility_map", imp).show();
634    * }
635    * </pre>
636    * 
637    * @param name Name of the ImagePlus window
638    * @param imp Image processor
639    * @return Created ImagePlus object
640    * @see #map2ColorImagePlus(String, String, double[][], double, double)
641    */
642   public ImagePlus map2ImagePlus(String name, ImageProcessor imp) {
643     ImagePlus ret = new ImagePlus(name, imp);
644     resize(ret);
645     setCalibration(ret);
646     return ret;
647   }
648 
649   /**
650    * Convert raw map to colorscale.
651    * 
652    * <p><b>warning</b>
653    * 
654    * <p>Assumes that input 2D array map is regular and not column. This method can be used with data
655    * restored from <i>QCONF</i> file in the following way:
656    * 
657    * <pre>
658    * {@code
659    * // Maps are correlated in order with Outlines in DataContainer.
660    * mapCell.map2ColorImagePlus("motility_map", "rwb",mapCell.motMap, oHs.oHs.get(h).migLimits[0],
661    *               oHs.oHs.get(h).migLimits[1]).show();
662    * }
663    * </pre>
664    * 
665    * @param name Name of the ImagePlus
666    * @param map Map to convert
667    * @param palette Palette code, can be "rww" or "rwb".
668    * @param min Minimum value in Outline that was used for creation of this map
669    * @param max Maximum value in Outline that was used for creation of this map
670    * @return Created ImagePlus object
671    * @see #map2ImagePlus(String, ImageProcessor)
672    * @see QColor#erColorMap2(String, double, double, double) for palettes
673    */
674   public ImagePlus map2ColorImagePlus(String name, String palette, double[][] map, double min,
675           double max) {
676     int[] migColor = new int[map.length * map[0].length];
677     int pn = 0;
678     int t = map.length;
679     int res = map[0].length;
680     for (int r = 0; r < t; r++) {
681       for (int c = 0; c < res; c++) {
682         // pN = (c * mapCell.getT()) + r;
683         QColor color = QColor.erColorMap2(palette, map[r][c], min, max);
684         migColor[pn++] = color.getColorInt();
685       }
686     }
687     return map2ImagePlus(name, new ColorProcessor(res, t, migColor));
688   }
689 
690   private Vert closestFloor(Outline o, Verts="jxr_keyword">double target, char c, Vert head) {
691     // find the vert with coor closest (floored) to target coordinate
692 
693     Vert v = head; // the fcoord or cCoord head
694     double coord;
695     double coordNext;
696     do {
697       coord = (c == 'f') ? v.fCoord : v.coord;
698       coordNext = (c == 'f') ? v.getNext().fCoord : v.getNext().coord;
699 
700       if (coord == target) {
701         break;
702       }
703       if (coordNext > target && coord < target) {
704         break;
705       }
706 
707       v = v.getNext();
708     } while (v.getNext().getTrackNum() != head.getTrackNum());
709 
710     // System.out.println("found fcoord " + v.fCoord);
711     return v;
712   }
713 
714   private double cfraction(Vertuimp/Vert.html#Vert">Vert v, double target, Vert head) {
715     // calc fraction for iterpolation
716     double v2coord;
717     if (v.getNext().getTrackNum() == head.getTrackNum()) { // passed zero
718       v2coord = v.getNext().coord + 1;
719       target = (target > v.coord) ? target : target + 1; // not passed zero as all values are zero!
720     } else {
721       v2coord = v.getNext().coord;
722     }
723 
724     double frac = (target - v.coord) / (v2coord - v.coord);
725     // System.out.println("\tffraction:
726     // |v:"+v.coord+"|v2:"+v2coord+"|tar:"+target+"|frac:"+frac);
727     if (frac >= 1) {
728       frac = frac - 1;
729       LOGGER.warn("WARNING- frac corrected: " + frac);
730     }
731     if (frac > 1 || frac < 0) {
732       LOGGER.warn("!WARNING, frac out of range:" + frac);
733     }
734     return frac;
735   }
736 
737   private double ffraction(Vertuimp/Vert.html#Vert">Vert v, double target, Vert head) {
738     // calc fraction for iterpolation
739     double v2coord;
740     if (v.getNext().getTrackNum() == head.getTrackNum()) { // passed zero
741       // System.out.println("\tffraction: pass zero. wrap");
742       v2coord = v.getNext().fCoord + 1;
743       target = (target > v.fCoord) ? target : target + 1; // not passed zero as all values are zero!
744     } else {
745       v2coord = v.getNext().fCoord;
746     }
747     double frac = (target - v.fCoord) / (v2coord - v.fCoord);
748     // System.out.println("\tffraction:
749     // |v:"+v.fCoord+"|v2:"+v2coord+"|tar:"+target+"|frac:"+frac);
750 
751     if (frac >= 1) {
752       frac = frac - 1;
753       LOGGER.warn("WARNING- frac corrected: " + frac);
754     }
755     if (frac > 1 || frac < 0) {
756       LOGGER.warn("WARNING, frac out of range:" + frac);
757     }
758 
759     if (Double.isNaN(frac)) {
760       LOGGER.warn("WARNING, frac is nan:" + frac);
761       System.out.println("\tffraction: |v:" + v.fCoord + "|v2:" + v2coord + "|tar:" + target
762               + "|frac:" + frac);
763       frac = 0.5;
764     }
765     return frac;
766   }
767 
768   private double interpCoord(Vert/quimp/Vert.html#Vert">Vert v, double frac, Vert head) {
769     double v2Coord = (v.getNext().getTrackNum() == head.getTrackNum()) ? v.getNext().coord + 1
770             : v.getNext().coord; // pass zero
771     double dis = v2Coord - v.coord;
772     double targ = v.coord + (dis * frac);
773 
774     if (targ >= 1) {
775       targ += -1; // passed zero
776     }
777 
778     if (targ < 0) {
779       LOGGER.error("ERROR: target less than zero");
780     }
781 
782     return targ;
783   }
784 
785   private double interpFCoord(Vert/quimp/Vert.html#Vert">Vert v, double frac, Vert head) {
786     double v2Coord = (v.getNext().getTrackNum() == head.getTrackNum()) ? v.getNext().fCoord + 1
787             : v.getNext().fCoord;
788     double dis = v2Coord - v.fCoord;
789     double targ = v.fCoord + (dis * frac);
790 
791     if (targ >= 1) {
792       targ += -1; // passed zero
793     }
794 
795     if (targ < 0) {
796       LOGGER.error("ERROR: target less than zero");
797     }
798 
799     return targ;
800   }
801 
802   private double interpolate(double v1, double v2, double frac) {
803     return v1 + ((v2 - v1) * frac);
804   }
805 
806   /**
807    * Calculates convexity by smoothing or averaging across nodes.
808    */
809   private void calcCurvature() {
810 
811     Outline o;
812     Vert v;
813 
814     oh.curvLimits = new double[2];
815 
816     for (int f = oh.getStartFrame(); f <= oh.getEndFrame(); f++) {
817       o = oh.getStoredOutline(f);
818       if (o == null) {
819         IJ.log("ERROR: Outline at frame " + f + " is empty");
820         continue;
821       }
822 
823       // update local curvature just in case
824       o.updateCurvature();
825 
826       // set default curvatures
827       v = o.getHead();
828       do {
829         v.curvatureSmoothed = v.curvatureLocal;
830         v.curvatureSum = v.curvatureLocal;
831         v = v.getNext();
832       } while (!v.isHead());
833 
834       new OutlineProcessor<Outline>(o).averageCurvature(params.avgCov).sumCurvature(params.sumCov);
835       // averageCurvature(o);
836       // sumCurvature(o);
837 
838       // find min and max of sum curvature
839 
840       v = o.getHead();
841       if (f == oh.getStartFrame()) {
842         oh.curvLimits[1] = v.curvatureSum;
843         oh.curvLimits[0] = v.curvatureSum;
844       }
845       do {
846         if (v.curvatureSum > oh.curvLimits[1]) {
847           oh.curvLimits[1] = v.curvatureSum;
848         }
849         if (v.curvatureSum < oh.curvLimits[0]) {
850           oh.curvLimits[0] = v.curvatureSum;
851         }
852         v = v.getNext();
853       } while (!v.isHead());
854       // System.out.println("Min curv: " + oh.curvLimits[0] + ", max curv:
855       // " + oh.curvLimits[1]);
856 
857     }
858 
859     // System.out.println("curve limits before: " + oh.curvLimits[0] + ", "
860     // + oh.curvLimits[1]);
861     oh.curvLimits = QuimpToolsCollection.setLimitsEqual(oh.curvLimits);
862     // System.out.println("curve limits after: " + oh.curvLimits[0] + ", " +
863     // oh.curvLimits[1]);
864   }
865 
866   @Deprecated
867   private void averageCurvature(Outline o) {
868 
869     Vert v;
870     Vert tmpV;
871     double totalCur;
872     double distance;
873     int count;
874 
875     // avertage over curvatures
876     if (params.avgCov > 0) {
877       // System.out.println("new outline");
878       v = o.getHead();
879       do {
880         // System.out.println("\tnew vert");
881         totalCur = v.curvatureLocal; // reset
882         count = 1;
883 
884         // add up curvatures of prev nodes
885         // System.out.println("\t prev nodes");
886         tmpV = v.getPrev();
887         distance = 0;
888         do {
889           distance += ExtendedVector2d.lengthP2P(tmpV.getNext().getPoint(), tmpV.getPoint());
890           totalCur += tmpV.curvatureLocal;
891           count++;
892           tmpV = tmpV.getPrev();
893         } while (distance < params.avgCov / 2);
894 
895         // add up curvatures of next nodes
896         distance = 0;
897         tmpV = v.getNext();
898         do {
899           distance += ExtendedVector2d.lengthP2P(tmpV.getPrev().getPoint(), tmpV.getPoint());
900           totalCur += tmpV.curvatureLocal;
901           count++;
902           tmpV = tmpV.getNext();
903         } while (distance < params.avgCov / 2);
904 
905         v.curvatureSmoothed = totalCur / count;
906 
907         v = v.getNext();
908       } while (!v.isHead());
909     }
910   }
911 
912   /**
913    * Sum smoothed curavture over a region of the membrane.
914    * 
915    * @param o the outline
916    */
917   @Deprecated
918   private void sumCurvature(Outline o) {
919 
920     Vert v;
921     Vert tmpV;
922     double totalCur;
923     double distance;
924     // avertage over curvatures
925     if (params.sumCov > 0) {
926       LOGGER.trace("summing curv");
927       v = o.getHead();
928       do {
929         // System.out.println("\tnew vert");
930         totalCur = v.curvatureSmoothed; // reset
931         // add up curvatures of prev nodes
932         // System.out.println("\t prev nodes");
933         tmpV = v.getPrev();
934         distance = 0;
935         do {
936           distance += ExtendedVector2d.lengthP2P(tmpV.getNext().getPoint(), tmpV.getPoint());
937           totalCur += tmpV.curvatureSmoothed;
938           tmpV = tmpV.getPrev();
939         } while (distance < params.sumCov / 2);
940 
941         // add up curvatures of next nodes
942         distance = 0;
943         tmpV = v.getNext();
944         do {
945           distance += ExtendedVector2d.lengthP2P(tmpV.getPrev().getPoint(), tmpV.getPoint());
946           totalCur += tmpV.curvatureSmoothed;
947           tmpV = tmpV.getNext();
948         } while (distance < params.sumCov / 2);
949 
950         v.curvatureSum = totalCur;
951 
952         v = v.getNext();
953       } while (!v.isHead());
954     }
955 
956   }
957 
958   /**
959    * Compute vertical resolution tiff image generated from map.
960    * 
961    * <p>Horizontal resolution is as defined in UI but vertical can be different. USe this method
962    * if input coordinates comes from image but not map.
963    * 
964    * @return vertical resolution.
965    * @see STmap#resize(ImagePlus)
966    */
967   public int getVerticalResolution() {
968     double vertRes = Math.ceil((double) res / (double) T);
969     return (int) (T * vertRes);
970   }
971 
972   /**
973    * Resize image to size of this map.
974    * 
975    * @param imp image to resize.
976    */
977   public void resize(ImagePlus imp) {
978     if (T >= res * 0.9) {
979       return; // don't resize if its going to compress frames
980     }
981     ImageProcessor ip = imp.getProcessor();
982 
983     ip.setInterpolationMethod(ImageProcessor.NONE);
984 
985     double vertRes = Math.ceil((double) res / (double) T);
986     mapPixelHeight = 1.0d / vertRes;
987     vertRes = T * vertRes;
988 
989     ip = ip.resize(res, (int) vertRes);
990     imp.setProcessor(ip);
991 
992   }
993 
994   /**
995    * Calibrate image for sizes of this maps.
996    * 
997    * @param imp image to calibrate.
998    */
999   public void setCalibration(ImagePlus imp) {
1000     imp.getCalibration().setUnit("frames");
1001     imp.getCalibration().pixelHeight = mapPixelHeight;
1002     imp.getCalibration().pixelWidth = mapPixelWidth;
1003     LOGGER.debug("PixelWidth=" + mapPixelWidth + " PixelHeight=" + mapPixelHeight);
1004   }
1005 
1006   /**
1007    * Return resolution of the map.
1008    * 
1009    * <p>This is value set by user in Q Analysis UI.
1010    * 
1011    * @return the res
1012    */
1013   public int getRes() {
1014     return res;
1015   }
1016 
1017   /**
1018    * Return number of outline points.
1019    * 
1020    * <p>This is value obtained after interpolation of Outlines.
1021    * 
1022    * @return the T
1023    */
1024   public int getT() {
1025     return T;
1026   }
1027 
1028   /**
1029    * getxMap.
1030    * 
1031    * @return the xMap
1032    */
1033   public double[][] getxMap() {
1034     return xMap;
1035   }
1036 
1037   /**
1038    * Set map. Initialise also resolution fields. All maps must have the same resolution.
1039    * 
1040    * @param xmap the xMap to set
1041    * @see STmap#getRes()
1042    * @see #getT()
1043    */
1044   public void setxMap(double[][] xmap) {
1045     this.xMap = xmap;
1046     T = xmap.length;
1047     res = xmap[0].length;
1048   }
1049 
1050   /**
1051    * getyMap.
1052    * 
1053    * @return the yMap
1054    */
1055   public double[][] getyMap() {
1056     return yMap;
1057   }
1058 
1059   /**
1060    * Set map. Initialise also resolution fields. All maps must have the same resolution.
1061    * 
1062    * @param ymap the yMap to set
1063    * @see STmap#getRes()
1064    * @see #getT()
1065    */
1066   public void setyMap(double[][] ymap) {
1067     this.yMap = ymap;
1068     T = ymap.length;
1069     res = ymap[0].length;
1070   }
1071 
1072   /**
1073    * Get coordinates map.
1074    * 
1075    * @return the coordMap
1076    */
1077   public double[][] getCoordMap() {
1078     return coordMap;
1079   }
1080 
1081   /**
1082    * Set map. Initialise also resolution fields. All maps must have the same resolution.
1083    * 
1084    * @param coordMap the coordMap to set
1085    * @see STmap#getRes()
1086    * @see #getT()
1087    */
1088   public void setCoordMap(double[][] coordMap) {
1089     this.coordMap = coordMap;
1090     T = coordMap.length;
1091     res = coordMap[0].length;
1092   }
1093 
1094   /**
1095    * Get origin map.
1096    * 
1097    * @return the originMap
1098    */
1099   public double[][] getOriginMap() {
1100     return originMap;
1101   }
1102 
1103   /**
1104    * Set map. Initialise also resolution fields. All maps must have the same resolution.
1105    * 
1106    * @param originMap the originMap to set
1107    * 
1108    * @see STmap#getRes()
1109    * @see #getT()
1110    */
1111   public void setOriginMap(double[][] originMap) {
1112     this.originMap = originMap;
1113     T = originMap.length;
1114     res = originMap[0].length;
1115   }
1116 
1117   /**
1118    * getMotMap.
1119    * 
1120    * @return the motMap
1121    */
1122   public double[][] getMotMap() {
1123     return motMap;
1124   }
1125 
1126   /**
1127    * Set map. Initialise also resolution fields. All maps must have the same resolution.
1128    * 
1129    * @param motMap the motMap to set
1130    * 
1131    * @see STmap#getRes()
1132    * @see #getT()
1133    */
1134   public void setMotMap(double[][] motMap) {
1135     this.motMap = motMap;
1136     T = motMap.length;
1137     res = motMap[0].length;
1138   }
1139 
1140   /**
1141    * getConvMap.
1142    * 
1143    * @return the convMap
1144    */
1145   public double[][] getConvMap() {
1146     return convMap;
1147   }
1148 
1149   /**
1150    * getFluMaps.
1151    * 
1152    * @return the fluo maps
1153    */
1154   public FluoMap[] getFluMaps() {
1155     return fluoMaps;
1156   }
1157 
1158   /**
1159    * Set map. Initialise also resolution fields. All maps must have the same resolution.
1160    * 
1161    * @param convMap the convMap to set
1162    * @see STmap#getRes()
1163    * @see #getT()
1164    */
1165   public void setConvMap(double[][] convMap) {
1166     this.convMap = convMap;
1167     T = convMap.length;
1168     res = convMap[0].length;
1169   }
1170 
1171   /**
1172    * Attach other than default configuration object.
1173    * 
1174    * <p>This is passed by constructors, use {@link #setParams(Qp)} if deserialised.
1175    * 
1176    * @param params the params to set
1177    */
1178   public void setParams(Qp params) {
1179     this.params = params;
1180   }
1181 
1182   /*
1183    * (non-Javadoc)
1184    * 
1185    * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#beforeSerialize()
1186    */
1187   @Override
1188   public void beforeSerialize() {
1189   }
1190 
1191   /*
1192    * (non-Javadoc)
1193    * 
1194    * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#afterSerialize()
1195    */
1196   @Override
1197   public void afterSerialize() throws Exception {
1198     LOGGER.debug("This class can not be deserialzied without assgning OutlineHndler and Qp");
1199   }
1200 }