RandomWalkModel.java

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

import java.util.Arrays;

import com.github.celldynamics.quimp.geom.TrackOutline;
import com.github.celldynamics.quimp.geom.filters.OutlineProcessor;
import com.github.celldynamics.quimp.plugin.AbstractPluginOptions;
import com.github.celldynamics.quimp.plugin.EscapedPath;
import com.github.celldynamics.quimp.plugin.generatemask.GenerateMask_;
import com.github.celldynamics.quimp.plugin.randomwalk.BinaryFilters.Filters;
import com.github.celldynamics.quimp.plugin.randomwalk.PropagateSeeds.Propagators;

import ij.ImagePlus;
import ij.WindowManager;
import ij.io.Opener;

/**
 * This class holds all possible RW parameters.
 * 
 * @author p.baniukiewicz
 * @see RandomWalkSegmentationPlugin_
 * @see RandomWalkView
 * @see RandomWalkOptions
 */
public class RandomWalkModel extends AbstractPluginOptions {

  /**
   * Possible sources of seeds.
   * 
   * @author p.baniukiewicz
   *
   */
  public enum SeedSource {
    /**
     * Seed for IJ RGB image.
     */
    RGBImage,
    /**
     * Seed form RGB image created from UI.
     */
    CreatedImage,
    /**
     * Seed form binary mask image.
     */
    MaskImage,
    /**
     * Seed from binary mask image read from Qconf file.
     */
    QconfFile,
    /**
     * ROIs.
     */
    Rois
  }

  /**
   * Parameters of Random Walk algorithm itself.
   * 
   * @see RandomWalkSegmentation
   */
  public RandomWalkOptions algOptions;

  /**
   * Get shrink methods supported by PropagateSeeds class in form of String[].
   * 
   * @return array of filters
   * @see PropagateSeeds
   * @see RandomWalkSegmentationPlugin_#runPlugin()
   */
  public String[] getShrinkMethods() {
    return Arrays.stream(Propagators.values()).map(Enum::name).toArray(String[]::new);
  }

  /**
   * Get filtering methods supported by BinaryFilters class in form of String[].
   * 
   * @return array of filters
   * @see BinaryFilters
   * @see RandomWalkSegmentation#run(Seeds)
   * @see RandomWalkSegmentation
   */
  public String[] getFilteringMethods() {
    return Arrays.stream(Filters.values()).map(Enum::name).toArray(String[]::new);
  }

  /**
   * Get seed sources defined in {@link SeedSource}.
   * 
   * @return array of seed sources
   * @see SeedSource
   */
  public String[] getSeedSources() {
    return Arrays.stream(SeedSource.values()).map(Enum::name).toArray(String[]::new);
  }

  /**
   * Image to process.
   */
  private transient ImagePlus originalImage;
  /**
   * Image to process - name or full path. This is required for proper serialisation. Only string is
   * remembered.
   * 
   * @see #getOriginalImage()
   */
  @EscapedPath
  private String originalImageName;

  /**
   * Get original image from this object.
   * 
   * <p>If not available try to restore from remembered title searched among opened images. If it
   * fails tries to load from disk.
   * 
   * @return the originalImage or null if it can not be obtained
   */
  public ImagePlus getOriginalImage() {
    if (this.originalImage != null) {
      return originalImage;
    } else if (WindowManager.getImage(originalImageName) != null) { // try get opened
      return WindowManager.getImage(originalImageName);
    } else if (originalImageName != null && !originalImageName.isEmpty()) { // try to load
      Opener op = new Opener();
      return op.openImage(originalImageName);
    } else {
      return null;
    }
  }

  /**
   * Set original image and update name field for serialisation.
   * 
   * @param originalImage the originalImage to set
   */
  public void setOriginalImage(ImagePlus originalImage) {
    this.originalImage = originalImage;
    if (originalImage == null) {
      this.originalImageName = "";
    } else {
      this.originalImageName = originalImage.getTitle();
    }
  }

  /**
   * Selected seed source. Depending on value some of fields may be invalid.
   */
  private SeedSource selectedSeedSource;

  /**
   * SeedSource getter.
   * 
   * @return the seedSource
   * @see SeedSource
   */
  public SeedSource getSelectedSeedSource() {
    return selectedSeedSource;
  }

  /**
   * SeedSource setter.
   * 
   * @param selectedSeedSource the selectedSeedSource to set
   */
  public void setSelectedSeedSource(SeedSource selectedSeedSource) {
    this.selectedSeedSource = selectedSeedSource;
  }

  /**
   * SeedSource setter.
   * 
   * @param selectedSeedSource index of SeedSource to set according to order returned by
   *        {@link #getSeedSources()}
   */
  public void setSelectedSeedSource(int selectedSeedSource) {
    this.selectedSeedSource = SeedSource.valueOf(getSeedSources()[selectedSeedSource]);
  }

  /**
   * Seed given by RGB image selected from IJ. Valid for all seed sources.
   */
  private transient ImagePlus seedImage;
  /**
   * Seed image - name or path. This is required for proper serialisation.
   * 
   * @see #getSeedImage()
   */
  @EscapedPath
  private String seedImageName;

  /**
   * Get seed image.
   * 
   * <p>It returns image from this object. If it is null it tries to get it from opened images or
   * finally read from disk.
   * 
   * @return the seedImage or null if it can not be obtained
   */
  public ImagePlus getSeedImage() {
    if (this.seedImage != null) {
      return seedImage;
    } else if (WindowManager.getImage(seedImageName) != null) {
      return WindowManager.getImage(seedImageName);
    } else if (seedImageName != null && !seedImageName.isEmpty()) { // try to load
      Opener op = new Opener();
      return op.openImage(seedImageName);
    } else {
      return null;
    }
  }

  /**
   * Set seed image and update name for serialisation.
   * 
   * @param seedImage the seedImage to set
   */
  public void setSeedImage(ImagePlus seedImage) {
    this.seedImage = seedImage;
    if (seedImage == null) {
      this.seedImageName = "";
    } else {
      this.seedImageName = seedImage.getTitle();
    }
  }

  /**
   * Selected QCONF file. Will fill seedImage.
   */
  @EscapedPath
  private String qconfFile;

  /**
   * Get qconffile field.
   * 
   * @return the qconfFile
   */
  public String getQconfFile() {
    return qconfFile;
  }

  /**
   * Setter of qconffile filed.
   * 
   * <p>Replaces windows paths for Linux. required for proper call of {@link GenerateMask_} from
   * {@link RandomWalkSegmentationPlugin_#runPlugin()}
   * 
   * @param qconfFile the qconfFile to set
   */
  public void setQconfFile(String qconfFile) {
    if (qconfFile != null) {
      this.qconfFile = qconfFile.replace("\\", "/");
    } else {
      this.qconfFile = qconfFile;
    }
  }

  /**
   * Selected shrink algorithm.
   * 
   * @see PropagateSeeds
   */
  public Propagators selectedShrinkMethod;

  /**
   * Shrink method getter.
   * 
   * @return the selectedFilteringMethod number
   * @see PropagateSeeds#getPropagator(Propagators, boolean, ij.process.AutoThresholder.Method)
   * @see Propagators
   */
  public Propagators getselectedShrinkMethod() {
    return selectedShrinkMethod;
  }

  /**
   * Seed propagator setter.
   * 
   * @param selectedShrinkMethod the selectedFilteringMethod to set
   */
  public void setselectedShrinkMethod(Propagators selectedShrinkMethod) {
    this.selectedShrinkMethod = selectedShrinkMethod;
  }

  /**
   * Seed propagator setter.
   * 
   * @param selectedShrinkMethod index of shrink method respecting order returned by
   *        {@link RandomWalkModel#getShrinkMethods()}
   */
  public void setselectedShrinkMethod(int selectedShrinkMethod) {
    this.selectedShrinkMethod = Propagators.valueOf(getShrinkMethods()[selectedShrinkMethod]);
  }

  /**
   * ShrinkPower parameter.
   * 
   * <p>Number of erosions for generating next seed from previous one. Also number of pixels to
   * shrink contour.
   */
  public double shrinkPower;
  /**
   * ExpandPower parameter.
   * 
   * <p>Number of dilations for generating next seed from previous one. Also number of pixels to
   * expand contour.
   */
  public double expandPower;
  /**
   * Scale sigma parameter.
   * 
   * <p>Shape of Gaussian curve used to estimate magnitude of scaling related to local curvature.
   * 
   * @see PropagateSeeds.Contour#propagateSeed(ij.process.ImageProcessor, ij.process.ImageProcessor,
   *      double, double)
   * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double, double)
   * @see PropagateSeeds.Contour#scaleSigma
   */
  public double scaleSigma = 0.3;
  /**
   * Maximal magnitude of scaling for regions with smallest curvature.
   * 
   * @see PropagateSeeds.Contour#propagateSeed(ij.process.ImageProcessor,
   *      ij.process.ImageProcessor,
   *      double, double)
   * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double, double)
   * @see PropagateSeeds.Contour#scaleMagn
   */
  public double scaleMagn = 1.0;
  /**
   * If non zero, normals are set to direction of normal of node with smallest curvature (negative).
   * Work locally within defined range.
   * 
   * @see PropagateSeeds.Contour#propagateSeed(ij.process.ImageProcessor, ij.process.ImageProcessor,
   *      double, double)
   * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double, double)
   * @see PropagateSeeds.Contour#averageNormalsDist
   */
  public double scaleEqNormalsDist = 0;
  /**
   * Range of averaging of curvature. Roughly equals to number of nodes being averaged. Regardless
   * this value always at least three nodes are averaged.
   * 
   * @see PropagateSeeds.Contour#propagateSeed(ij.process.ImageProcessor, ij.process.ImageProcessor,
   *      double, double)
   * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double, double)
   * @see PropagateSeeds.Contour#averageCurvDist
   */
  public double scaleCurvDistDist = 1.0;
  /**
   * Estimate background if true.
   * 
   * @see PropagateSeeds#getTrueBackground(ij.process.ImageProcessor, ij.process.ImageProcessor)
   * @see PropagateSeeds
   */
  public boolean estimateBackground;
  /**
   * If true add extra filtering during applying {@link Propagators#CONTOUR} shrink method.
   * 
   * <p>By default none filtering is applied (use of {@link TrackOutlinNoFilter} object. Otherwise
   * {@link TrackOutline} is used.
   * 
   * @see TrackOutlinNoFilter
   * @see TrackOutline
   * @see PropagateSeeds.Contour#useFiltering
   */
  public boolean interFrameFilter = false;
  /**
   * Selected intermediate filtering algorithm.
   */
  private Filters selectedFilteringMethod;

  /**
   * Filtering getter.
   * 
   * @return the selectedFilteringMethod
   * @see BinaryFilters#getFilter(Filters)
   * @see Filters
   */
  public Filters getSelectedFilteringMethod() {
    return selectedFilteringMethod;
  }

  /**
   * Filtering setter. Creates instance of filter.
   * 
   * @param selectedFilteringMethod the selectedFilteringMethod to set
   */
  public void setSelectedFilteringMethod(Filters selectedFilteringMethod) {
    this.selectedFilteringMethod = selectedFilteringMethod;
    algOptions.intermediateFilter = BinaryFilters.getFilter(selectedFilteringMethod);
  }

  /**
   * Post filtering setter. Creates instance of filter.
   * 
   * @param selectedFilteringMethod index of filter to set according to order returned by
   *        {@link #getFilteringMethods()}
   */
  public void setSelectedFilteringMethod(int selectedFilteringMethod) {
    this.selectedFilteringMethod = Filters.valueOf(getFilteringMethods()[selectedFilteringMethod]);
    algOptions.intermediateFilter = BinaryFilters.getFilter(this.selectedFilteringMethod);
  }

  /**
   * true for HatFilter active.
   */
  public boolean hatFilter;
  /**
   * alev parameter. Valid for hatFilter==true.
   */
  public double alev;
  /**
   * num parameter. Valid for hatFilter==true.
   */
  public int num;
  /**
   * window parameter. Valid for hatFilter==true.
   */
  public int window;
  /**
   * Selected final binary filtering.
   */
  private Filters selectedFilteringPostMethod;

  /**
   * Post filtering getter.
   * 
   * @return the selectedFilteringPostMethod
   * @see BinaryFilters#getFilter(Filters)
   * @see Filters
   */
  public Filters getSelectedFilteringPostMethod() {
    return selectedFilteringPostMethod;
  }

  /**
   * Post filtering setter. Creates instance of filter.
   * 
   * @param selectedFilteringPostMethod the selectedFilteringPostMethod to set
   */
  public void setSelectedFilteringPostMethod(Filters selectedFilteringPostMethod) {
    this.selectedFilteringPostMethod = selectedFilteringPostMethod;
    algOptions.finalFilter = BinaryFilters.getFilter(selectedFilteringPostMethod);
  }

  /**
   * Post filtering setter. Creates instance of filter.
   * 
   * @param selectedFilteringPostMethod index of filter to set according to order returned by
   *        {@link #getFilteringMethods()}
   */
  public void setSelectedFilteringPostMethod(int selectedFilteringPostMethod) {
    this.selectedFilteringPostMethod =
            Filters.valueOf(getFilteringMethods()[selectedFilteringPostMethod]);
    algOptions.finalFilter = BinaryFilters.getFilter(this.selectedFilteringPostMethod);
  }

  /**
   * true for showing seeds.
   */
  public boolean showSeeds;
  /**
   * true for showing preview.
   */
  public boolean showPreview;
  /**
   * true for showing probability maps.
   */
  public boolean showProbMaps;

  /**
   * Default constructor setting default parameters.
   */
  public RandomWalkModel() {
    algOptions = new RandomWalkOptions();
    originalImage = null;
    setSelectedSeedSource(SeedSource.RGBImage);
    seedImage = null;
    qconfFile = null;
    selectedShrinkMethod = Propagators.NONE;
    shrinkPower = 10;
    expandPower = 15;
    estimateBackground = false;
    setSelectedFilteringMethod(Filters.NONE);
    hatFilter = false;
    alev = 0.9;
    num = 1;
    window = 15;
    setSelectedFilteringPostMethod(Filters.NONE);
    showSeeds = false;
    showPreview = false;
    showProbMaps = false;
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "RandomWalkModel [params=" + algOptions + ", originalImage=" + originalImage
            + ", seedSource=" + getSelectedSeedSource() + ", seedImage=" + seedImage
            + ", qconfFile=" + qconfFile + ", selectedShrinkMethod=" + selectedShrinkMethod
            + ", shrinkPower=" + shrinkPower + ", expandPower=" + expandPower
            + ", estimateBackground=" + estimateBackground + ", interFrameFilter="
            + interFrameFilter + ", selectedFilteringMethod=" + selectedFilteringMethod
            + ", hatFilter=" + hatFilter + ", alev=" + alev + ", num=" + num + ", window=" + window
            + ", selectedFilteringPostMethod=" + selectedFilteringPostMethod + ", showSeeds="
            + showSeeds + ", showPreview=" + showPreview + ", showPprobMaps=" + showProbMaps
            + ", getShrinkMethods()=" + Arrays.toString(getShrinkMethods())
            + ", getFilteringMethods()=" + Arrays.toString(getFilteringMethods())
            + ", getselectedShrinkMethod()=" + getselectedShrinkMethod()
            + ", getSelectedFilteringMethod()=" + getSelectedFilteringMethod()
            + ", getSelectedFilteringPostMethod()=" + getSelectedFilteringPostMethod() + "]";
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    long temp;
    temp = Double.doubleToLongBits(alev);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(expandPower);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + (hatFilter ? 1231 : 1237);
    result = prime * result + num;
    result = prime * result + ((originalImage == null) ? 0 : originalImage.getTitle().hashCode());
    result = prime * result + ((algOptions == null) ? 0 : algOptions.hashCode());
    result = prime * result + ((qconfFile == null) ? 0 : qconfFile.hashCode());
    result = prime * result + ((seedImage == null) ? 0 : seedImage.getTitle().hashCode());
    result = prime * result + ((selectedSeedSource == null) ? 0 : selectedSeedSource.hashCode());
    result = prime * result
            + ((selectedFilteringMethod == null) ? 0 : selectedFilteringMethod.hashCode());
    result = prime * result
            + ((selectedFilteringPostMethod == null) ? 0 : selectedFilteringPostMethod.hashCode());
    result = prime * result
            + ((selectedShrinkMethod == null) ? 0 : selectedShrinkMethod.hashCode());
    result = prime * result + (showPreview ? 1231 : 1237);
    result = prime * result + (showSeeds ? 1231 : 1237);
    result = prime * result + (showProbMaps ? 1231 : 1237);
    result = prime * result + (estimateBackground ? 1231 : 1237);
    result = prime * result + (interFrameFilter ? 1231 : 1237);
    temp = Double.doubleToLongBits(shrinkPower);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + window;
    return result;
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    RandomWalkModel other = (RandomWalkModel) obj;
    if (Double.doubleToLongBits(alev) != Double.doubleToLongBits(other.alev)) {
      return false;
    }
    if (Double.doubleToLongBits(expandPower) != Double.doubleToLongBits(other.expandPower)) {
      return false;
    }
    if (hatFilter != other.hatFilter) {
      return false;
    }
    if (num != other.num) {
      return false;
    }
    if (originalImage == null) {
      if (other.originalImage != null) {
        return false;
      }
    } else if (!originalImage.getTitle().equals(other.originalImage.getTitle())) {
      return false;
    }
    if (algOptions == null) {
      if (other.algOptions != null) {
        return false;
      }
    } else if (!algOptions.equals(other.algOptions)) {
      return false;
    }
    if (qconfFile == null) {
      if (other.qconfFile != null) {
        return false;
      }
    } else if (!qconfFile.equals(other.qconfFile)) {
      return false;
    }
    if (seedImage == null) {
      if (other.seedImage != null) {
        return false;
      }
    } else if (!seedImage.getTitle().equals(other.seedImage.getTitle())) {
      return false;
    }
    if (selectedSeedSource != other.selectedSeedSource) {
      return false;
    }
    if (selectedFilteringMethod != other.selectedFilteringMethod) {
      return false;
    }
    if (selectedFilteringPostMethod != other.selectedFilteringPostMethod) {
      return false;
    }
    if (selectedShrinkMethod != other.selectedShrinkMethod) {
      return false;
    }
    if (showPreview != other.showPreview) {
      return false;
    }
    if (showSeeds != other.showSeeds) {
      return false;
    }
    if (showProbMaps != other.showProbMaps) {
      return false;
    }
    if (estimateBackground != other.estimateBackground) {
      return false;
    }
    if (interFrameFilter != other.interFrameFilter) {
      return false;
    }
    if (Double.doubleToLongBits(shrinkPower) != Double.doubleToLongBits(other.shrinkPower)) {
      return false;
    }
    if (window != other.window) {
      return false;
    }
    return true;
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.plugin.AbstractPluginOptions#afterSerialize()
   */
  @Override
  public void afterSerialize() throws Exception {
    setSelectedFilteringPostMethod(selectedFilteringPostMethod);
    setSelectedFilteringMethod(selectedFilteringMethod);
  }

}