View Javadoc
1   package com.github.celldynamics.quimp.plugin.ecmm;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.util.ArrayList;
6   // import java.util.Vector;
7   import java.util.Random;
8   
9   import org.slf4j.Logger;
10  import org.slf4j.LoggerFactory;
11  
12  import com.github.celldynamics.quimp.Nest;
13  import com.github.celldynamics.quimp.Outline;
14  import com.github.celldynamics.quimp.OutlineHandler;
15  import com.github.celldynamics.quimp.QParams;
16  import com.github.celldynamics.quimp.QParamsQconf;
17  import com.github.celldynamics.quimp.QuimP;
18  import com.github.celldynamics.quimp.QuimpException;
19  import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
20  import com.github.celldynamics.quimp.SnakeHandler;
21  import com.github.celldynamics.quimp.Vert;
22  import com.github.celldynamics.quimp.filesystem.DataContainer;
23  import com.github.celldynamics.quimp.filesystem.FileExtensions;
24  import com.github.celldynamics.quimp.filesystem.OutlinesCollection;
25  import com.github.celldynamics.quimp.filesystem.QconfLoader;
26  import com.github.celldynamics.quimp.filesystem.converter.FormatConverter;
27  import com.github.celldynamics.quimp.geom.ExtendedVector2d;
28  import com.github.celldynamics.quimp.plugin.AbstractPluginQconf;
29  import com.github.celldynamics.quimp.plugin.QuimpPluginException;
30  
31  import ij.IJ;
32  import ij.WindowManager;
33  import ij.gui.YesNoCancelDialog;
34  import ij.process.ImageProcessor;
35  
36  /*
37   * //!>
38   * @startuml doc-files/ECMM_Mapping_1_UML.png
39   * start
40   * :Check registration;
41   * if (input file given) then (no) 
42   * :ask user; 
43   * endif
44   * :Load config file;
45   * if (QUIMP_11 file) then (yes)
46   * :process it;
47   * :scan for other files;
48   * repeat 
49   * :process other file;
50   * repeat while(more files?) else (no)
51   * if(BOA data) then (no)
52   * stop
53   * endif
54   * if(ECMM data) then (yes)
55   * if(overwrite?) then (no)
56   * end
57   * endif
58   * endif
59   * :process it;
60   * endif
61   * end
62   * @enduml
63   * 
64   * //!< 
65   */
66  /**
67   * Main ECMM implementation class.
68   * 
69   * <p>To disable plotting hide {@link ECMplot#imPlus} by accessing {@link #plot}.
70   * 
71   * @author Richard Tyson. 23/09/2009. ECM Mapping Systems Biology DTC, Warwick University.
72   * @author p.baniukiewicz
73   *
74   */
75  public class ECMM_Mapping extends AbstractPluginQconf {
76  
77    private static String thisPluginName = "ECMM Mapping";
78  
79    private File fileToLoad = null; // file to load paQP/QCONF
80    /**
81     * The Constant LOGGER.
82     */
83    static final Logger LOGGER = LoggerFactory.getLogger(ECMM_Mapping.class.getName());
84    private OutlineHandler oh;
85    private OutlineHandler outputH;
86    private OutlinesCollection outputOutlineHandlers; // output for new data file
87  
88    /**
89     * The plot.
90     */
91    public static ECMplot plot;
92  
93    /**
94     * Default constructor called on plugin run from IJ GUI.
95     */
96    public ECMM_Mapping() {
97      super(new EcmmOptions(), thisPluginName);
98    }
99  
100   /**
101    * Constructor called by ANA.
102    * 
103    * @param frames frame
104    */
105   public ECMM_Mapping(int frames) { // work around. b is nothing
106     this();
107     if (ECMp.plot) {
108       plot = new ECMplot(frames);
109     }
110   }
111 
112   /**
113    * Constructor that allows to provide own parameters. Set apiCall to true.
114    * 
115    * @param paramString it can be null to ask user for file or it can be parameters string like that
116    *        passed in macro.
117    * @throws QuimpPluginException on any error
118    */
119   public ECMM_Mapping(String paramString) throws QuimpPluginException {
120     super(paramString, new EcmmOptions(), thisPluginName); // will parse and fill options
121   }
122 
123   /**
124    * Create object and fill options with specified file.
125    * 
126    * <p>Set apiCall to true and errors redirects to console.
127    * 
128    * @param file file to process.
129    */
130   public ECMM_Mapping(File file) {
131     super(new EcmmOptions(file), thisPluginName);
132     apiCall = true;
133   }
134 
135   /*
136    * (non-Javadoc)
137    * 
138    * @see com.github.celldynamics.quimp.plugin.PluginTemplate#run(java.lang.String)
139    */
140   @Override
141   public void run(String arg) {
142     super.run(arg);
143   }
144 
145   /*
146    * (non-Javadoc)
147    * 
148    * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#loadFile(java.lang.String)
149    */
150   @Override
151   protected void loadFile(String paramFile) throws QuimpException {
152     if (options.paramFile == null || options.paramFile.isEmpty()) {
153       fileToLoad = null;
154     } else {
155       fileToLoad = new File(options.paramFile);
156     }
157 
158     qconfLoader = new QconfLoader(fileToLoad); // load file
159     if (qconfLoader == null || qconfLoader.getQp() == null) {
160       return; // failed to load exit
161     }
162     if (qconfLoader.isFileLoaded() == QParams.QUIMP_11) { // old path
163       QParams qp;
164       runFromPaqp();
165       // old flow with paQP files - detect other paQP
166       File[] otherPaFiles = qconfLoader.getQp().findParamFiles();
167       if (otherPaFiles.length > 0) {
168         YesNoCancelDialog yncd = new YesNoCancelDialog(IJ.getInstance(), "Batch Process?",
169                 "\tBatch Process?\n\n" + "Process other " + FileExtensions.configFileExt
170                         + " files in the same folder with ECMM?\n"
171                         + "[Files already run through ECMM will be skipped!]");
172         if (yncd.yesPressed()) {
173           ArrayList<String> runOn = new ArrayList<String>(otherPaFiles.length);
174           ArrayList<String> skipped = new ArrayList<String>(otherPaFiles.length);
175 
176           for (int j = 0; j < otherPaFiles.length; j++) {
177             plot.close();
178             qconfLoader = new QconfLoader(otherPaFiles[j]); // load file
179             if (qconfLoader == null || qconfLoader.getQp() == null) {
180               return; // failed to load exit
181             }
182             qp = qconfLoader.getQp();
183             if (!qp.isEcmmHasRun()) {
184               IJ.log("Running on " + otherPaFiles[j].getAbsolutePath());
185               runFromPaqp();
186               runOn.add(otherPaFiles[j].getName());
187             } else {
188               IJ.log("Skipped " + otherPaFiles[j].getAbsolutePath());
189               skipped.add(otherPaFiles[j].getName());
190             }
191 
192           }
193           IJ.log("\n\nBatch - Successfully ran ECMM on:");
194           for (int i = 0; i < runOn.size(); i++) {
195             IJ.log(runOn.get(i));
196           }
197           IJ.log("\nSkipped:");
198           for (int i = 0; i < skipped.size(); i++) {
199             IJ.log(skipped.get(i));
200           }
201         } else {
202           return; // no batch processing
203         }
204       }
205     } else if (qconfLoader.isFileLoaded() == QParams.NEW_QUIMP) { // new path
206       // validate in case new format
207       validate();
208       if (qconfLoader.isECMMPresent() && apiCall == false && errorSink == MessageSinkTypes.GUI) {
209         YesNoCancelDialog ync;
210         ync = new YesNoCancelDialog(IJ.getInstance(), "Overwrite",
211                 "You are about to override previous ECMM results. Is it ok?");
212         if (!ync.yesPressed()) { // if no or cancel
213           IJ.log("No changes done in input file.");
214           return; // end}
215         }
216       }
217       runFromQconf();
218       IJ.log("The new data file " + qconfLoader.getQp().getParamFile().toString()
219               + " has been updated by results of ECMM analysis.");
220     } else {
221       throw new QuimpPluginException("QconfLoader returned unknown version of QuimP or error: "
222               + qconfLoader.isFileLoaded());
223     }
224   }
225 
226   /*
227    * (non-Javadoc)
228    * 
229    * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#executer()
230    */
231   @Override
232   protected void executer() throws QuimpException {
233     // we need to use different handling for multiple paQP files, so use own loader
234     if (apiCall == true) { // if run from other constructor, override sink
235       errorSink = MessageSinkTypes.CONSOLE;
236     }
237     super.executer();
238   }
239 
240   /**
241    * Called by ANA if no ECCM results are in file (old path).
242    * 
243    * @param m OutlineHandler
244    * @param ipr ImageProcessor
245    * @param d cortex?
246    * @return Processed outline
247    */
248   public OutlineHandler./../../../com/github/celldynamics/quimp/OutlineHandler.html#OutlineHandler">OutlineHandler runByANA(OutlineHandler m, ImageProcessor ipr, double d) {
249     oh = m;
250     ECMp.image = ipr;
251     ECMp.setParams(oh.maxLength);
252     ECMp.startFrame = oh.getStartFrame();
253     ECMp.endFrame = oh.getEndFrame();
254     ECMp.plot = false;
255     ECMp.ANA = true;
256     ECMp.anaMigDist = d;
257     ECMp.migQ = 1.5E-5; //
258     ECMp.tarQ = -1.5E-5; // use same charge
259 
260     if (ECMp.plot) {
261       plot = new ECMplot(oh.getSize() - 1);
262     }
263 
264     // ECMp.setParams(m.indexGetOutline(0));
265 
266     // *******adjust params for ana***********
267     ECMp.h = 0.9;
268     ECMp.chargeDensity = 4;
269     ECMp.d = 0.4;
270     ECMp.maxVertF = 0.7;
271     // *************************
272     runPlugin();
273     // IJ.log("ECM Mapping FINISHED");
274     return outputH;
275   }
276 
277   /**
278    * Main executive for ECMM plugin.
279    * 
280    * @see #runFromQconf()
281    * @see #runFromPaqp()
282    */
283   private void runPlugin() {
284     long time = System.currentTimeMillis();
285     if (!ECMp.ANA) {
286       IJ.log("ECMM resolution: " + ECMp.markerRes + "(av. spacing)\n");
287     }
288 
289     outputH = new OutlineHandler(oh);
290     ECMp.unSnapped = 0;
291     // int skippedFrames = 0; // if a frame is skipped need to divide next
292     // time point migration by 2, etc...
293 
294     Mapping map1;
295     int f = ECMp.startFrame; // now in frames
296     Outline o1 = oh.getStoredOutline(f);
297     // resolution is always as in segmentation - not now
298     if (!ECMp.ANA) {
299       if (Math.abs(ECMp.markerRes) > 0) {
300         o1.setResolution(Math.abs(ECMp.markerRes));
301       }
302     }
303     // LOGGER.trace("Outline o1:head =[" + o1.getHead().getX() + "," + o1.getHead().getY() +
304     // "]");
305     o1.resetAllCoords();
306     o1.clearFluores();
307     o1.calcCentroid(); // TODO this should be called as in case of Snakes
308 
309     outputH.save(o1, f);
310     Outline o2;
311 
312     int stopAt = -1; // debug break
313 
314     for (; f <= oh.getEndFrame() - 1; f++) {
315       if (f == stopAt) {
316         ECMp.plot = true;
317       }
318       if (o1.checkCoordErrors()) {
319         IJ.error("There was an error in tracking due to a bug (frame " + (f) + ")"
320                 + "\nPlease try again");
321         break;
322       }
323 
324       if (!ECMp.ANA) {
325         IJ.showStatus("Running ECMM");
326         IJ.showProgress(f, oh.getEndFrame());
327         IJ.log("Mapping " + f + " to " + (f + 1));
328       }
329 
330       o2 = oh.getStoredOutline(f + 1);
331       // o2 left as seen in the segmentation - i.e. marker res unchanged
332       if (!ECMp.ANA && ECMp.markerRes > 0) {
333         o2.setResolution(ECMp.markerRes); // must be done b4 intersects are calculated
334       }
335       o2.resetAllCoords();
336       o2.clearFluores();
337 
338       if (!ECMp.ANA) {
339         this.nudgeOverlaps(o1, o2); // ensure no points/edges lie directly on each other (to 1e-4)/
340       }
341 
342       if (ECMp.plot) {
343         plot.setDrawingFrame(f);
344         o1.calcCentroid(); // calc it again as it is broken in loop by migrate() where Outline is
345         // initialzied from one vertex
346         plot.centre = o1.getCentroid();
347 
348         if (ECMp.drawInitialOutlines) {
349           plot.setColor(0d, 0d, 1d);
350           plot.drawOutline(o1);
351           plot.setColor(0d, 1d, 0d);
352           plot.drawOutline(o2);
353           plot.setSlice(f);
354         }
355       }
356 
357       // OutlineHandler.writeSingle("o2.snQP", o2);
358       // OutlineHandler.writeSingle("o1.snQP", o1);
359 
360       map1 = new Mapping(o1, o2);
361 
362       /*
363        * if (map1.invalid) { //Use no sectors IJ.log(" invalid outline intersection,
364        * attempting non-intersect mapping..."); plot.writeText("Non-intersect mapping");
365        * ECMp.noSectors = true; map1 = new Mapping(o1, o2); ECMp.noSectors = false; }
366        */
367 
368       o1 = map1.migrate();
369       // System.out.println("num nodes: "+o1.getVerts());
370 
371       if (!ECMp.ANA) {
372         // System.out.println("\n check final intersects");
373         if (!ECMp.disableDensityCorrections) {
374           if (o1.removeNanoEdges()) {
375             // IJ.log(" result had some v.small edges- removed");
376           }
377           if (o1.cutSelfIntersects()) {
378             IJ.log("    result self intersected - fixed");
379             if (ECMp.plot) {
380               plot.writeText("Fixed self intersection");
381             }
382           }
383 
384           if (ECMp.markerRes == 0) {
385             o1.correctDensity(2 * 1.6, 2 / 1.6);
386           } else {
387             o1.correctDensity(ECMp.markerRes * 1.6, ECMp.markerRes / 1.6);
388           }
389         }
390         if (ECMp.plot && ECMp.drawSolutionOutlines) {
391           plot.setColor(1d, 0d, 0d);
392           plot.drawOutline(o1);
393         }
394       }
395 
396       if (ECMp.ANA && ECMp.plot) {
397         plot.setColor(0d, 0.7d, 0.7d);
398         plot.drawOutline(o1);
399       }
400 
401       // OutlineHandler.writeSingle("o2.snQP", o2);
402       // OutlineHandler.writeSingle("o1.snQP", o1);
403 
404       o1.coordReset(); // reset the frame Coordinate system
405 
406       outputH.save(o1, f + 1);
407       if (f == stopAt) {
408         break;
409       }
410     }
411 
412     // IJ.log("Total iterations = " + ECMp.its);
413     if (ECMp.plot) {
414       plot.repaint();
415     }
416 
417     if (!ECMp.ANA) {
418       double timeSec = (System.currentTimeMillis() - time) / 1000d;
419       IJ.showStatus("ECMM finished");
420       IJ.log("ECMM finished in " + timeSec + " seconds.");
421     }
422     return;
423   }
424 
425   private void nudgeOverlaps(Outlineref="../../../../../../com/github/celldynamics/quimp/Outline.html#Outline">Outline o1, Outline o2) {
426 
427     int state;
428     double[] intersect = new double[2];
429     Random rg = new Random();
430 
431     Vert na = o1.getHead();
432     Vert nb;
433     do {
434       nb = o2.getHead();
435       do {
436         // check if points on top of each other
437         if (nb.getX() == na.getX() && nb.getY() == na.getY()) {
438           // use a minimum nudge of 0.01 (imageJ pixel accurracy
439           na.setX(na.getX() + (rg.nextDouble() * 0.5) + 0.01);
440           na.setY(na.getY() + (rg.nextDouble() * 0.5) + 0.01);
441         }
442 
443         // check if lines are parallel
444         state = ExtendedVector2d.segmentIntersection(na.getX(), na.getY(), na.getNext().getX(),
445                 na.getNext().getY(), nb.getX(), nb.getY(), nb.getNext().getX(), nb.getNext().getY(),
446                 intersect);
447         if (state == -1 || state == -2) {
448           // IJ.log(" outline parrallel -fixed");
449           na.setX(na.getX() + (rg.nextDouble() * 0.5) + 0.01);
450           na.setY(na.getY() + (rg.nextDouble() * 0.5) + 0.01);
451         }
452 
453         nb = nb.getNext();
454       } while (!nb.isHead());
455       na = na.getNext();
456 
457     } while (!na.isHead());
458 
459   }
460 
461   /*
462    * (non-Javadoc)
463    * 
464    * @see com.github.celldynamics.quimp.plugin.IQuimpPlugin#about()
465    */
466   @Override
467   public String about() {
468     return "ECMM plugin.\n" + "Authors: Piotr Baniukiewicz\n"
469             + "mail: p.baniukiewicz@warwick.ac.uk\n" + "Richard Tyson";
470   }
471 
472   /*
473    * (non-Javadoc)
474    * 
475    * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#runFromQconf()
476    */
477   @Override
478   protected void runFromQconf() throws QuimpException {
479     LOGGER.debug("Processing from new file format");
480     Nest nest = ((QParamsQconf) qconfLoader.getQp()).getNest();
481     outputOutlineHandlers = new OutlinesCollection(nest.size());
482     for (int i = 0; i < nest.size(); i++) { // go over all snakes
483       // For compatibility, all methods have the same syntax (assumes that there is only one
484       // handler)
485       ((QParamsQconf) qconfLoader.getQp()).setActiveHandler(i); // set current handler number.
486       SnakeHandler sh = nest.getHandler(i);
487       if (sh == null) {
488         continue;
489       }
490       oh = new OutlineHandler(sh); // convert to outline, oh is global var
491       ECMp.setup(qconfLoader.getQp());
492       ECMp.setParams(oh.maxLength); // base params on outline in middle of
493       // sequence
494       if (ECMp.plot) {
495         plot = new ECMplot(oh.getSize() - 1);
496         plot.imPlus.setTitle(WindowManager.makeUniqueName(ECMplot.ECMM_TITLE + "_" + "cell_" + i));
497       }
498       runPlugin(); // fills outputH
499       outputOutlineHandlers.oHs.add(i, new OutlineHandler(outputH)); // store actual result
500     }
501 
502     DataContainer dc = ((QParamsQconf) qconfLoader.getQp()).getLoadedDataContainer();
503     dc.ECMMState = outputOutlineHandlers; // assign ECMM container to global output
504     try {
505       qconfLoader.getQp().writeParams();
506     } catch (IOException e) {
507       throw new QuimpPluginException(e);
508     } // save global container
509     // generate additional OLD files, disabled #263, enabled GH228
510     if (QuimP.newFileFormat.get() == false) {
511       FormatConverterm/converter/FormatConverter.html#FormatConverter">FormatConverter foramtConvrter = new FormatConverter(qconfLoader);
512       foramtConvrter.doConversion();
513     }
514 
515   }
516 
517   /*
518    * (non-Javadoc)
519    * 
520    * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#runFromPaqp()
521    */
522   @Override
523   protected void runFromPaqp() throws QuimpException {
524     oh = new OutlineHandler(qconfLoader.getQp());
525     if (!oh.readSuccess) {
526       throw new QuimpException("Could not read OutlineHandler");
527     }
528 
529     ECMp.setup(qconfLoader.getQp());
530     // System.out.println("sf " + ECMp.startFrame + ", ef " +
531     // ECMp.endFrame);
532     // System.out.println("outfile " + ECMp.OUTFILE.getAbsolutePath());
533     ECMp.setParams(oh.maxLength); // base params on outline in middle of sequence
534     if (ECMp.plot) {
535       plot = new ECMplot(oh.getSize() - 1);
536     }
537     runPlugin();
538 
539     if (ECMp.saveTemp) {
540       // ------ save a temporary version instead as to not over write the
541       // old version
542       File tempFile = new File(ECMp.OUTFILE.getAbsolutePath() + ".temp.txt");
543       outputH.writeOutlines(tempFile, true);
544       IJ.log("ECMM:137, saving to a temp file instead");
545     } else {
546       ECMp.INFILE.delete();
547       outputH.writeOutlines(ECMp.OUTFILE, true);
548     }
549 
550   }
551 
552   /*
553    * (non-Javadoc)
554    * 
555    * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#validate()
556    */
557   @Override
558   protected void validate() throws QuimpException {
559     super.validate();
560     boolean fail = false;
561     String ver = IJ.getVersion();
562     if (ver.indexOf("/") > -1) {
563       ver = ver.substring(ver.indexOf("/") + 1);
564     }
565     LOGGER.info(ver);
566     // assume letter code at the end
567     try {
568       if (Character.isLetter(ver.charAt(ver.length() - 1))) {
569         String vern = ver.substring(0, ver.length() - 1); // should be 1.52 only
570         if (Double.parseDouble(vern) < 1.52) {
571           fail = true;
572         } else {
573           if (ver.charAt(ver.length() - 1) < 'n') {
574             fail = true;
575           }
576         }
577       } else {
578         if (Double.parseDouble(ver) < 1.52) {
579           fail = true;
580         }
581       }
582       if (fail) {
583         throw new QuimpException("IJ 1.52n version required (or above)");
584       }
585     } catch (NullPointerException | NumberFormatException e) {
586       LOGGER.warn("Cannot process IJ version.");
587     }
588   }
589 
590   /*
591    * (non-Javadoc)
592    * 
593    * @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#showUi(boolean)
594    */
595   @Override
596   public void showUi(boolean val) throws Exception {
597     // no options given, no UI but load file and execute plugin
598     executer();
599 
600     // in case user loaded the file
601     if (qconfLoader != null && qconfLoader.getQp() != null) {
602       options.paramFile = qconfLoader.getQp().getParamFile().getAbsolutePath();
603     }
604   }
605 }