QParamsQconf.java

package com.github.celldynamics.quimp;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

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

import com.github.celldynamics.quimp.BOAState.BOAp;
import com.github.celldynamics.quimp.filesystem.DataContainer;
import com.github.celldynamics.quimp.filesystem.FileExtensions;
import com.github.celldynamics.quimp.filesystem.versions.Converter170202;
import com.github.celldynamics.quimp.utils.QuimpToolsCollection;

/**
 * This class override most of methods from super class QParams. The goal of this class is rather
 * not to extend QParams but to use polymorphism to provide requested data to callers keeping
 * compatibility with old QuimP architecture. The QuimP uses QParams to keep parameters read from
 * configuration files (<i>paQP</i>, <i>snQP</i>) and then to provide some of parameters stored in
 * these files to local configuration classes such as e.g.
 * {@link com.github.celldynamics.quimp.plugin.ecmm.ECMp},
 * {@link com.github.celldynamics.quimp.plugin.qanalysis.Q_Analysis},
 * {@link com.github.celldynamics.quimp.plugin.ana.ANAp}. QuimP supports two independent file
 * formats:
 * <ol>
 * <li>based on separate files (old QuimP) such as case_cellno.paQP
 * <li>compound <i>case.QCONF</i> that contains data for all cells
 * </ol>
 * Many of parameters in underlying class QParams are set to be private and they are accessible by
 * setters and getters. Many setter/getter are overridden in this class and contains simple logic to
 * provide requested and expected data even if the source file was <i>QCONF</i>. There is also
 * method that convert parameters read from QCONF and fills underlying fields in QParams.
 * Appropriate object either QParam or QParamsQconf is created upon configuration file type. Owing
 * to Java late binding, always correct method is called even if the object is casted to QParams
 * 
 * @author p.baniukiewicz
 *
 */
public class QParamsQconf extends QParams {

  /**
   * The Constant LOGGER.
   */
  static final Logger LOGGER = LoggerFactory.getLogger(QParamsQconf.class.getName());
  private Serializer<DataContainer> loaded; // instance of loaded data
  private File newParamFile;
  /**
   * Currently processed handler.
   * 
   * <p>This is compatibility parameter. Old QuimP uses separated files for every snake thus QParams
   * contained always correct values as given snake has been loaded. New QuimP uses composed file
   * and this field points to currently processed Handler and it must be controlled from outside.
   * For compatibility reasons all setters and getters assumes that there is only one Handler (as
   * in old QuimP). This field allow to set current Handler if QParamsEschange instance is used.
   */
  private int currentHandler;

  /**
   * Instantiates a new q params qconf.
   */
  public QParamsQconf() {

  }

  /**
   * Set default values for superclass, also prefix and path for files.
   * 
   * @param p <i>QCONF</i> file with extension
   */
  public QParamsQconf(File p) {
    super(p);
    currentHandler = 0;
    newParamFile = p;
    // prepare correct name for old parameters
    super.setParamFile(new File(QuimpToolsCollection
            .removeExtension(newParamFile.getParent() + File.separator + newParamFile.getName())
            + "_" + currentHandler + FileExtensions.configFileExt));
    paramFormat = QParams.NEW_QUIMP;
  }

  /**
   * Get configuration file (with path).
   * 
   * @return the newParamFile
   */
  @Override
  public File getParamFile() {
    return newParamFile;
  }

  /**
   * Get name of configuration file.
   * 
   * @return the prefix. Without any cell number in contrary to super.getFileName(). Only filename
   *         without path and extension.
   */
  @Override
  public String getFileName() {
    return QuimpToolsCollection.removeExtension(newParamFile.getName());
  }

  /**
   * Extract DataContainer from Serializer super class.
   * 
   * @return the loadedDataContainer
   */
  public DataContainer getLoadedDataContainer() {
    return loaded.obj;
  }

  /**
   * Return file creation date and other parameters.
   * 
   * @return QuimpVersion structure
   */
  public QuimpVersion getFileVersion() {
    return loaded.timeStamp;
  }

  /**
   * Read composite <i>QCONF</i> file.
   * 
   * <p>Update <tt>outputFileCore</tt> in {@link BOAp} to current QCONF.
   * 
   * @throws QuimpException when problem with loading/parsing JSON
   */
  @Override
  public void readParams() throws QuimpException {
    Serializer<DataContainer> s = new Serializer<>(DataContainer.class, QuimP.TOOL_VERSION);
    s.registerConverter(new Converter170202<>(QuimP.TOOL_VERSION));
    try {
      // load file and make first check of correctness
      loaded = s.load(getParamFile()); // try to load
      // restore qstate because some methods still need it
      BOA_.qState = getLoadedDataContainer().getBOAState();
      // update path and file core name
      if (getLoadedDataContainer().getBOAState() != null) {
        getLoadedDataContainer().getBOAState().boap
                .setOutputFileCore(newParamFile.getAbsolutePath());
      }
    } catch (Exception e) { // stop on fail (file or json error)
      LOGGER.debug(e.getMessage(), e);
      throw new QuimpException(
              "Loading or processing of " + getParamFile().getAbsolutePath() + " failed", e);
    }
    // second check of basic logic
    // checking against nulls is in Serializer
    if (!loaded.className.equals("DataContainer") || !loaded.timeStamp.getName().equals("QuimP")
            && !loaded.timeStamp.getName().equals(QuimpToolsCollection.defNote)) {
      LOGGER.debug("Not QuimP file?");
      throw new QuimpException(
              "Loaded file " + getParamFile().getAbsolutePath() + " is not QuimP file");
    }
    compatibilityLayer(); // fill underlying data (paQP) from QCONF
  }

  /**
   * Sets the active handler.
   *
   * @param num the new active handler
   */
  public void setActiveHandler(int num) {
    currentHandler = num;
    compatibilityLayer();
  }

  /**
   * Gets the active handler.
   *
   * @return the active handler
   */
  public int getActiveHandler() {
    return currentHandler;
  }

  /**
   * Write all parameters in new format.
   * 
   * <p>Makes pure dump what means that object is already packed with QuimP format. Used when
   * original data has been loaded, modified and then they must be saved again under the same
   * name.
   * 
   * @throws IOException When file can not be saved
   */
  @Override
  public void writeParams() throws IOException {
    LOGGER.debug("New file format: Updating data " + getParamFile());
    try {
      // loaded.obj.beforeSerialize(); // call explicitly beforeSerialize because Dump doesn't
      // do
      // Serializer.Dump(loaded, getParamFile(), BOA_.qState.boap.savePretty); // "loaded" is
      // already
      // packed by
      // Serializer
      Serializer<DataContainer> n;
      n = new Serializer<>(getLoadedDataContainer(), QuimP.TOOL_VERSION);
      if (getLoadedDataContainer().BOAState.boap.savePretty) {
        // configured
        n.setPretty();
      }
      n.save(getParamFile().getAbsolutePath());
      n = null;
    } catch (FileNotFoundException e) {
      LOGGER.error("File " + getParamFile() + " could not be saved. " + e.getMessage());
      LOGGER.debug(e.getMessage(), e);
      throw new IOException("File " + getParamFile() + " could not be saved. ", e);
    }
  }

  /**
   * Fill some underlying fields to assure compatibility between new and old formats.
   * 
   * <p><b>Warning</b>
   * 
   * <p>Some data depend on status of <tt>currentHandler</tt> that points to current outline. This
   * is
   * due to differences in file handling between old format (separate paQP for every cell) and new
   * (one file).
   */
  private void compatibilityLayer() {
    // fill underlying parameters
    super.setParamFile(new File(QuimpToolsCollection.removeExtension(newParamFile.getAbsolutePath())
            + "_" + currentHandler + FileExtensions.configFileExt));
    super.guessOtherFileNames();
    super.setSnakeQP(getSnakeQP());
    super.setStatsQP(getStatsQP());
    if (getLoadedDataContainer().getBOAState() != null) {
      super.setSegImageFile(getLoadedDataContainer().getBOAState().boap.getOrgFile());
      super.setImageScale(getLoadedDataContainer().getBOAState().boap.getImageScale());
      super.setFrameInterval(getLoadedDataContainer().getBOAState().boap.getImageFrameInterval());
      super.nmax = getLoadedDataContainer().getBOAState().boap.NMAX;
      super.deltaT = getLoadedDataContainer().getBOAState().boap.delta_t;
      super.maxIterations = getLoadedDataContainer().getBOAState().segParam.max_iterations;
      super.setNodeRes(getLoadedDataContainer().getBOAState().segParam.getNodeRes());
      super.setBlowup(getLoadedDataContainer().getBOAState().segParam.blowup);
      super.sampleTan = getLoadedDataContainer().getBOAState().segParam.sample_tan;
      super.sampleNorm = getLoadedDataContainer().getBOAState().segParam.sample_norm;
      super.velCrit = getLoadedDataContainer().getBOAState().segParam.vel_crit;
      super.centralForce = getLoadedDataContainer().getBOAState().segParam.f_central;
      super.contractForce = getLoadedDataContainer().getBOAState().segParam.f_contract;
      super.frictionForce = getLoadedDataContainer().getBOAState().boap.f_friction;
      super.imageForce = getLoadedDataContainer().getBOAState().segParam.f_image;
      super.sensitivity = getLoadedDataContainer().getBOAState().boap.sensitivity;
      super.finalShrink = getLoadedDataContainer().getBOAState().segParam.finalShrink;
      // set frames from snakes
      super.setStartFrame(getLoadedDataContainer().getBOAState().nest.getHandler(currentHandler)
              .getStartFrame());
      super.setEndFrame(
              getLoadedDataContainer().getBOAState().nest.getHandler(currentHandler).getEndFrame());
      if (getLoadedDataContainer().getEcmmState() != null) {
        super.setStartFrame(
                getLoadedDataContainer().getEcmmState().oHs.get(currentHandler).getStartFrame());
        super.setEndFrame(
                getLoadedDataContainer().getEcmmState().oHs.get(currentHandler).getEndFrame());
      }
      // fill only if ANA has been run
      if (getLoadedDataContainer().getANAState() != null) {
        super.cortexWidth =
                getLoadedDataContainer().getANAState().aS.get(currentHandler).getCortexWidthScale();

        // copy here is due to #204 - when new tiff is added to old loaded fluTiffs,
        // previous absolute paths / are extended to full: /xxx/yyy/Quimp
        File[] lf = getLoadedDataContainer().getANAState().aS.get(currentHandler).fluTiffs;
        this.fluTiffs = new File[lf.length];
        fluTiffs[0] = new File(lf[0].getPath());
        fluTiffs[1] = new File(lf[1].getPath());
        fluTiffs[2] = new File(lf[2].getPath());
      }

    }
  }

  /**
   * Write parameter file paQP in old format (QuimP11).
   * 
   * @throws IOException
   * 
   */
  public void writeOldParams() throws IOException {
    super.writeParams();
  }

  /*
   * (non-Javadoc)
   * 
   * In old way this was related always to loaded file that was separate for every snake. In new
   * way this field should not exist stand alone
   * 
   * @see com.github.celldynamics.quimp.QParams#getStartFrame()
   * 
   */
  @Override
  public int getStartFrame() {
    return super.getStartFrame();
  }

  /*
   * (non-Javadoc)
   * 
   * In old way this was related always to loaded file that was separate for every snake. In new
   * way this field should not exist stand alone
   * 
   * @see com.github.celldynamics.quimp.QParams#setStartFrame(int)
   * 
   */
  @Override
  public void setStartFrame(int startFrame) {
    super.setStartFrame(startFrame); // backward compatibility
    getLoadedDataContainer().getBOAState().nest.getHandler(currentHandler).startFrame = startFrame;
  }

  /*
   * (non-Javadoc)
   * 
   * In old way this was related always to loaded file that was separate for every snake. In new
   * way this field should not exist stand alone
   * 
   * @see com.github.celldynamics.quimp.QParams#getEndFrame()
   */
  @Override
  public int getEndFrame() {
    return super.getEndFrame();
  }

  /*
   * (non-Javadoc)
   * 
   * In old way this was related always to loaded file that was separate for every snake. In new
   * way this field should not exist stand alone
   * 
   * @see com.github.celldynamics.quimp.QParams#setEndFrame(int)
   */
  @Override
  public void setEndFrame(int endFrame) {
    super.setEndFrame(endFrame);
    getLoadedDataContainer().getBOAState().nest.getHandler(currentHandler).endFrame = endFrame;
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#getImageScale()
   */
  @Override
  public double getImageScale() {
    return super.getImageScale();
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#setImageScale(double)
   */
  @Override
  public void setImageScale(double imageScale) {
    getLoadedDataContainer().getBOAState().boap.setImageScale(imageScale);
    super.setImageScale(imageScale);
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#getFrameInterval()
   */
  @Override
  public double getFrameInterval() {
    return super.getFrameInterval();
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#setFrameInterval(double)
   */
  @Override
  public void setFrameInterval(double frameInterval) {
    getLoadedDataContainer().getBOAState().boap.setImageFrameInterval(frameInterval);
    super.setFrameInterval(frameInterval);
  }

  /**
   * Return Nest object.
   * 
   * @return {@link Nest} object from loaded dataset.
   */
  public Nest getNest() {
    if (getLoadedDataContainer() != null) {
      return getLoadedDataContainer().getBOAState().nest;
    } else {
      return null;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#getBlowup()
   */
  @Override
  public int getBlowup() {
    return super.getBlowup();
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#setBlowup(int)
   */
  @Override
  public void setBlowup(int blowup) {
    getLoadedDataContainer().getBOAState().segParam.blowup = blowup;
    super.setBlowup(blowup);
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#getNodeRes()
   */
  @Override
  public double getNodeRes() {
    return super.getNodeRes();
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#setNodeRes(int)
   */
  @Override
  public void setNodeRes(double nodeRes) {
    getLoadedDataContainer().getBOAState().segParam.setNodeRes(nodeRes);
    super.setNodeRes(nodeRes);
  }

  /**
   * For new file format it redirects call to super class searching for old files (paQP).
   * 
   * <p>Finally old files can be processed together with new one.
   * 
   * @return Array of found files.
   * @see com.github.celldynamics.quimp.QParams#findParamFiles()
   */
  @Override
  public File[] findParamFiles() {
    return super.findParamFiles();
  }

  /**
   * Create fake snQP name, for compatibility reasons.
   * 
   * @return theoretical name of snQP file which is used then to estimate names of map files by
   *         com.github.celldynamics.quimp.Qp class. This name contains \a suffix already
   * @see com.github.celldynamics.quimp.QParams#getSnakeQP()
   */
  @Override
  public File getSnakeQP() {
    String path = getParamFile().getParent();
    String file = QuimpToolsCollection.removeExtension(getParamFile().getName());
    return new File(
            path + File.separator + file + "_" + currentHandler + FileExtensions.snakeFileExt);
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.QParams#getStatsQP()
   * 
   * @see com.github.celldynamics.quimp.QParamsQconf.getSnakeQP()
   */
  @Override
  public File getStatsQP() {
    String path = getParamFile().getParent();
    String file = QuimpToolsCollection.removeExtension(getParamFile().getName());
    return new File(
            path + File.separator + file + "_" + currentHandler + FileExtensions.statsFileExt);
  }

}