PropagateSeeds.java

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

import java.awt.Color;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.celldynamics.quimp.Outline;
import com.github.celldynamics.quimp.QuimP;
import com.github.celldynamics.quimp.geom.TrackOutline;
import com.github.celldynamics.quimp.geom.filters.OutlineProcessor;
import com.github.celldynamics.quimp.plugin.ana.ANAp;
import com.github.celldynamics.quimp.plugin.randomwalk.BinaryFilters.MorphoOperations;
import com.github.celldynamics.quimp.plugin.randomwalk.RandomWalkSegmentation.SeedTypes;
import com.github.celldynamics.quimp.utils.IJTools;
import com.github.celldynamics.quimp.utils.test.RoiSaver;

import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.Roi;
import ij.process.AutoThresholder;
import ij.process.Blitter;
import ij.process.ByteProcessor;
import ij.process.ImageProcessor;

/**
 * Generate new seeds for n+1 frame in stack using previous results of segmentation.
 * 
 * <p>This class supports two methods:
 * <ol>
 * <li>Based on morphological operations
 * <li>Based on contour shrinking (part of QuimP Outline framework)
 * </ol>
 * 
 * <p>In both cases the aim is to shrink the object (which is white) to prevent overlapping
 * foreground
 * and background in next frame (assuming that objects are moving). The same is for background.
 * Finally, the new seed should have set foreground pixels to area inside the object and background
 * pixels in remaining part of image. There should be unseeded strip of pixels around the object.
 * 
 * @author p.baniukiewicz
 */
public abstract class PropagateSeeds {

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

  /**
   * Seed propagators available in this class.
   * 
   * @author p.baniukiewicz
   *
   */
  public enum Propagators {
    /**
     * Just copy input as output.
     */
    NONE,
    /**
     * Use contour shrinking.
     * 
     * @see Contour
     */
    CONTOUR,
    /**
     * Use morphological operations.
     * 
     * @see Morphological
     */
    MORPHOLOGICAL

  }

  /**
   * Default setting. Better do not change.
   */
  public boolean darkBackground = true;
  /**
   * Thresholding method used for estimating true background.
   * 
   * <p>If null background is not modified.
   * 
   */
  private AutoThresholder.Method thresholdMethod = null;

  /**
   * Default constructor.
   */
  public PropagateSeeds() {
  }

  /**
   * Allow to store seed history that can be later presented in form of composite image.
   * 
   * @param storeSeeds <tt>true</tt> to store seeds.
   * @param trueBackground if not null, selected method will be used for estimating true background
   *        - excluding bright objects from it
   * @see #getCompositeSeed(ImagePlus, int)
   */
  public PropagateSeeds(boolean storeSeeds, AutoThresholder.Method trueBackground) {
    this.storeSeeds = storeSeeds;
    if (storeSeeds) {
      this.seeds = new ArrayList<>();
    }
    thresholdMethod = trueBackground;
  }

  /**
   * Default resolution used during outlining objects.
   * 
   * @see Contour#getOutlineAndColors(ImageProcessor, boolean)
   */
  public static final int STEPS = 4;
  /**
   * By default seed history is not stored.
   */
  protected boolean storeSeeds = false;
  /**
   * Container for FG and BG seeds pixels used for seed visualisation.
   * 
   * <p>Every imageProcessor in pair contains important bits set to WHITE. For example BG pixels are
   * white here as well as FG pixels.
   * 
   * @see #getCompositeSeed(ImagePlus, int)
   * @see PropagateSeeds#storeSeeds
   */
  protected List<Seeds> seeds;
  /**
   * Scale color values in composite preview.
   * 
   * <p>1.0 stand for opaque colors.
   * 
   * @see #getCompositeSeed(ImagePlus, int)
   */
  public static final double colorScaling = 0.5;

  /**
   * Return demanded propagator.
   * 
   * @param prop propagator to create
   * @param storeseeds true for storing seeds
   * @param trueBackground if not null, selected method will be used for estimating true
   *        background - excluding bright objects from it
   * @return the propagator
   */
  public static PropagateSeeds getPropagator(Propagators prop, boolean storeseeds,
          AutoThresholder.Method trueBackground) {
    switch (prop) {
      case NONE:
        return new PropagateSeeds.Dummy(storeseeds);
      case CONTOUR:
        return new PropagateSeeds.Contour(storeseeds, trueBackground);
      case MORPHOLOGICAL:
        return new PropagateSeeds.Morphological(storeseeds, trueBackground);
      default:
        throw new IllegalArgumentException("Unknown propagator");
    }
  }

  /**
   * Empty propagator. Do nothing.
   * 
   * @author p.baniukiewicz
   *
   */
  public static class Dummy extends PropagateSeeds {

    /** The binary. */
    PropagateSeeds binary;

    /**
     * Default constructor without storing seed history.
     */
    public Dummy() {
      binary = new PropagateSeeds.Morphological();
    }

    /**
     * Allow to store seed history that can be later presented in form of composite image.
     * 
     * @param storeSeeds <tt>true</tt> to store seeds.
     * @see #getCompositeSeed(ImagePlus, int)
     */
    public Dummy(boolean storeSeeds) {
      binary = new PropagateSeeds.Morphological(storeSeeds, null);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.github.celldynamics.quimp.plugin.randomwalk.PropagateSeeds#propagateSeed(ij.process.
     * ImageProcessor, double, double)
     * 
     */
    @Override
    public Seeds propagateSeed(ImageProcessor previous, ImageProcessor org, double shrinkPower,
            double expandPower) {
      Seeds decodedSeeds = new Seeds(2);
      try {
        decodedSeeds = SeedProcessor.decodeSeedsfromGrayscaleImage(previous);
        if (decodedSeeds.get(SeedTypes.FOREGROUNDS) == null) {
          throw new RandomWalkException("no FG maps");
        }
      } catch (RandomWalkException e) {
        // this is handled to console only as return is still valid (no FG seeds in output)
        LOGGER.debug("Empty seeds. " + e.getMessage());
      }
      return decodedSeeds;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.github.celldynamics.quimp.plugin.randomwalk.PropagateSeeds#getCompositeSeed(ij.ImagePlus)
     */
    @Override
    public ImagePlus getCompositeSeed(ImagePlus org, int offset) throws RandomWalkException {
      // Need override as we have different object here (binary, not this).
      return binary.getCompositeSeed(org, offset);
    }

  }

  /**
   * Contain methods for propagating seeds to the next frame using contour shrinking operations.
   * 
   * <p>{@link Contour#scaleMagn} and <t>shrinkPower</t> specified in
   * {@link OutlineProcessor#shrinknl(double, double, double, double, double, double, double, double)}
   * are related to each other. Larger <t>shrinkPower</t> stands for larger number of steps during
   * shrinking
   * (Step size is fixed {@link Contour#stepSize}). Therefore large {@link Contour#scaleMagn} will
   * move concave node too
   * fast because internally {@link Contour#stepSize} is multiplied by factor returned by
   * {@link OutlineProcessor#amplificationFactor(double, double, double)} that ranges 1.0 for
   * positive curvatures to {@link Contour#scaleMagn} for negative with specified
   * {@link Contour#scaleSigma}. Generally increasing <t>shrinkPower</t>,
   * the{@link Contour#scaleMagn} should be deceased.
   * 
   * <p>To increase effect of shifting nodes with negative curvature, normals locally can be
   * adjusted according to node with lowest curvature. See
   * {@link OutlineProcessor#shrinknl(double, double, double, double, double, double, double,
   * double)}
   * 
   * <p>For linear scaling set {@link Contour#scaleMagn} to 1.0 and
   * {@link Contour#averageNormalsDist} to 0.0.
   * 
   * <p>PropagateSeeds_Contour<br />
   * <img src=
   * "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmcAAAJBCAIAAACMJYVtAAAhEElEQVR42u3d25Kj
   * urYAUf7/p+d5OBErdpdtELrAlDQy+qGju3yjSKeEMToOAPmICBsBAADVBMjc9vMj
   * 7hPYrZrcBHYZAn+9z/gfhj7b+Be/YmxSTW4Cq1Wz/UFHjKYB1eQmcDHW+7Mffw79
   * PoeEX0eIX2/+9e8nN/x06evzLBylMhMzVpObQGpv//j29Qd+/funQr8kPL9h439d
   * vvs4CoRZqslNYBpvL8eh53dyMnQ9l6pOv5NB98m42K8eE1WTm0AudUuO2LSb+VWS
   * QjOP06NDfd+tgAzV5CYwjcMJx7OFtyoZzzITs1STm0Bqbx8ws/zOu3928kddv3ok
   * ryY3gbzqVh8F+jpyLLf966jz12i0/Py985dJS8wy1+QmAAA3pmIAAEA1AQBQTQAA
   * VBMAANUEAEA1AQBQTQDeLLyhgAjcBHBl3fMr3/76d28EUE1uAhNUs/0dpOJinufP
   * BFBNbgI3xnrlV9g6mhfO/fRkxMq3Jw+X+S0Mm1eTm9xE0mT+EvLWgu/nC+eWrxff
   * 8l/MxDLV5CY3kd3bCjPLj+10NPPSt5NrTJ88YWYiWzW5yU0knWv+OrxT6HDLugqF
   * Zh49Vr41nsV0c01uchNzeNt4FGiEmeWC3R3PMhPJ55rctLcgo7cdPztp+ceOB4gu
   * 34acp4cpqslNIJe6Xc7TK1kR/vMnn1n59vI+mYls1eQmN7G44QBecYqbgGoCnOIm
   * wHAAqgkAGd6Ey/7ASBTcBDg55IehmuAmsJqWLYNfqCa4CRjGGt6qJriJx2SuW6kA
   * UznZPijG09XkJjcxt9LtZpZ/l/n8i9g7vUfEVHeL/vpwk5vYupold/Vrtb8tR7KT
   * 3jnmqyY3uYmzceKlDBWL3N4S++5VK/czM5Z4CJTu8NzkJjdTe1txjebyRW4HmbnN
   * UaAnzwtwDkKKanKTm9yczOFCM8+9+vU5x58BaeH49ETCpeWM1d8LGMdNbnJzKnWr
   * V74tX0JhxHi2+j1ofTO7XXWEnG9Wk5vc5GZqb3uZebmY0ed4lpl93PjlYb2czHyt
   * mtzkJje3rublE7i1mPvXhXm3NrPEvcqxLTkXryY3uYnb6p4vKnvyY41HgY7iY1BH
   * wTK5W2tZcYyInLmryU1ucnNxwzGHlpU39NvP6xQ3uQnVRJkM0fwIQU7VxKNuFmnH
   * TYbjeS1LT9O74ad9QDVR6eYfGYsM5SbQzcwCLe/daTDTSBQD3awfu3ITeMvMMzkN
   * aVUTY9yMxrvmJvBWMnvIyUzV5OaQZHITyFtNcqomHnAzOj4GN5fRfrP18xYZz7bJ
   * 6dc9QTW5OW81uUn+fxxmcquZ0VOUKjn9+qaZa3LzRTdjxCNxc6tq2lAJq/n95oa0
   * m1XT7yJhNbmZWt2TS2GdX7WLmQuY+eVOfIKSo5rc3HxEy83s3n5dUa/kus+OAj1q
   * 5r//HyMe0pnuCarJzdndfK+a3Hzc4UYzvy489PkDNngSM4OZWeea3FRNI9qk6n4e
   * 8Ok+nq1+v2DmUDOjVTq/x4HV5KZqmm6m9vax8Swzs801G6TzexxVTW5y8+edcHOx
   * apY8EDMnqObhhL3VqsnNGat5OJk2obqFR4HOx6ctS+ziXTOFM2E1ucnNi/vh5uyG
   * Y+pq/ly0iJnpneLm8tU03VRNJDXz+x2SUzWRwE3hVE1U7egxXAtyqiba3YxnHp6b
   * wOvVPFyRxEgUKavJTSBjMsmpmuAmwMwHzDy+nlEE1eQmN4E3zDweOgrUJufx64Rc
   * qOYmbsbDT4Wb2Put5/3PTprNpKhqbu1m4mpyM7EwHb803eVb2LfWf1DNfnL+UbTw
   * j2pyM6Gh3FTZ8W8HXfbvLndYscRERjPnG9K2m6ya3Bzy5LnJzT4Dz5O/f47yfl30
   * q3B3P7/5r/v874piJ6sdXa7lq5rp5Vzc0rpVqbd1M8fx3kSnHXAz0aTw1z/++oGv
   * F8m8Zeavm5+b2fJfy1RzDzO/Wrp4Nbn59XmmPkLLzd0moJ/7dOEgt+S9oPzml49e
   * rV/5qkmqefaAWaSIlYzj5qWbfyqe/wgtN1eO5a9slBxIyWbm+dEhc82FzJxezrql
   * SLZ1s33BbW5yc8j8stCNzGYWamauOe2BoEXkrDhCu7ObqsnN1NUsl6HQw1s3bzns
   * 43PNzYa0x7wfpdRVc2c3HaHlZhZ1f52AV3J05UStk5PlKswsP/uu/Py9863hjINJ
   * zJx1YFu9WPSebh4pVs+Ou/8f3PQVT6TYyeKFXXIGOWfy0xX1lnfToHbxSSeYeeM5
   * RSeP+l9+ZBo/VZObSau5vZtYUc5nDwRF7RvImZBj/ZpATtXcwc145TlxE8ws+f94
   * /qnc8qdCtvrhrWpil2oGN4FUc81oV679wEyNotnlVM3l3YzXnxA3wcyHk3lUL3k7
   * 6PqUUbGUoGriHTffryY3wcyHzYw6c+Lez94+5yAG2qyaWK2ad9xsXSdsCTcX2ls3
   * epeZtpr9whqXh56Ouf1caX/m5lvVjK5KRpcfnt/NsaqMWK5vp0VuJzDz19Gd9rtq
   * 3RCTn4kwdK/jpmr2Gr/W3IOzhB42s/0O51nkdg4zv+oR90/T65LtZeScsZrcXKOa
   * Iz7T3CicFSvf/lp1789VIkvWyL1cOHf1RW6nqWbLbh79vni9UjhHrErNTdV8Ze9f
   * zM3bA7q7q9SWLF17efNdF7mdvprnT+Ch0/TmlLPXqtTcVM0Mu/5G4TzZ0UuuGf2/
   * yt1dQqGjmefvR4kXuV25mo9+hbRSzkhiHDe5+Uw1J7mIWO7zgwpXvj1Ol+t7xcxj
   * hUVuVbNrOKca2PZalZqbqplnd+9woaLkk87ylW9zmlmoWeJFbpet5psXEptEzl6r
   * UnNTNVPt6Eueu1d5jOX8X77u8Y1mbrDIrWomGdjGu/Zxc0k3X5vbvb2Ln4VzBjev
   * 987ylW9LzDyK18j9ZeZOi9zW7ihpwhlv7+n9/Izn1eMmN5es5tFx1Yec4UQ+VPMV
   * Px/dkK6ox83HwhkvbalJ3cSKcqrmrYdLKadq7uBmhnC+/pXuThcq4QvWnW4mHdgm
   * k1M1ubne+LVmqwknNp9u5r1WUeQyUzVNN4cOHLN953EiN2FI+4SrkcbSZjlVExO7
   * GYmvETCLm9hFTjtU0XbovXqnatrRMg9qJwtnDjfBTFut/6BXNe1l3Bw1rlVNmG4+
   * vL1UE9yceMapmmDm0EDWfLCqmjDdzDmoVU0I52sTysrNqZoQzv4+Nh8EUk0wc9Tm
   * GDjWVU20uLmYnn8O8MS376HdOAjkbCAI55vVjFEHcFUTm7t5LlOUXA8ot5tg5k7b
   * os+31VQTppu93ZjHTWwzv9rmE5QzJxO8OagmN1d1M6pvNpWbMN3cYCvEGxtcNbGT
   * m5FII+6AmaoJbq6XTNUEOTNugnhvg6sm9nAz8glknwMzU000VRPczDnRVE2QM+lE
   * c+wDqObObm5RzWndxNbVXGbPihcmmqoJI9qcE03VBDk/Aln6Bei4c6eZ5FRN003V
   * zOkmTDfn2LNqvgMdN52s/551/02omtyM6V/Pmm6CnNNUs89tSmap8b6ZqsnN/G52
   * ruYkbmJLM/cZ1Z5fPbrn43XehKrJzZWrObObYObqch61x3biTTNVk5v53YwudzGb
   * myDnHnI+oXTXi2mqJje3FPPGiX7x7jPF9tXMvGfFSC2vLX1jlSLV5OaeYt6LoxXE
   * QM5xe30UrynfMLDttglVk5tbTTfjflDfchPMXH9Ue/ejkwY/VROmm6P0jBxugpzT
   * yBm/p48n88jou+FUE9x8aTibxE1sb+Yx2XHay0ZGm5NtcqomRrm50nRzajdBzgUv
   * 6R6dDhypJrjZvZox7kFVE+TMuOFUEy+5ucZ0c+AJRKoJ1cy47VQTqlm768f8boKc
   * wqmaEM75qvnl3lQTj+7VqlmyYZxDi1fdnHq6GUu4CWaqpmqCm31eRtz/L9UEOVVT
   * NbHpdPO4u268akI1F99kT31woprc5Ga/atpgGGQmOVUTM7hp5zicCgRDWmaqpn2N
   * m6oJ081NtFRNcPO97eVDTTBz88GsatrdSn6EniaaSCdn2ExvDGZV005nUGuiCWbO
   * vI1UE6abqglymm6Wbh3VhEFtzm2kmkgh505mXn8j+3EtVZObppsmmmDmpGN71URq
   * N7eKg4kmhDN9NXv8hGpCOB+faKomnp9lrR7O6PNDqon33dwkEaqJ7MnYPZwvaama
   * 3BROycQicqqmakI4VRPMNN1UTaxQzWOTc/dUE3nN3Hm6qZqYyk3VVE08sB9WnhYU
   * O2wF1cRsbi5cDNXEVAPbDYa0UfMKVRPcVE2Qc1czX78CrWqCm6Uv1CUOMN2QVjVV
   * E9zMOt0kEfINabcfz47aAKoJbrZVk0FIOaRVTdUEN1UTzFRN1QQ3VRNol3O3jzZf
   * +uBENdHo5vrV9KEmVFM1VROqmdxN4P6utrqcqokF3FRNewxUUzXBTW6qJqaTk5mq
   * CW6qJsDMm2YOfOmqCW5evHgn0IKZqqma4KZqgpnMVE1ws/crV01MI+c+1XzvgxPV
   * BDdrh7OqCWbuZ6Zqgpu1E03VBDMzJlM1wc2cyVRNMHOzZKomuCmZWNbMETtp/Psn
   * XzJVE6qZM5mqif2qGc9KECm1VE1sXk3JxEJmjpQzHlch27FZ1URON2eoJmuw2XQz
   * Hhci50RTNbH5dNNEE6rZ+pBbJVM1kc3Nl1+hZGJ6M8fI+WQ1o/IBVBOTuRmzv7xk
   * bgLdqtm420qmaiLtiPa1lyeZEM66Bxt4wlH02yCqCdPNF5KpmpjFzK7hfKaaUX/X
   * j2qpmujrZszwkhybxQZm/g5n9H6w6O5kYi1VE93djMR5mchNYFQ471o6qJpnFxhK
   * rKVqYpCb0WkA2vHwj2SCnDWWRrVgdT+cW0vVxFA3644G/blVxWUvo+R6mZKJLeSM
   * IlvaW1jy89FtHaHXtFRNPOBm9ButXhY0On/rkiDYIJxfR5ot59xGxR3GHCfmqSYe
   * czOu/nRxs0+cVRNLmXk88HFHc2OPObRUTXBTMrGHnEfbLPLPbWPQubPZtVRNcFMy
   * sY2Zn5o1fnhSd8yo3ur3tVRNcFMysaWcJ3a1fHhycsPW8+JTaKma4KZkYm8zhz6p
   * 6PIlskTfAldNcDOnm4DhXsbXpZrgpikmVtqJg5aqCW5KJrDh3pzxhagmuCmZMLD1
   * /FUT3Nxz0gz83b/DE1ZN2NU3e8LA4nv8NEKqJrgJ7Kaop6Sa4KZeAreHt1G8Tknh
   * n4onMNu2U01wE+DqgKuPdFyqQTXBTW4CUE0AAFQTAACoJgAAqgkAgGoCAKCaAACo
   * JgAAqgkAgGoCAADVBABANQEAUE0AAFQTAADVBABANQEAgGoCAKCaAACoJgAAqgkA
   * gGoCAKCaAACoJgAAUE0AAFQTAADVBABANQEAUE0AAFQTAACoJgAAqgkAgGoCAKCa
   * AACoJgAAqgkAgGoCAADVBABANQEAUE0AAFQTAADVBABANQEAgGoCAKCaAACoJgAA
   * qgkAgGoCAKCaAACoJgAAUE0AAFQTAADVBABANQEAUE0AAFQTAACoJgAAqgkAgGoC
   * AKCaAACoJgAAqgkAgGqqJgAAqgkAgGoCAKCaAACoJgAAqgkAgGoCAADVBABANQEA
   * UE0AAFQTAADVBABANQEAgGoCAKCaAACoJgAAqgkAgGoCAKCaAACoJgAAUE0AAFQT
   * AADVBABANQEAUE0AAFQTAACoJgAAqgkAgGoCAKCaAH7ICQAASjBsAMw1AQBQTQDT
   * yVzy8yPuE9itmtwEdhkCf73PLseRC98XHLPGhtXkJrBaNdsfdMRoGlBNbgIXY70/
   * +/Hn0O9zSPh1hPj15l//fnLDT5e+Ps/CUSozMWM1uQmk9vaPb19/4Ne/fyr0S8Lz
   * Gzb+1+W7j6NAmKWa3ASm8fZyHHp+JydD13Op6vQ7GXSfjIv96jFRNbkJ5FK35IhN
   * u5lfJSk08zg9OtT33QrIUE1uAtM4nHA8W3irkvEsMzFLNbkJpPb2ATPL77z7Zyd/
   * 1PWrR/JqchPIq271UaCvI8dy27+OOn+NRsvP3zt/mbTELHNNbgIAcGMqBgAAVBMA
   * ANUEAEA1AQBQTQAAVBMAANUE4M3CGwqIwE0AV9Y9v/Ltr3/3RgDV5CYwQTXb30Eq
   * LuZ5/kwA1eQmcGOsV36FraN54dxPT0asfHvycJnfwrB5NbnJTSRN5i8hby34fr5w
   * bvl68S3/xUwsU01uchPZva0ws/zYTkczL307ucb0yRNmJrJVk5vcRNK55q/DO4UO
   * t6yrUGjm0WPlW+NZTDfX5CY3MYe3jUeBRphZLtjd8SwzkXyuyU17CzJ62/Gzk5Z/
   * 7HiA6PJtyHl6mKKa3ARyqdvlPL2SFeE/f/KZlW8v75OZyFZNbnITixsO4BWnuAmo
   * JsApbgIMB6CaAJDhTbjsD4xEwU2Ak0N+GKoJbgKradky+IVqgpuAYazhrWqCm3hM
   * 5rqVCjCVk+2DYjxdTW5yE3Mr3W5m+XeZz7+IvdN7REx1t+ivDze5ia2rWXJXv1b7
   * 23IkO+mdY75qcpObOBsnXspQscjtLbHvXrVyPzNjiYdA6Q7PTW5yM7W3FddoLl/k
   * dpCZ2xwFevK8AOcgpKgmN7nJzckcLjTz3Ktfn3P8GZAWjk9PJFxazlj9vYBx3OQm
   * N6dSt3rl2/IlFEaMZ6vfg9Y3s9tVR8j5ZjW5yU1upva2l5mXixl9jmeZ2ceNXx7W
   * y8nM16rJTW5yc+tqXj6BW4u5f12Yd2szS9yrHNuSc/FqcpObuK3u+aKyJz/WeBTo
   * KD4GdRQsk7u1lhXHiMiZu5rc5CY3Fzccc2hZeUO//bxOcZObUE2UyRBtY9Z7A1s7
   * gGqCm2Dmclr+r25FJ+vd8NM+oJqodDO4CbxpZtQoduEnM41EMcDN8vOBuAk8ZObd
   * pW8bpCOnanLznpsHN4F5tWyWk5mqyc17E01uAtNXk5yqCW7iHe03Wz8vnZnR/WGC
   * mStUk5vzVpOb5P/HYSa3mhk9LamS069vmrkmN7mJiatpQyU08/s9GNJuVk2/i1Wq
   * yc2n1D25FNb5VbuYuUA1v9yJT1ByVJObRrQHNzN7+3VFvZLrPjsK9KiZR7fPNduq
   * Sc6x1eQmN6PeOb/HZx1uNPPrwkOfP2CD56nm3/thZo65Jje5aUSbVN3PAz7dx7PV
   * 7xfMvPx/cq5aTW6qpulmam8fG88yc4JqHj5Bebma3OQmN3epZskDMTObmYeTaTeo
   * Jje5iT7qFh4FOh+ftiyxi6RmkvO9anKTm8K5uOFgJl5xips7uHnUnHxgx1BNZn78
   * fzzw2ORUTSRwUzhVE1U7egx3QjhVE9wEmDl0SEtOteMmN4HcWg61gZyqCW4CzBxq
   * 5v//HD9Vk5vcBDKYeTx0FKhNzv/8pKhqcpOb8NaTaUgbTz6VqLsDim5aTW6mrCY3
   * EwvT8UvTXb6Ffeti2a9+4ztrNZseL27+UU1uprKSmyr71NtBl728yx0W3rD7k+9s
   * 5jH8O2G9p5vtJqsmN5PMXxO52a+a3GweeJ78/XOs9+uiX4Vqnd/8133+d0Wxk9WO
   * LtfyTelk+moer5iypqV1q1Jv66ZqcjPvpPDXP/76ga8Xybxl5q+bn5vZ8l/nGyT1
   * Edodzfxq6eLV5KZqcnO+ghYOckt26/KbXz56tX4lqyb9eafIf4T2WPkgbdtWmn+u
   * yc3z2TY3ufmaur92zZIDKdnMPD86dPeNTDUTmzm9nHVLkWzrpmpyM+n8stCNzGYW
   * JvDXXFM10x8IWkTOiiO0O7upmtxMXc1yGQo9vHXzlsM+7Z+dOEI7lZnHvB+l1FVz
   * ZzdV8/rZcPMZdX+dgFdydOVErZOT5SrMLD/7rvz8vVsbRDVzDyJjRvW4We7m3bNt
   * M7h5PHytA25iV+LWj8QrTyipnDP56Yp6y7t5PHk1Wm6CmYtXc9TlR6bxUzVVM2k1
   * t3cTK8r5ejWrH7jQvQ5+qiZedvOdanITzExazbv+VMhWP7xVTaxczeirGzehmrn8
   * bD8wU6NodjlVk5vD52vcBDMfG8y2+jno+pRRsZSgamJBN+s94CaYeSSR8/7As/Js
   * gxhos2qCm5u7udDeutG7zLRm9vC3yNKoe0z7s9eyjpvHgHNdC908lnZzrCojlut7
   * bAHqBF+azm5mjJG8j8OTn4kwdK/j5ibV7LijV5w/1PXbL3uEc5CZ7XdYcfE8Zj6w
   * I0fbbWMtOWesJjdnqWb5o3eZm+4VzoqVb3+tuvfnCq4la+ReLpz72ALUyeRcrZq9
   * DsGsFM4Rq1JzUzULH73v+UDrDWrvDR7vrlJbsnTt5c3fWoCamaN34XhgwjqnnL1W
   * peamatZV84lHXPtQ7fliCMfvhYf+LLN1d43cLmZeWneyKNjJMvfMjAT3MFLOSGIc
   * N7m5ZjXndPNayJKVb4/T5fpeMfNYYQFq1ewazqkGtr1Wpeamat59rk9ffmGlqwiV
   * r3yb08xCzRIvQD2BmfHSbSuHo/PI2WtVam6qZp5qHsufH3TrGMv5v3zd4xvNfHIB
   * 6my/GdV8dtIZ79rHTW4uU82LQe0Mbl7vneUr35aYeRSvkfvLzJ0WoK7dUZJcvT3H
   * zt7poFA8rx43ublqNad2EzOSzsx45Cb9n97ta2Mmqia4OeK5RpLtldhNrCjnGweC
   * YtgPD396KeVUTW6uPdGc100Y0vZ8TpF47+50ia+Hnr5qcnP5ieakbsKQdogAkXW/
   * bj6FTzWx7HQz6VdIk7kJQ9qH+pTqm8kN30tRTSw43YzHD0p9HV63DWopgyXkjMRX
   * 8qg69101sVQ44+0zgEqH12ncBDNtu27/p5rgZt2mKe13AjdBTltNNcHNJBtFNZHJ
   * zJV2q7izTv3thexVE9xUTZBzmT0rCn4gqj+z8bkmuJmzmj7XxCtmLrBzxdC7Vk1w
   * M6G8zgaCIe2bySw8tvuenKrJzU2rmd5NMHOnZPY50V41wc3em2MeN7HLAG6BE/Za
   * PxGJLM9FNbm5mJtNm2M2N7HjkDYWfHnPezR2K6omN7fYFhO6iR3NnHG3Uk1wc82J
   * pmrCkDaXB5HxSakmNzecbsYSbsKQ1kRTNcHN4a8/VnET5DTRzCinanJz8Vc+rZtg
   * pmqqJrj54BRTNUHO6asZ6cqsmtyc8eSDG1cmmNZNMHPvZP6vk/Xfs+6/FVWTm3Od
   * eRDbuAlyZt+tOlezZDAc75upmtw8Fj4UNLOb2NLMY6Yz3btdEujWWPVtOVWTm8fa
   * H6BM6yaYmX23ii53EaMfWDWxnZsdnuSEboKc2fesePex451nqprcnOhdP3ZyE8zc
   * uppRuHSRauI9N4+1P0Z54oHZhJ3CGcPu9sYJeqUD225PVjW5OV04o/e9RUo3wcwd
   * p5tRJ+CDq8mrJjdnfOOvWUm6TLK4vGZCPPwuAnLuNN2M6lGzasJ0c8wENO5/jfMV
   * N7G9mccEZ7rHG/dWFU7VxCg3533vL89h6+Z7xE2Qc7vppmrCdDPhq432O1RNvCLn
   * 2tWcZTyrmtycaFDb/mr7fGlTNcHMvs9qIjNVk5vrVfPoeKW8r7dSTZhu9t3xVRPc
   * zPmCVRMTyjmJmfHuDZ1Di1fdXHW6GfO4CWbOZ2bFWQPR6+FUE9wc8IJVE6abw19J
   * 9Psx1YRwvvuCVRPMfLmdMei026c+OFFNbq5dzf9eRTzx6QmbMMjMY9ZViqLHpbyK
   * NplqgpuPvux0boKc65upmuAmNwFD2se314MfnKgmN7nZKZnexsDMDQazqml3K/mR
   * sLFMNCGcJpqqaY/jpokmmKmaqgnTTdWEcDJTNcHNFzeQaiKFnNsPad/9ArVqcpOb
   * ny9TMjGTmVvtgK8fAlJNbqpm0WtUTQjnbBNN1YRw5kymauJhMzcOp2piOjdjw22h
   * mkhn5rHj4aBIoKVqcpObkglyqqZqgpudNoRqYiIz194fVRPcnGBbqCbymrnZdFM1
   * wc0JNodq4r290WlBqgluzhZO1UT2ge2e5+ypJripmkDNHrbtme5vfI1aNdHipnCq
   * Jgxp0043VRPcVE2QUzWLXuSoDaCa4GZON4GmIS0zVRPcVE0wk5mqCW5yE2iXc7eP
   * T1764EQ10ejm+tX0oSZUc7bBrGpCNVUTzLz5I6qpmkjo5vrn0KomVJOZqgluqiaW
   * k5OZqgluqibAzJtmDnzpqglu5nQTYKZqgpvcBJjZ5cWrJripmkCTnPtU870PTlQT
   * 3PzzWnyoCWZOPdFUTXDzvdesmmDmbMlUTXAzZzJVE8zcLJmqCW5KJpi5UjJVE9zM
   * mUzVBDP3S6ZqgpuSiYXMPJa63GW2Y7OqiV5ujthZ498/yarJGhjSbjnRVE2kdTMe
   * kcFEE8yUTNXEC25232XjESUyuwnU7oKThzMqjVNN7OtmPGJFcjeBPmbOtc/m11I1
   * kdDNF6opmSDntMlUTew+3XygmpHeTaBtR5wwnFMc/1FN9HUzRj5q7OQm0Lwvfgtn
   * zPKSsmqpmujuZqOYMVgPycTW4eyyL/f9TlhMpaVqItugNoZJEpIJct71M779+fq/
   * R+0dHlNpqZpI4mb5D0ez6ZKJneSMyinj3ZFvdLnD9FqqJh5zs72XJT8fnb91SRAs
   * LeeNYWZtQaO8lzOcmKeaeMzN+P3nOTHncRPouo+O/RyyR2CPObRUTXBTMrGHnEfb
   * SPXPbXtNSMe9WNUEN3dyExi5s946QHP5kcjdI0f1Vr+vpWqCm5KJLeU8savlI5ST
   * G7Z+ZJpCS9UENyUTe5s59En1Ob8o0eUZVBPczOkmYLiX8XWpJrhpiomVduKgpWqC
   * m5IJbLg3Z3whqgluSiYMbD1/1QQ395w0A3/37/CEVRN29c2eMLD4Hj+NkKoJbgK7
   * KeopqSa4qZfA7eFtFC+uUPin4gnMtu1UE9wEuDrg6iPdF2xQTXCTmwBUEwAA1QQA
   * QDUBAIBqAgCgmgAAqCYAAKoJAIBqAgCgmgAAQDUBAFBNAABUEwAA1QQAQDUBAFBN
   * AABUUzUBAFBNAABUEwAA1QQAQDUBAFBNAABUEwAAqCYAAKoJAIBqAgCgmgAAqCYA
   * AKoJAABUEwAA1QQAQDUBAFBNAABUEwAA1QQAQDUBAIBqAgCgmgAAqCYAAKoJAIBq
   * AgCgmgAAQDUBAFBNAABUEwAA1QQAQDUBAFBNAABUEwAAqCYAAKoJAIBqAgCgmgAA
   * qCYAAKoJAABUEwAA1QQAQDUBAFBNAABUEwAA1QQAQDUBAIBqAgCgmgAAqCYAAKoJ
   * AIBqAgCgmgAAQDUBAFBNAABUEwAA1QQAQDUBAFBNAABU00YAAEA1AQBQTQAAVBMA
   * ANUEAEA1AQBQTQAAoJoAAKgmAACqCQCAagIAoJoAAKgmAABQTQAAVBMAANUEAEA1
   * AfzD/wFqNHlO4MJ0zwAAAABJRU5ErkJggg==" />
   * 
   * @author p.baniukiewicz
   * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double, double)
   * @see OutlineProcessor#amplificationFactor(double, double, double)
   */
  public static class Contour extends PropagateSeeds {

    /**
     * Scale magnification. Used by {@link PropagateSeeds.Contour} only.
     * 
     * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double,
     *      double)
     */
    public double scaleMagn = 5;
    /**
     * Sigma. Used by {@link PropagateSeeds.Contour} only.
     * 
     * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double,
     *      double)
     */
    public double scaleSigma = 0.3;
    /**
     * Distance to set normals equal. Used by {@link PropagateSeeds.Contour} only.
     * 
     * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double,
     *      double)
     * @see Contour#Contour(boolean, ij.process.AutoThresholder.Method, double, double, double,
     *      double)
     */
    public double averageNormalsDist = 0;
    /**
     * Distance to average curvature over. This is multiplier of average node distance. Always at
     * least three nodes are averaged (n-1,n,n+1).
     * 
     * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double,
     *      double)
     * @see Contour#Contour(boolean, ij.process.AutoThresholder.Method, double, double, double,
     *      double)
     */
    public double averageCurvDist = 1.0;
    /**
     * Use or not extra filtering defined in {@link TrackOutline}.
     */
    public boolean useFiltering = false;

    /**
     * Step size during object outline shrinking.
     * 
     * @see Outline#scaleOutline(double, double, double, double)
     * @see ANAp
     */
    public static final double stepSize = 0.04;

    /**
     * Default constructor without storing seed history.
     */
    public Contour() {
      this(false, null);
    }

    /**
     * Allow to store seed history that can be later presented in form of composite image.
     * 
     * @param storeSeeds <tt>true</tt> to store seeds.
     * @param trueBackground if not null, selected method will be used for estimating true
     *        background - excluding bright objects from it
     * @see #getCompositeSeed(ImagePlus, int)
     */
    public Contour(boolean storeSeeds, AutoThresholder.Method trueBackground) {
      super(storeSeeds, trueBackground);
    }

    /**
     * Allow additionally to set power of shrinking curved parts of the outline and window for
     * equalising normals.
     * 
     * @param storeSeeds <tt>true</tt> to store seeds.
     * @param trueBackground if not null, selected method will be used for estimating true
     *        background - excluding bright objects from it
     * @param sigma sigma of Gaussian
     * @param scaleMagn maximum amplification of scaling index (for curv<<0). 1.0 causes linear
     *        shrink.
     * @param averageCurvDist distance to average curvature over. At any case at least 3 nodes are
     *        taken (current, previous and next). See {@link OutlineProcessor}. This parameter plays
     *        as multiplier of average node distance. Setting it to e.g. 5, likely 5 nodes will be
     *        takes in total.
     * @param averageNormalsDist distance to look along for node with smallest curvature (negative)
     *        from current node (forward and backward). Set 0 to turn off this feature. See
     *        {@link OutlineProcessor} and description of previous parameter.
     * @see Contour#Contour(boolean, ij.process.AutoThresholder.Method)
     * @see OutlineProcessor#shrinknl(double, double, double, double, double, double, double,
     *      double)
     */
    public Contour(boolean storeSeeds, AutoThresholder.Method trueBackground, double sigma,
            double scaleMagn, double averageCurvDist, double averageNormalsDist) {
      super(storeSeeds, trueBackground);
      this.scaleMagn = scaleMagn;
      this.scaleSigma = sigma;
      this.averageNormalsDist = averageNormalsDist;
      this.averageCurvDist = averageCurvDist;
    }

    /**
     * Generate seeds for next frame using provided mask.
     * 
     * <p>The mask provided to this method is shrunk to get new seeds of object (that can move
     * meanwhile). The same mask is expanded and subtracted from image forming the background.
     * 
     * <p>Setting <tt>shrinkPower</tt> or <tt>expandPower</tt> to zero prevents contour
     * modifications.
     * 
     * <p>Algorithm can utilise object colour to process them in groups within one colour. Objects
     * in one group are processed separately, but then all are stored in the same {@link Seeds}
     * {@link SeedTypes#FOREGROUNDS} map.
     * 
     * @param previous Previous result of segmentation. BW mask with white object on black
     *        background.
     * @param org original image that new seeds are computed for. Usually it is current image
     * @param shrinkPower Shrink size for objects in pixels.
     * @param expandPower Expand size used to generate background (object is expanded and then
     *        subtracted from background)
     * @return List of background and foreground coordinates.
     * @see PropagateSeeds.Morphological#propagateSeed(ImageProcessor, ImageProcessor, double,
     *      double)
     * @see Outline#scaleOutline(double, double, double, double)
     * @see #getTrueBackground(ImageProcessor, ImageProcessor)
     * @see #setTrueBackgroundProcessing(ij.process.AutoThresholder.Method)
     */
    @Override
    public Seeds propagateSeed(ImageProcessor previous, ImageProcessor org, double shrinkPower,
            double expandPower) {
      double stepsshrink = shrinkPower / stepSize; // total shrink/step size
      double stepsexp = (expandPower) / stepSize; // total shrink/step size
      Seeds ret = new Seeds(2);

      // this supports grayscales;
      List<Pair<Outline, Color>> oc = getPairsOutlineAndColors(previous, useFiltering);
      // sort according to colors, need to collect outlines with the same object color in the same
      // map
      Collections.sort(oc, new Comparator<Pair<Outline, Color>>() {

        @Override
        public int compare(Pair<Outline, Color> o1, Pair<Outline, Color> o2) {
          Color c1 = o1.getRight();
          Color c2 = o2.getRight();
          Integer ci1 = new Integer(c1.getRed() + c1.getBlue() + c1.getGreen());
          Integer ci2 = new Integer(c2.getRed() + c2.getBlue() + c2.getGreen());
          return ci1.compareTo(ci2);
        }
      });

      Iterator<Pair<Outline, Color>> oci = oc.iterator();

      // save extra debug info if property set
      if (QuimP.SUPER_DEBUG) {
        String tmp = System.getProperty("java.io.tmpdir");
        for (Pair<Outline, Color> o : oc) {
          long time = new Date().getTime();
          RoiSaver.saveRoi(tmp + File.separator + "propagateSeed_" + time + "_" + oc.hashCode(),
                  o.getLeft().asList());
        }
      }
      int cprev = -1; // previous object color, IT CANT EXIST IN LIST
      ByteProcessor small = null;
      while (oci.hasNext()) {
        Pair<Outline, Color> p = oci.next();
        Outline o = p.getLeft();
        Color c = p.getRight();
        // restore int (see TrackOutline.getColors())
        int color = c.getRed() + c.getBlue() + c.getGreen();
        if (o.getNumPoints() < 4) {
          continue;
        }
        if (color != cprev) { // if new color, create new processor (list is sorted)
          small = new ByteProcessor(previous.getWidth(), previous.getHeight());
        }
        small.setColor(Color.WHITE); // for fill(Roi)
        // shrink outline - copy as we want to expand it later
        Outline copy = new Outline(o);
        LOGGER.debug("Shrink object");
        new OutlineProcessor<Outline>(copy).shrinknl(stepsshrink, stepSize, 0.1, 1.5, scaleSigma,
                scaleMagn, averageCurvDist, averageNormalsDist);
        copy.unfreezeAll();
        Roi fr = copy.asIntRoi();
        fr.setFillColor(Color.WHITE);
        fr.setStrokeWidth(1.1);
        fr.setStrokeColor(Color.WHITE);
        small.fill(fr);
        small.drawRoi(fr);
        // small.resetRoi();
        if (color != cprev) { // store only new, old will be updated by reference
          ret.put(SeedTypes.FOREGROUNDS, small);
        }
        cprev = color; // for next iter
      }

      ByteProcessor big = new ByteProcessor(previous.getWidth(), previous.getHeight());
      // big.setColor(Color.BLACK);
      // big.fill();
      for (Pair<Outline, Color> p : oc) {
        Outline o = p.getLeft();
        if (o.getNumPoints() < 4) {
          continue;
        }
        // frezeTh influences artifacts that appear when concave regions are expanded
        // 0 prevent a little
        LOGGER.debug("Expand object");
        new OutlineProcessor<Outline>(o).shrinknl(stepsexp, -stepSize, 0.1, 0, scaleSigma,
                scaleMagn, 1.0, 0);
        o.unfreezeAll();
        Roi fr = o.asFloatRoi();
        fr.setFillColor(Color.WHITE);
        fr.setStrokeColor(Color.WHITE);
        big.drawRoi(fr);
      }
      big.invert();
      // store seeds if option ticked
      ret.put(SeedTypes.BACKGROUND, getTrueBackground(big, org));
      if (storeSeeds) {
        seeds.add(ret);
      }

      return ret;

    }

    /**
     * Convert mask to outline.
     * 
     * @param previous image to be converted outline. White object on black background.
     * @param filtering if true enable default filtering from {@link TrackOutline} class
     * @return List of Outline for current frame and colors
     * @see TrackOutline
     * @see #getPairsOutlineAndColors(ImageProcessor, boolean)
     */
    public static Pair<List<Outline>, List<Color>> getOutlineAndColors(ImageProcessor previous,
            boolean filtering) {
      TrackOutline track = null;
      if (filtering) {
        track = new TrackOutline(previous, 0);
      } else {
        track = new TrackOutlinNoFilter(previous, 0);
      }
      return track.getOutlinesColors(STEPS, false);
    }

    /**
     * Convert mask to outline.
     * 
     * @param previous image to be converted outline. White object on black background.
     * @param filtering if true enable default filtering from {@link TrackOutline} class
     * @return List of Outline for current frame and colors enclosed in pairs
     * @see TrackOutline
     * @see #getOutlineAndColors(ImageProcessor, boolean)
     */
    public static List<Pair<Outline, Color>> getPairsOutlineAndColors(ImageProcessor previous,
            boolean filtering) {
      TrackOutline track = null;
      if (filtering) {
        track = new TrackOutline(previous, 0);
      } else {
        track = new TrackOutlinNoFilter(previous, 0);
      }
      return track.getPairs(STEPS, false);
    }

  }

  /**
   * Contain methods for propagating seeds to next frame using morphological operations.
   * 
   * @author p.baniukiewicz
   *
   */
  public static class Morphological extends PropagateSeeds {

    /**
     * Default constructor without storing seed history.
     */
    public Morphological() {
      this(false, null);
    }

    /**
     * Allow to store seed history that can be later presented in form of composite image.
     * 
     * @param storeSeeds <tt>true</tt> to store seeds.
     * @param trueBackground if not null, selected method will be used for estimating true
     *        background - excluding bright objects from it
     * @see #getCompositeSeed(ImagePlus, int)
     */
    public Morphological(boolean storeSeeds, AutoThresholder.Method trueBackground) {
      super(storeSeeds, trueBackground);
    }

    /**
     * Generate new seeds using segmented image.
     * 
     * <p>Setting <tt>shrinkPower</tt> or <tt>expandPower</tt> to zero prevents contour
     * modifications.
     * 
     * @param previous segmented image, background on zero
     * @param org original image that new seeds are computed for. Usually it is current image
     * @param shrinkPower number of erode iterations
     * @param expandPower number of dilate iterations
     * 
     * @return Map containing list of coordinates that belong to foreground and background. Map is
     *         addressed by two enums: <tt>FOREGROUND</tt> and <tt>BACKGROUND</tt>
     * @see SeedProcessor#decodeSeedsfromRgb(ImagePlus, List, Color)
     * @see #getTrueBackground(ImageProcessor, ImageProcessor)
     * @see #setTrueBackgroundProcessing(ij.process.AutoThresholder.Method)
     */
    @Override
    public Seeds propagateSeed(ImageProcessor previous, ImageProcessor org, double shrinkPower,
            double expandPower) {
      ImageProcessor cp = previous.duplicate();
      // object smaller than on frame n
      ImageProcessor small = cp.duplicate();
      // object bigger than on frame n
      ImageProcessor big = cp.duplicate();
      // store seeds if option ticked
      Seeds ret = new Seeds(2);
      // need to process each object separately to preserve multi fg seeds.
      Seeds decodedSeeds;
      try {
        decodedSeeds = SeedProcessor.decodeSeedsfromGrayscaleImage(cp);
        if (decodedSeeds.get(SeedTypes.FOREGROUNDS) == null) {
          throw new RandomWalkException("no FG maps");
        }
        for (ImageProcessor ip : decodedSeeds.get(SeedTypes.FOREGROUNDS)) {
          // make objects smaller
          small = BinaryFilters.iterateMorphological(ip, MorphoOperations.ERODE, shrinkPower);
          ret.put(SeedTypes.FOREGROUNDS, small);
        }

        // make background bigger but for all, convert to binary first
        big.threshold(0);
        big = BinaryFilters.iterateMorphological(big, MorphoOperations.DILATE, expandPower);
        cp.threshold(0); // input also must be binary (after computing FG)

        // apply big to old background making object bigger and prevent covering objects on
        // frame
        // n+1
        // by previous background (make "empty" not seeded space around objects)
        // IJ.saveAsTiff(new ImagePlus("", big), "/tmp/testIterateMorphological_bigbef.tif");
        // IJ.saveAsTiff(new ImagePlus("", cp), "/tmp/testIterateMorphological_cp.tif");
        for (int x = 0; x < cp.getWidth(); x++) {
          for (int y = 0; y < cp.getHeight(); y++) {
            big.putPixel(x, y, big.getPixel(x, y) | cp.getPixel(x, y));
          }
        }

        big.invert(); // invert to have BG pixels white in seed. (required by convertToList)
        ret.put(SeedTypes.BACKGROUND, getTrueBackground(big, org));
        if (storeSeeds) {
          seeds.add(ret);
        }
      } catch (RandomWalkException e) { // from decodeseeds - no FG seeds
        // this is handled to console only as return is still valid (no FG seeds in output)
        LOGGER.debug("Empty seeds. " + e.getMessage());
      }
      return ret;
    }

  }

  /**
   * Produce composite image containing seeds generated during segmentation of particular frames.
   * 
   * <p>To have this method working, the Contour object must be created with storeSeeds==true.
   * 
   * @param org Original image (or stack) where composite layer will be added to.
   * @param offset Slice number to display in composite if there is stack provided. Ignored if org
   *        is single image. Set it to 0 to dispplay whole stack.
   * @return Composite image with marked foreground and background.
   * @throws RandomWalkException When seeds were not collected.
   */
  public ImagePlus getCompositeSeed(ImagePlus org, int offset) throws RandomWalkException {
    ImagePlus ret = null;
    try {
      ImageStack smallstack =
              new ImageStack(seeds.get(0).get(SeedTypes.FOREGROUNDS).get(0).getWidth(),
                      seeds.get(0).get(SeedTypes.FOREGROUNDS).get(0).getHeight());
      ImageStack bigstack =
              new ImageStack(seeds.get(0).get(SeedTypes.FOREGROUNDS).get(0).getWidth(),
                      seeds.get(0).get(SeedTypes.FOREGROUNDS).get(0).getHeight());

      for (Seeds p : seeds) {
        // just in case convert to byte
        ImageProcessor fg = SeedProcessor.flatten(p, SeedTypes.FOREGROUNDS, 1).convertToByte(true);
        fg.threshold(0); // need 255 not real value of map
        // why duplicate here? without returned map is at 128???????????????? Seems that
        // converToByte returns duplicate only if input is not 8-bit
        // (PropagateSeedsTest.testGetCompositeSeed_Morphological()
        ImageProcessor bg = p.get(SeedTypes.BACKGROUND, 1).duplicate().convertToByte(true);
        bg.threshold(0); // need 255 not real value of map
        // make colors transparent
        bg.multiply(colorScaling);
        fg.multiply(colorScaling);
        // set gray lut just in case
        fg.setLut(IJTools.getGrayLut());
        bg.setLut(IJTools.getGrayLut());
        smallstack.addSlice((ImageProcessor) fg);
        bigstack.addSlice((ImageProcessor) bg);
      }
      // check if stack or not. getComposite requires the same type
      if (org.getStack().getSize() == 1) { // single image
        ret = IJTools.getComposite(org.duplicate(), new ImagePlus("", smallstack.getProcessor(1)),
                new ImagePlus("", bigstack.getProcessor(1)));
      } else {
        if (offset > 0) { // stack but show only one image
          ImageProcessor tmp = org.getStack().getProcessor(offset).duplicate();
          ret = IJTools.getComposite(new ImagePlus("", tmp), new ImagePlus("", smallstack),
                  new ImagePlus("", bigstack));
        } else { // stack
          ret = IJTools.getComposite(org.duplicate(), new ImagePlus("", smallstack),
                  new ImagePlus("", bigstack));
        }
      }
    } catch (NullPointerException | IndexOutOfBoundsException e) {
      throw new RandomWalkException("Problem with showing seeds. Seeds were not stored. "
              + "You need at least two time frames to collect one seed or "
              + "segmentation returned empty image.");
    }
    return ret;
  }

  /**
   * Propagate seed.
   * 
   * <p>Each separated object found in <tt>previous</tt> is returned as separate BW map in
   * {@link Seeds} under {@link SeedTypes#FOREGROUNDS} map. <tt>previous</tt> can contain grayscale
   * labelled objects.
   *
   * @param previous the previous
   * @param org original image that new seeds are computed for. Usually it is current image
   * @param shrinkPower the shrink power
   * @param expandPower the expand power
   * @return the map
   * @see #getTrueBackground(ImageProcessor, ImageProcessor)
   * @see #setTrueBackgroundProcessing(ij.process.AutoThresholder.Method)
   */
  public abstract Seeds propagateSeed(ImageProcessor previous, ImageProcessor org,
          double shrinkPower, double expandPower);

  /**
   * Excludes objects from estimated background.
   * 
   * <p>If seed propagator is used, background is obtained by expanding initially segmented cell and
   * then negating the image. Thus background covers all area except cell. If there are other cell
   * there they can influence background mean. To avoid this, that background is thresholded to
   * detect objects that should be excluded from mean. This should be used when there are other
   * objects around.
   * 
   * @param bck Background (white) estimated from Propagator
   * @param org Original 8-bit image
   * @return Background without objects above threshold
   * @see #setTrueBackgroundProcessing(ij.process.AutoThresholder.Method)
   */
  ImageProcessor getTrueBackground(ImageProcessor bck, ImageProcessor org) {
    if (thresholdMethod == null) {
      return bck;
    }
    ImageProcessor orgD = org.duplicate();
    orgD.threshold(new AutoThresholder().getThreshold(thresholdMethod, orgD.getHistogram()));
    orgD.invert();
    orgD.copyBits(bck, 0, 0, Blitter.AND); // cut
    return orgD;
  }

  /**
   * Turn on processing background before using it as seed.
   * 
   * @param method Threshold method. null for turning off processing
   */
  public void setTrueBackgroundProcessing(AutoThresholder.Method method) {
    thresholdMethod = method;
  }

}

/**
 * Disabled preliminary filtering default for {@link TrackOutline}.
 * 
 * <p>In purpose of overriding {@link TrackOutline#prepare()} which in super class can remove thin
 * lines.
 * 
 * @author p.baniukiewicz
 *
 */
class TrackOutlinNoFilter extends TrackOutline {

  /**
   * Default constructor here.
   * 
   * @param imp image to process
   * @param background background color
   */
  public TrackOutlinNoFilter(ImageProcessor imp, int background) {
    super(imp, background);
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.github.celldynamics.quimp.geom.TrackOutline#prepare()
   */
  @Override
  public ImageProcessor prepare() {
    ImageProcessor filtered = imp.duplicate();
    return filtered;
  }

}