View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.FileReader;
6   import java.io.FileWriter;
7   import java.io.IOException;
8   import java.io.PrintWriter;
9   import java.nio.file.Path;
10  import java.nio.file.Paths;
11  import java.util.ArrayList;
12  import java.util.Random;
13  
14  import org.slf4j.Logger;
15  import org.slf4j.LoggerFactory;
16  
17  import com.github.celldynamics.quimp.BOAState.BOAp;
18  import com.github.celldynamics.quimp.filesystem.FileExtensions;
19  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
20  
21  import ij.IJ;
22  
23  /**
24   * Container class for parameters defining the whole process of analysis in QuimP. Stores parameters
25   * read from configuration files and provide them to different modules. Supports writing and reading
26   * segmentation parameters from files (paQP). This class defines file format used for storing
27   * parameters in file. Object of this class is used for creating local configuration objects for
28   * ECMM and QAnalysis modules. Process only main paQP file. QuimP uses several files to store
29   * segmentation results and algorithm parameters:
30   * <ul>
31   * <li>.paQP - core file, contains reference to images and parameters of algorithm. This file is
32   * saved and processed by QParams class</li>
33   * <li>.snQP - contains positions of all nodes for every frame</li>
34   * <li>.stQP - basic shape statistics for every frame</li>
35   * <li>.mapQP - maps described in documentation</li>
36   * </ul>
37   * 
38   * <p>This class exists for compatibility purposes. Allows reading old files. There is also child
39   * class
40   * QParamsEsxhanger that is based on new file format. Because QParams is strongly integrated with
41   * QuimP it has been left.
42   * 
43   * @author rtyson
44   * @see BOAp
45   * 
46   */
47  public class QParams {
48  
49    /**
50     * The Constant LOGGER.
51     */
52    static final Logger LOGGER = LoggerFactory.getLogger(QParams.class.getName());
53  
54    /**
55     * The Constant OLD_QUIMP. Denotes version of QuimP < Q11
56     */
57    public static final int OLD_QUIMP = 1;
58  
59    /**
60     * The Constant QUIMP_11. Denotes version Q11
61     */
62    public static final int QUIMP_11 = 2;
63  
64    /**
65     * The Constant NEW_QUIMP. Denotes version > Q11
66     */
67    public static final int NEW_QUIMP = 3;
68    /**
69     * Name of the case. Used to set <i>fileName</i> and <i>path</i>
70     */
71    private File paramFile;
72    /**
73     * Indicates format of data file.
74     */
75    protected int paramFormat;
76    /**
77     * Name of the data file - without path and extension. Equals to name of the case. If
78     * initialised from {@link QParamsQconf} contains underscored cell number as well.
79     * 
80     * @see com.github.celldynamics.quimp.BOAState.BOAp
81     */
82    private String fileName;
83    /**
84     * Path where user files exist.
85     * 
86     * @see com.github.celldynamics.quimp.BOAState.BOAp
87     */
88    private String path;
89    private File segImageFile;
90    private File snakeQP;
91  
92    /**
93     * The stats QP.
94     */
95    protected File statsQP;
96    /**
97     * This field is set by direct call from ANA. Left here for compatibility reasons. Main holder
98     * of fluTiffs is {@link com.github.celldynamics.quimp.plugin.ana.ANAp}
99     */
100   public File[] fluTiffs;
101 
102   private File convexFile;
103   private File coordFile;
104   private File motilityFile;
105   private File originFile;
106   private File xmapFile;
107   private File ymapFile;
108   private File[] fluFiles;
109 
110   private double imageScale;
111   private double frameInterval;
112   private int startFrame;
113   private int endFrame;
114 
115   private int blowup;
116   private double nodeRes;
117 
118   int nmax;
119   int maxIterations;
120   int sampleTan;
121   int sampleNorm;
122 
123   double deltaT;
124   double velCrit;
125   double centralForce;
126   double contractForce;
127   double imageForce;
128   double frictionForce;
129 
130   /**
131    * Shrink value.
132    */
133   public double finalShrink;
134   /**
135    * The cortex width.
136    */
137   public double cortexWidth;
138 
139   /**
140    * The key.
141    */
142   long key;
143 
144   /**
145    * The sensitivity.
146    */
147   double sensitivity; // no longer used. blank holder
148   /**
149    * Indicate if <i>snQP</i> has been processed by ECMM (<tt>true</tt>). Set by checkECMMrun.
150    */
151   private boolean ecmmHasRun = false;
152 
153   /**
154    * Check if ECMM was run.
155    * 
156    * @return the ecmmHasRun
157    */
158   public boolean isEcmmHasRun() {
159     return ecmmHasRun;
160   }
161 
162   /**
163    * Instantiates a new q params.
164    */
165   public QParams() {
166 
167   }
168 
169   /**
170    * Read basic information from <i>paQP</i> file such as its name and path. Initialise structures
171    * 
172    * @param p <i>paQP</i> file, should contain underscored cell number and absolute path.
173    */
174   public QParams(File p) {
175     setParamFile(p);
176 
177     paramFormat = QParams.QUIMP_11;
178 
179     segImageFile = new File("/");
180     snakeQP = new File("/");
181     statsQP = new File("/");
182 
183     fluTiffs = new File[3];
184     fluTiffs[0] = new File("/");
185     fluTiffs[1] = new File("/");
186     fluTiffs[2] = new File("/");
187 
188     imageScale = -1;
189     frameInterval = -1;
190     startFrame = -1;
191     endFrame = -1;
192     nmax = -1;
193     blowup = -1;
194     maxIterations = -1;
195     sampleTan = -1;
196     sampleNorm = -1;
197     deltaT = -1;
198     nodeRes = -1;
199     velCrit = -1;
200     centralForce = -1;
201     contractForce = -1;
202     imageForce = -1;
203     frictionForce = -1;
204     finalShrink = -1;
205     cortexWidth = 0.7;
206     key = -1;
207     sensitivity = -1;
208   }
209 
210   /**
211    * Get name of parameter file with extension (paQP).
212    * 
213    * @return the paramFile
214    * @see #getPath()
215    */
216   public File getParamFile() {
217     return paramFile;
218   }
219 
220   /**
221    * Set name of parameter file.
222    * 
223    * @param paramFile the paramFile to set. Extension will be removed.
224    */
225   public void setParamFile(File paramFile) {
226     fileName = QuimpToolsCollection.removeExtension(paramFile.getName());
227     this.paramFile = paramFile;
228     path = paramFile.getParent();
229     // path is used for producing other file names in directory where paramFile. This is in case
230     // if paramFile does not contain path (should not happen)
231     if (path == null) {
232       throw new IllegalArgumentException("Input file: " + paramFile
233               + " must contain path as other files will be created in its folder");
234     }
235   }
236 
237   /**
238    * Get the name of parameter file but without extension.
239    * 
240    * @return the prefix. This name probably contains also underscored cell number. It is added
241    *         when object of this is created from {@link QParamsQconf} and should be added when
242    *         creating only this object QParams(File).
243    */
244   public String getFileName() {
245     return fileName;
246   }
247 
248   /**
249    * getPath.
250    * 
251    * @return the path of param file (no file name)
252    */
253   public String getPath() {
254     return path;
255   }
256 
257   /**
258    * getPathasPath.
259    * 
260    * @return the path of param file as Path (no file name)
261    * @see #getParamFile()
262    */
263   public Path getPathasPath() {
264     return Paths.get(this.getPath());
265   }
266 
267   /**
268    * getNodeRes.
269    * 
270    * @return the nodeRes
271    */
272   public double getNodeRes() {
273     return nodeRes;
274   }
275 
276   /**
277    * setNodeRes.
278    * 
279    * @param nodeRes the nodeRes to set
280    */
281   public void setNodeRes(double nodeRes) {
282     this.nodeRes = nodeRes;
283   }
284 
285   /**
286    * getBlowup.
287    * 
288    * @return the blowup
289    */
290   public int getBlowup() {
291     return blowup;
292   }
293 
294   /**
295    * setBlowup.
296    * 
297    * @param blowup the blowup to set
298    */
299   public void setBlowup(int blowup) {
300     this.blowup = blowup;
301   }
302 
303   /**
304    * getImageScale.
305    * 
306    * @return the imageScale
307    */
308   public double getImageScale() {
309     return imageScale;
310   }
311 
312   /**
313    * setImageScale.
314    * 
315    * @param imageScale the imageScale to set
316    */
317   public void setImageScale(double imageScale) {
318     this.imageScale = imageScale;
319   }
320 
321   /**
322    * getFrameInterval.
323    * 
324    * @return the frameInterval
325    */
326   public double getFrameInterval() {
327     return frameInterval;
328   }
329 
330   /**
331    * setFrameInterval.
332    * 
333    * @param frameInterval the frameInterval to set
334    */
335   public void setFrameInterval(double frameInterval) {
336     this.frameInterval = frameInterval;
337   }
338 
339   /**
340    * getStartFrame.
341    * 
342    * @return the startFrame
343    */
344   public int getStartFrame() {
345     return startFrame;
346   }
347 
348   /**
349    * setStartFrame.
350    * 
351    * @param startFrame the startFrame to set
352    */
353   public void setStartFrame(int startFrame) {
354     this.startFrame = startFrame;
355   }
356 
357   /**
358    * getEndFrame.
359    * 
360    * @return the endFrame
361    */
362   public int getEndFrame() {
363     return endFrame;
364   }
365 
366   /**
367    * setEndFrame.
368    * 
369    * @param endFrame the endFrame to set
370    */
371   public void setEndFrame(int endFrame) {
372     this.endFrame = endFrame;
373   }
374 
375   /**
376    * Get snQP file name.
377    * 
378    * @return the snakeQP
379    */
380   public File getSnakeQP() {
381     return snakeQP;
382   }
383 
384   /**
385    * Set snQP file name.
386    * 
387    * @param snakeQP the snakeQP to set
388    */
389   public void setSnakeQP(File snakeQP) {
390     this.snakeQP = snakeQP;
391   }
392 
393   /**
394    * Get stats file name.
395    * 
396    * @return the statsQP
397    */
398   public File getStatsQP() {
399     return statsQP;
400   }
401 
402   /**
403    * Get names of flu files.
404    * 
405    * @return the fluFiles
406    */
407   public File[] getFluFiles() {
408     return fluFiles;
409   }
410 
411   /**
412    * Set stats file name.
413    * 
414    * @param statsQP the statsQP to set
415    */
416   public void setStatsQP(File statsQP) {
417     this.statsQP = statsQP;
418   }
419 
420   /**
421    * Get name of BOA loaded image.
422    * 
423    * @return the segImageFile
424    */
425   public File getSegImageFile() {
426     return segImageFile;
427   }
428 
429   /**
430    * Set name of BOA loaded image.
431    * 
432    * @param segImageFile the segImageFile to set
433    */
434   public void setSegImageFile(File segImageFile) {
435     this.segImageFile = segImageFile;
436   }
437 
438   /**
439    * getConvexFile name.
440    * 
441    * @return the convexFile
442    */
443   public File getConvexFile() {
444     return convexFile;
445   }
446 
447   /**
448    * setConvexFile name.
449    * 
450    * @param convexFile the convexFile to set
451    */
452   public void setConvexFile(File convexFile) {
453     this.convexFile = convexFile;
454   }
455 
456   /**
457    * getCoordFile name.
458    * 
459    * @return the coordFile
460    */
461   public File getCoordFile() {
462     return coordFile;
463   }
464 
465   /**
466    * setCoordFile name.
467    * 
468    * @param coordFile the coordFile to set
469    */
470   public void setCoordFile(File coordFile) {
471     this.coordFile = coordFile;
472   }
473 
474   /**
475    * getMotilityFile name.
476    * 
477    * @return the motilityFile
478    */
479   public File getMotilityFile() {
480     return motilityFile;
481   }
482 
483   /**
484    * setMotilityFile name.
485    * 
486    * @param motilityFile the motilityFile to set
487    */
488   public void setMotilityFile(File motilityFile) {
489     this.motilityFile = motilityFile;
490   }
491 
492   /**
493    * getOriginFile name.
494    * 
495    * @return the originFile
496    */
497   public File getOriginFile() {
498     return originFile;
499   }
500 
501   /**
502    * setOriginFile name.
503    * 
504    * @param originFile the originFile to set
505    */
506   public void setOriginFile(File originFile) {
507     this.originFile = originFile;
508   }
509 
510   /**
511    * Get x coord map file.
512    * 
513    * @return the xmapFile
514    */
515   public File getxmapFile() {
516     return xmapFile;
517   }
518 
519   /**
520    * Set x coord map file.
521    * 
522    * @param xmapFile the xmapFile to set
523    */
524   public void setxmapFile(File xmapFile) {
525     this.xmapFile = xmapFile;
526   }
527 
528   /**
529    * Get y coord map file.
530    * 
531    * @return the ymapFile
532    */
533   public File getymapFile() {
534     return ymapFile;
535   }
536 
537   /**
538    * Set y coord map file.
539    * 
540    * @param ymapFile the ymapFile to set
541    */
542   public void setymapFile(File ymapFile) {
543     this.ymapFile = ymapFile;
544   }
545 
546   /**
547    * Return loaded format.
548    * 
549    * @return the paramFormat {@value #OLD_QUIMP}, {@value #NEW_QUIMP}, {@link #QUIMP_11}
550    */
551   public int getParamFormat() {
552     return paramFormat;
553   }
554 
555   /**
556    * Read the paQP file specified by paramFile
557    * 
558    * <p>See {@link #QParams(File)}. Create handles to files stored as names in <i>paQP</i>. Read
559    * segmentation parameters.
560    * 
561    * @throws QuimpException on wrong file that can not be interpreted or read.
562    * @throws IllegalStateException if input file is empty
563    */
564   public void readParams() throws QuimpException {
565     paramFormat = QParams.OLD_QUIMP;
566     BufferedReader d = null;
567     try {
568       d = new BufferedReader(new FileReader(paramFile));
569 
570       String l = d.readLine();
571       if (l == null) {
572         throw new IllegalStateException(
573                 "File " + paramFile.getAbsolutePath() + " seems to be empty");
574       }
575       if (!(l.length() < 2)) {
576         String fileID = l.substring(0, 2);
577         if (!fileID.equals("#p")) {
578           throw new QuimpException("Not compatible paramater file");
579         }
580       } else {
581         throw new QuimpException("Not compatible paramater file");
582       }
583       key = (long) QuimpToolsCollection.s2d(d.readLine()); // key
584       segImageFile = new File(d.readLine()); // image file name
585 
586       String sn = d.readLine();
587       // fileName = sn;
588       if (!l.substring(0, 3).equals("#p2")) { // old format, fix file names
589         sn = sn.substring(1); // strip the dot off snQP file name
590         // fileName = fileName.substring(1); // strip the dot off file
591         // name
592         int lastDot = sn.lastIndexOf(".");
593 
594         String tempS = sn.substring(0, lastDot);
595         statsQP = new File(paramFile.getParent() + tempS + FileExtensions.statsFileExt);
596       }
597       snakeQP = new File(paramFile.getParent() + "" + sn); // snQP file
598       LOGGER.debug("snake file: " + snakeQP.getAbsolutePath());
599 
600       d.readLine(); // # blank line
601       imageScale = QuimpToolsCollection.s2d(d.readLine());
602       if (imageScale == 0) {
603         IJ.log("Warning. Image scale was zero. Set to 1");
604         imageScale = 1;
605       }
606       frameInterval = QuimpToolsCollection.s2d(d.readLine());
607 
608       d.readLine(); // skip #segmentation parameters
609       nmax = (int) QuimpToolsCollection.s2d(d.readLine());
610       deltaT = QuimpToolsCollection.s2d(d.readLine());
611       maxIterations = (int) QuimpToolsCollection.s2d(d.readLine());
612       nodeRes = QuimpToolsCollection.s2d(d.readLine());
613       blowup = (int) QuimpToolsCollection.s2d(d.readLine());
614       sampleTan = (int) QuimpToolsCollection.s2d(d.readLine());
615       sampleNorm = (int) QuimpToolsCollection.s2d(d.readLine());
616       velCrit = QuimpToolsCollection.s2d(d.readLine());
617 
618       centralForce = QuimpToolsCollection.s2d(d.readLine());
619       contractForce = QuimpToolsCollection.s2d(d.readLine());
620       frictionForce = QuimpToolsCollection.s2d(d.readLine());
621       imageForce = QuimpToolsCollection.s2d(d.readLine());
622       sensitivity = QuimpToolsCollection.s2d(d.readLine());
623 
624       if (l.substring(0, 3).equals("#p2")) { // new format
625         paramFormat = QParams.QUIMP_11;
626         // new params
627         // # - new parameters (cortext width, start frame, end frame, final shrink, statsQP,
628         // fluImage)
629         d.readLine();
630         cortexWidth = QuimpToolsCollection.s2d(d.readLine());
631         startFrame = (int) QuimpToolsCollection.s2d(d.readLine());
632         endFrame = (int) QuimpToolsCollection.s2d(d.readLine());
633         finalShrink = QuimpToolsCollection.s2d(d.readLine());
634         statsQP = new File(paramFile.getParent() + "" + d.readLine());
635 
636         d.readLine(); // # fluo channel tiffs
637         fluTiffs[0] = new File(d.readLine());
638         fluTiffs[1] = new File(d.readLine());
639         fluTiffs[2] = new File(d.readLine());
640       }
641       this.guessOtherFileNames(); // generate handles of other files that will be created here
642       // check if snQP file is already processed by ECMM. Set ecmmHasRun
643       ecmmHasRun = verifyEcmminpsnQP();
644       if (ecmmHasRun) {
645         LOGGER.info("ECMM has been run on this paFile data");
646       }
647     } catch (IOException e) {
648       throw new QuimpException(e);
649     } finally {
650       try {
651         if (d != null) {
652           d.close();
653         }
654       } catch (IOException e) {
655         LOGGER.error("Can not close file " + e);
656       }
657     }
658   }
659 
660   /**
661    * Write params.
662    *
663    * @throws IOException Signals that an I/O exception has occurred.
664    */
665   public void writeParams() throws IOException {
666     LOGGER.debug("Write " + FileExtensions.configFileExt + " at: " + paramFile);
667     if (paramFile.exists()) {
668       paramFile.delete();
669     }
670 
671     Random generator = new Random();
672     double d = generator.nextDouble() * 1000000; // 6 digit key to ID job
673     key = Math.round(d);
674 
675     PrintWriter ppW = new PrintWriter(new FileWriter(paramFile), true);
676 
677     ppW.print("#p2 - QuimP parameter file (QuimP11). Created " + QuimpToolsCollection.dateAsString()
678             + "\n");
679     ppW.print(IJ.d2s(key, 0) + "\n");
680     ppW.print(segImageFile.getAbsolutePath() + "\n");
681     ppW.print(File.separator + snakeQP.getName() + "\n");
682     // pPW.print(outFile.getAbsolutePath() + "\n");
683 
684     ppW.print("#Image calibration (scale, frame interval)\n");
685     ppW.print(IJ.d2s(imageScale, 6) + "\n");
686     ppW.print(IJ.d2s(frameInterval, 3) + "\n");
687 
688     // according to BOAState and /trac/QuimP/wiki/QuimpQp
689     ppW.print("#segmentation parameters (" + "Maximum number of nodes, " + "ND, "
690             + "Max iterations, " + "Node spacing, " + "Blowup, " + "Sample tan, " + "Sample norm, "
691             + "Crit velocity, " + "Central F, " + "Contract F, " + "ND, " + "Image force, "
692             + "ND)\n");
693     ppW.print(IJ.d2s(nmax, 0) + "\n");
694     ppW.print(IJ.d2s(deltaT, 6) + "\n");
695     ppW.print(IJ.d2s(maxIterations, 6) + "\n");
696     ppW.print(IJ.d2s(nodeRes, 6) + "\n");
697     ppW.print(IJ.d2s(blowup, 6) + "\n");
698     ppW.print(IJ.d2s(sampleTan, 0) + "\n");
699     ppW.print(IJ.d2s(sampleNorm, 0) + "\n");
700     ppW.print(IJ.d2s(velCrit, 6) + "\n");
701     ppW.print(IJ.d2s(centralForce, 6) + "\n");
702     ppW.print(IJ.d2s(contractForce, 6) + "\n");
703     ppW.print(IJ.d2s(frictionForce, 6) + "\n");
704     ppW.print(IJ.d2s(imageForce, 6) + "\n");
705     ppW.print(IJ.d2s(sensitivity, 6) + "\n");
706 
707     ppW.print("# - new parameters (cortex width, start frame, end frame,"
708             + " final shrink, statsQP, fluImage)\n");
709     ppW.print(IJ.d2s(cortexWidth, 2) + "\n");
710     ppW.print(IJ.d2s(startFrame, 0) + "\n");
711     ppW.print(IJ.d2s(endFrame, 0) + "\n");
712     ppW.print(IJ.d2s(finalShrink, 2) + "\n");
713     ppW.print(File.separator + statsQP.getName() + "\n");
714 
715     ppW.print("# - Fluorescence channel tiff's\n");
716     ppW.print(fluTiffs[0].getAbsolutePath() + "\n");
717     ppW.print(fluTiffs[1].getAbsolutePath() + "\n");
718     ppW.print(fluTiffs[2].getAbsolutePath() + "\n");
719 
720     ppW.print("#END");
721 
722     ppW.close();
723   }
724 
725   /**
726    * Traverse through current directory looking for <i>paQP</i> files.
727    * 
728    * <p><b>Remarks</b>
729    * 
730    * <p>Current <i>paQP</i> file (that passed to QParams(File)) is not counted.
731    * 
732    * @return Array of file handlers or empty array if there is no <i>paQP</i> files (except
733    *         paramFile)
734    */
735   public File[] findParamFiles() {
736     File[] otherPaFiles;
737     File directory = new File(path);
738     ArrayList<String> paFiles = new ArrayList<String>();
739 
740     if (directory.isDirectory()) {
741       String[] filenames = directory.list();
742       if (filenames == null) {
743         otherPaFiles = new File[0];
744         return otherPaFiles;
745       }
746       String extension;
747 
748       for (int i = 0; i < filenames.length; i++) {
749         if (filenames[i].matches("\\.") || filenames[i].matches("\\.\\.")
750                 || filenames[i].matches(paramFile.getName())) {
751           continue;
752         }
753         extension = QuimpToolsCollection.getFileExtension(filenames[i]);
754         if (extension.matches(FileExtensions.configFileExt.substring(1))) {
755           paFiles.add(filenames[i]);
756           LOGGER.info("paFile: " + filenames[i]);
757         }
758       }
759     }
760     if (paFiles.isEmpty()) {
761       otherPaFiles = new File[0];
762       return otherPaFiles;
763     } else {
764       otherPaFiles = new File[paFiles.size()];
765       for (int j = 0; j < otherPaFiles.length; j++) {
766         otherPaFiles[j] =
767                 new File(directory.getAbsolutePath() + File.separator + (String) paFiles.get(j));
768       }
769       return otherPaFiles;
770     }
771   }
772 
773   /**
774    * Generate names and handles of files associated with paQP that will be created in result of
775    * analysis.
776    */
777   protected void guessOtherFileNames() {
778     LOGGER.debug("prefix: " + fileName);
779 
780     convexFile = new File(path + File.separator + fileName + FileExtensions.convmapFileExt);
781 
782     coordFile = new File(path + File.separator + fileName + FileExtensions.coordmapFileExt);
783     motilityFile = new File(path + File.separator + fileName + FileExtensions.motmapFileExt);
784     originFile = new File(path + File.separator + fileName + FileExtensions.originmapFileExt);
785     xmapFile = new File(path + File.separator + fileName + FileExtensions.xmapFileExt);
786     ymapFile = new File(path + File.separator + fileName + FileExtensions.ymapFileExt);
787 
788     fluFiles = new File[3];
789     fluFiles[0] = new File(
790             path + File.separator + fileName + FileExtensions.fluomapFileExt.replace('%', '1'));
791     fluFiles[1] = new File(
792             path + File.separator + fileName + FileExtensions.fluomapFileExt.replace('%', '2'));
793     fluFiles[2] = new File(
794             path + File.separator + fileName + FileExtensions.fluomapFileExt.replace('%', '3'));
795 
796   }
797 
798   /**
799    * Verify if ECMM was run on snQP file.
800    * 
801    * <p>snQP file contains ECMM string in first line after ECMM.
802    * 
803    * @return true if ECMM was run
804    * @throws IOException on error
805    */
806   public boolean verifyEcmminpsnQP() throws IOException {
807     BufferedReader br = new BufferedReader(new FileReader(snakeQP));
808     String line = br.readLine();
809     if (line == null) {
810       br.close();
811       throw new IllegalStateException("File " + snakeQP + " seems to be empty");
812     }
813     br.close();
814     return line.contains("-ECMM");
815   }
816 
817 }