Q_Analysis.java

package com.github.celldynamics.quimp.plugin.qanalysis;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.celldynamics.quimp.OutlineHandler;
import com.github.celldynamics.quimp.QColor;
import com.github.celldynamics.quimp.QParams;
import com.github.celldynamics.quimp.QParamsQconf;
import com.github.celldynamics.quimp.QuimP;
import com.github.celldynamics.quimp.QuimpException;
import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
import com.github.celldynamics.quimp.filesystem.FileExtensions;
import com.github.celldynamics.quimp.filesystem.QconfLoader;
import com.github.celldynamics.quimp.filesystem.converter.FormatConverter;
import com.github.celldynamics.quimp.plugin.AbstractPluginQconf;
import com.github.celldynamics.quimp.plugin.QuimpPluginException;
import com.github.celldynamics.quimp.plugin.ecmm.ECMM_Mapping;
import com.github.celldynamics.quimp.utils.graphics.svg.SVGplotter;

import ij.IJ;
import ij.gui.GenericDialog;
import ij.gui.YesNoCancelDialog;

/**
 * Run Q analysis for ECMM data.
 * 
 * <p>In principle this object loads and process name_X.paQP file generated by ECMM. <i>X</i> in
 * this
 * case means number of cell outline. The same result can be achieved by loading QCONF file that
 * contains all outlines for given case. This class is designed to process one outline in one time.
 * Thus most of methods operate on current status private fields (such as <tt>qp</tt>, <tt>oh</tt>).
 * The main analysis runner method is {@link #runPlugin()}, whereas support for both input formats
 * is
 * covered by {@link #runFromPaqp()} and {@link #runFromQconf()} (similarly to {@link ECMM_Mapping
 * ECMM_Mapping})
 * 
 * @author rtyson
 * @author p.baniukiewicz
 */
public class Q_Analysis extends AbstractPluginQconf {

  private static String thisPluginName = "QuimP Analysis";

  /**
   * The Constant LOGGER.
   */
  static final Logger LOGGER = LoggerFactory.getLogger(Q_Analysis.class.getName());

  private File fileToLoad = null; // file to load paQP/QCONF

  /**
   * The gd.
   */
  GenericDialog gd;
  private OutlineHandler oh; // keep loaded handler, can change during run
  private STmap stMap; // object holding all maps evaluated for current OutlineHandler (oh)

  /**
   * Main constructor and runner - class entry point.
   * 
   * <p>Left in this form for backward compatibility
   */
  public Q_Analysis() {
    super(new Qp(), thisPluginName);
  }

  /**
   * Parameterised constructor for tests.
   * 
   * @param paramFile paQP or QCONF file to process. If <tt>null</tt> user is asked for this file
   * @throws QuimpException on error in {@link #loadFile(String)}
   * @see com.github.celldynamics.quimp.plugin.ecmm.ECMM_Mapping#ECMM_Mapping(File)
   */
  public Q_Analysis(File paramFile) throws QuimpException {
    super(new Qp(paramFile), thisPluginName);
    apiCall = true;
    loadFile(paramFile.toString());
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#loadFile(java.lang.String)
   */
  @Override
  protected void loadFile(String paramFile) throws QuimpException {
    // we need to use different handling for multiple paQP files, so use own loader

    if (paramFile == null || paramFile.isEmpty()) {
      fileToLoad = null;
    } else {
      fileToLoad = new File(paramFile);
    }
    qconfLoader = new QconfLoader(fileToLoad); // load file
    if (qconfLoader != null && qconfLoader.getQp() != null) {
      options.paramFile = qconfLoader.getQp().getParamFile().getAbsolutePath();
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#executer()
   */
  @Override
  public void executer() throws QuimpException {
    Qp opts = (Qp) options;
    if (apiCall == true) { // if run from other constructor, override sink (after run() set it)
      errorSink = MessageSinkTypes.CONSOLE;
    }
    if (qconfLoader == null || qconfLoader.getQp() == null) {
      // if we came here from Macro, file is not loaded (it is loaded before UI in showUI)
      // paramFile should contain something
      if (options.paramFile == null) {
        throw new QuimpException("Option \"paramFile\" not specified in macro string");
      }
      loadFile(options.paramFile);

    }
    if (qconfLoader.isFileLoaded() == QParams.QUIMP_11) { // old path
      QParams qp;
      runFromPaqp();
      File[] otherPaFiles = qconfLoader.getQp().findParamFiles();
      if (otherPaFiles.length > 0) { // and process them if they are
        YesNoCancelDialog yncd = new YesNoCancelDialog(IJ.getInstance(), "Batch Process?",
                "\tBatch Process?\n\n" + "Process other " + FileExtensions.configFileExt
                        + " files in the same folder with QAnalysis?"
                        + "\n[The same parameters will be used]");
        if (yncd.yesPressed()) {
          ArrayList<String> runOn = new ArrayList<String>(otherPaFiles.length);
          this.closeAllImages();

          // if user agreed iterate over found files
          // (except that loaded explicitly by user)
          for (int j = 0; j < otherPaFiles.length; j++) {
            IJ.log("Running on " + otherPaFiles[j].getAbsolutePath());
            qconfLoader = new QconfLoader(otherPaFiles[j]);
            qp = qconfLoader.getQp();
            opts.setup(qp);
            oh = new OutlineHandler(qp); // prepare current OutlineHandler
            if (!oh.readSuccess) {
              LOGGER.error("OutlineHandlers could not be read!");
              return;
            }
            runPlugin(); // run on current OutlineHandler
            runOn.add(otherPaFiles[j].getName());
            this.closeAllImages();
          }
          IJ.log("\n\nBatch - Successfully ran QAnalysis on:");
          for (int i = 0; i < runOn.size(); i++) {
            IJ.log(runOn.get(i));
          }
        } else {
          return; // no batch processing
        }
      }
    } else if (qconfLoader.isFileLoaded() == QParams.NEW_QUIMP) { // new path
      // verification for components run
      validate();
      if (qconfLoader.isQPresent() && apiCall == false && errorSink == MessageSinkTypes.GUI) {
        YesNoCancelDialog ync;
        ync = new YesNoCancelDialog(IJ.getInstance(), "Overwrite",
                "You are about to override previous Q results. Is it ok?");
        if (!ync.yesPressed()) { // if no or cancel
          IJ.log("No changes done in input file.");
          return; // end}
        }
      }
      runFromQconf();
      IJ.log("The new data file " + qconfLoader.getQp().getParamFile().toString()
              + " has been updated by results of Q Analysis.");
    } else {
      throw new IllegalStateException("QconfLoader returned unknown version of QuimP or error: "
              + qconfLoader.isFileLoaded());
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#run(java.lang.String)
   */
  @Override
  public void run(String arg) {
    super.run(arg);
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#validate()
   */
  @Override
  protected void validate() throws QuimpException {
    super.validate();
    qconfLoader.getEcmm();
    qconfLoader.getANA();
    qconfLoader.getStats();
  }

  /**
   * Main runner - do all calculations on current OutlineHandler object.
   * 
   * <p><b>Warning</b>
   * 
   * <p>Process current <tt>OutlineHandler oh</tt>; object and modify it filling some fields from
   * Vertex class
   * 
   * @throws QuimpException on error wit saving maps
   * 
   */
  private void runPlugin() throws QuimpException {
    Qp opts = (Qp) options;
    if (oh.getSize() == 1) {
      opts.singleImage = true;
      // only one frame - re lable node indices
      oh.getStoredOutline(1).resetAllCoords();
    }

    opts.convexityToPixels();

    stMap = new STmap(oh, opts.mapRes, opts);
    if (QuimP.newFileFormat.get() == false) {
      stMap.saveMaps(STmap.ALLMAPS); // save maps only for old path
    }

    SVGplotter svgPlotter = new SVGplotter(oh, opts.fps, opts.scale, opts.channel, opts.outFile);
    svgPlotter.plotTrack(opts.trackColor, opts.increment);
    // svgPlotter.plotTrackAnim();
    svgPlotter.plotTrackER(opts.outlinePlot);

    opts.convexityToUnits(); // reset the covexity options to units (as they are static)
  }

  private boolean showDialog() {
    Qp opts = (Qp) options;
    gd = new GenericDialog("Q Analysis Options", IJ.getInstance());

    gd.setOKLabel("RUN");

    gd.addMessage("Pixel width: " + opts.scale + " \u00B5m\nFrame Interval: " + opts.frameInterval
            + " sec");

    gd.addMessage("******* Cell track options (svg) *******");
    gd.addNumericField("Frame_increment", opts.increment, 0);
    gd.addChoice("Colour_Map", QColor.colourMaps, QColor.colourMaps[0]);

    gd.addMessage("***** Motility movie options (svg) *****");
    gd.addChoice("Colour_using", opts.outlinePlots, opts.outlinePlots[0]);

    gd.addMessage("********** Convexity options **********");
    gd.addNumericField("Sum_over (\u00B5m)", opts.sumCov, 2);
    gd.addNumericField("Smooth_over (\u00B5m)", opts.avgCov, 2);

    gd.addMessage("************* Map options *************");
    gd.addNumericField("Map_resolution", opts.mapRes, 0);

    // gd.addMessage("************* Head nodes **************");
    // gd.addChoice("Heads", headActions, headActions[0]);

    gd.setResizable(false);
    gd.showDialog();

    if (gd.wasCanceled()) {
      return false;
    }

    // Qp.scale = gd.getNextNumber();
    // Qp.setFPS(gd.getNextNumber());
    opts.increment = (int) gd.getNextNumber();
    opts.trackColor = gd.getNextChoice();
    opts.outlinePlot = gd.getNextChoice();
    opts.sumCov = gd.getNextNumber();
    opts.avgCov = gd.getNextNumber();
    opts.mapRes = (int) gd.getNextNumber();
    // Qp.headProcessing = gd.getNextChoice();

    return true;
  }

  private void closeAllImages() {
    int[] ids = ij.WindowManager.getIDList();
    for (int i = 0; i < ids.length; i++) {
      ij.WindowManager.getImage(ids[i]).close();
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.IQuimpPlugin#about()
   */
  @Override
  public String about() {
    return "Q analysis plugin.\n" + "Authors: Piotr Baniukiewicz\n"
            + "mail: p.baniukiewicz@warwick.ac.uk\n" + "Richard Tyson";
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#runFromQconf()
   */
  @Override
  protected void runFromQconf() throws QuimpException {
    // {@link #run()} updates also {@link DataContainer#ECMMState ECMMState} by modifying fields in
    // Outlines that are accessed by reference here.
    Qp opts = (Qp) options;
    int i = 0;
    QParamsQconf qp = (QParamsQconf) qconfLoader.getQp();
    Iterator<OutlineHandler> oi = qp.getLoadedDataContainer().getEcmmState().oHs.iterator();
    ArrayList<STmap> tmp = new ArrayList<>();
    while (oi.hasNext()) {
      qp.setActiveHandler(i++); // set current handler number.
      opts.setup(qconfLoader.getQp()); // copy selected data from general QParams to local storage
      oh = oi.next();
      runPlugin();
      tmp.add(new STmap(stMap)); // store generated map
    }
    qp.getLoadedDataContainer().QState = tmp.toArray(new STmap[0]);
    try {
      qp.writeParams();
    } catch (IOException e) {
      throw new QuimpPluginException(e);
    } // save global container
    // generate additional OLD files, disabled #263, enabled 228
    if (QuimP.newFileFormat.get() == false) {
      FormatConverter formatConverter = new FormatConverter(qconfLoader);
      formatConverter.doConversion();
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#runFromPaqp()
   */
  @Override
  protected void runFromPaqp() throws QuimpException {
    Qp opts = (Qp) options;
    opts.setup(qconfLoader.getQp()); // copy selected data from general QParams to local storage
    oh = new OutlineHandler(qconfLoader.getQp()); // load data from file
    if (!oh.readSuccess) {
      throw new QuimpException("Could not read OutlineHandler");
    }
    runPlugin();
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#showUi(boolean)
   */
  @Override
  public void showUi(boolean val) throws Exception {
    // load file but do not execute - we need some information before showing UI
    loadFile(options.paramFile);
    // in case user loaded the file
    if (qconfLoader == null || qconfLoader.getQp() == null) {
      return; // cancelled (errors are by exceptions)
    }
    // show dialog
    if (!showDialog()) {
      return;
    }
    // execute comptations
    executer();
  }
}