//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
//
#ifndef STACKPROCESSMORPHOLOGY_HPP
#define STACKPROCESSMORPHOLOGY_HPP

#include <Process.hpp>

namespace mgx
{
  ///\addtogroup StackProcess
  ///@{
  /**
   * \class EdgeDetectProcess StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * TODO: This class needs documenting!
   */
  class mgxBase_EXPORT EdgeDetectProcess : public Process 
  {
  public:
    EdgeDetectProcess(const Process& process) : Process(process)
    {
       setName("Stack/Morphology/Edge Detect");
       setDesc("Do a multipass edge detection in Z direction.\n"
       "Stack is turned into a mask (0 or fill value)");
       setIcon(QIcon(":/images/EdgeDetect.png"));

       addParm("Threshold","Values of signal that should not belong to surface.","10000.0");
       addParm("Multiplier","Multiplicative factor for the threshold.","2.0");
       addParm("Adapt Factor","Adaptative factor for threshold.","0.3");
       addParm("Fill Value","Value to fill the mask with.","30000");
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY | STORE_NON_LABEL))
        return false;
      Stack* stack = currentStack();
      Store* store = stack->currentStore();
      Store* work = stack->work();
      bool res = run(store, work, parm("Threshold").toFloat(), parm("Multiplier").toFloat(),
                     parm("Adapt Factor").toFloat(), parm("Fill Value").toUInt());
      if(res) {
        store->hide();
        work->show();
      }
      return res;
    }

    bool run(Store* input, Store* output, float threshold, float multiplier,
            float factor, uint fillValue);

  };

  /**
   * \class DilateStack StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Dilate the gray-level stack using a cubic kernel.
   *
   * During dilation, a new image is created in which each voxel is replaced by
   * the maximum value within a given neighborhood around the voxel. In
   * MorphoGraphX, this neighborhood is defined as a cube, centered on the
   * voxel, and of size given by the user. The radius is then half the length
   * of the corresponding edge on the cube.
   */
  class mgxBase_EXPORT DilateStack : public Process
  {
  public:
    DilateStack(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Dilate");
      setDesc("Morphological dilation (max filter) on stack");
      setIcon(QIcon(":/images/Dilate.png"));

      addParm("X Radius","X Radius","1");
      addParm("Y Radius","Y Radius","1");
      addParm("Z Radius","Z Radius","1");
      addParm("Auto-Resize","Auto-Resize","No",booleanChoice());
      addParm("Round Nhbd","Use a round (Ellipsoidal) neighborhood","No",booleanChoice());
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY))
        return false;
      Stack* s = currentStack();
      Store* input = s->currentStore();
      Store* output = s->work();
      bool res = run(s, input, output, parm("X Radius").toUInt(), parm("Y Radius").toUInt(), parm("Z Radius").toUInt(),
                         stringToBool(parm("Auto-Resize")), stringToBool(parm("Round Nhbd")));
      if(res) {
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output, uint xradius,
           uint yradius, uint zradius, bool auto_resize = true, bool roundNhbd = false);

  };

  /**
   * \class ErodeStack StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Erode the gray-level stack using a cubic kernel.
   *
   * During dilation, a new image is created in which each voxel is replaced by
   * the minimum value within a given neighborhood around the voxel. In
   * MorphoGraphX, this neighborhood is defined as a cube, centered on the
   * voxel, and of size given by the user. The radius is then half the length
   * of the corresponding edge on the cube.
   */
  class mgxBase_EXPORT ErodeStack : public Process
  {
  public:
    ErodeStack(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Erode");
      setDesc("Morphological erosion on stack");
      setIcon(QIcon(":/images/Erode.png"));

      addParm("X Radius","X Radius","1");
      addParm("Y Radius","Y Radius","1");
      addParm("Z Radius","Z Radius","1");
      addParm("Auto-Resize","Auto-Resize","No",booleanChoice());
      addParm("Round Nhbd","Use a round (Ellipsoidal) neighborhood","No",booleanChoice());
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY))
        return false;
      Stack* s = currentStack();
      Store* input = s->currentStore();
      Store* output = s->work();
      bool res = run(s, input, output, parm("X Radius").toUInt(), parm("Y Radius").toUInt(), parm("Z Radius").toUInt(),
                         stringToBool(parm("Auto-Resize")), stringToBool(parm("Round Nhbd")));
      if(res) {
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output, uint xradius,
            uint yradius, uint zradius, bool auto_resize = true, bool roundNhbd = false);

  };

  /**
   * \class ClosingStack StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Close the gray-level stack using a cubic kernel.
   *
   * A morphological closing is a dilation, followed by an erosion using the same kernel.
   * The result is that any gap smaller than the kernel will be filled. This
   * will work both for holes and for gaps between separated objects.
   */
  class mgxBase_EXPORT ClosingStack : public Process
  {
  public:
    ClosingStack(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Closing");
      setDesc("Morphological closure (i.e. dilatation followed erosion) on stack");
      setIcon(QIcon(":/images/Closing.png"));

      addParm("X Radius","X Radius","1");
      addParm("Y Radius","Y Radius","1");
      addParm("Z Radius","Z Radius","1");
      addParm("Round Nhbd","Use a round (Ellipsoidal) neighborhood","No",booleanChoice());
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY))
        return false;
      Stack* s = currentStack();
      Store* input = s->currentStore();
      Store* output = s->work();
      bool res = run(s, input, output, parm("X Radius").toUInt(), parm("Y Radius").toUInt(),
                                         parm("Z Radius").toUInt(), stringToBool(parm("Round Nhbd")));
      if(res) {
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output,
            uint xradius, uint yradius, uint zradius, bool roundNhbd);

  };

  /**
   * \class OpeningStack StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Open the gray-level stack using a cubic kernel.
   *
   * A morphological opening is an erosion, followed by a dilation using the same kernel.
   * The result is that any part on an object narrower than the kernel will be
   * erased. As a consequence, any object smaller than the kernel will be
   * completly deleted.
   */
  class mgxBase_EXPORT OpeningStack : public Process
  {
  public:
    OpeningStack(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Opening");
      setDesc("Morphological opening (i.e. erosion followed dilatation) on stack");
      setIcon(QIcon(":/images/Opening.png"));

      addParm("X Radius","X Radius","5");
      addParm("Y Radius","Y Radius","5");
      addParm("Z Radius","Z Radius","5");
      addParm("Round Nhbd","Use a round (Ellipsoidal) neighborhood","No",booleanChoice());
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY))
        return false;
      Stack* s = currentStack();
      Store* input = s->currentStore();
      Store* output = s->work();
      bool res = run(s, input, output, parm("X Radius").toUInt(), parm("Y Radius").toUInt(),
                                         parm("Z Radius").toUInt(), stringToBool(parm("Round Nhbd")));
      if(res) {
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output, uint xradius,
            uint yradius, uint zradius, bool roundNhbd);

  };

  class mgxBase_EXPORT FillHolesProcess : public Process
  {
  public:
    FillHolesProcess(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Fill Holes");
      setDesc("Fill holes in stack.\n"
      "Use after Edge Detect.");
      setIcon(QIcon(":/images/FillHoles.png"));

      addParm("X Radius","X Radius of hole","10");
      addParm("Y Radius","Y Radius of hole","10");
      addParm("Threshold","Minimal signal value to fill the hole.","10000");
      addParm("Depth","Depth, when set it uses closest pixel at least this much higher within the radius.","0");
      addParm("Fill Value","Filling value. Usually same as Edge Detect.","30000");
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY | STORE_NON_LABEL))
        return false;
      Stack* s = currentStack();
      Store* input = s->currentStore();
      Store* output = s->work();
      bool res = run(input->data(), output->data(), Point3i(s->size()), parm("X Radius").toInt(), parm("Y Radius").toInt(), 
                     parm("Threshold").toInt(), parm("Depth").toInt(), parm("Fill Value").toInt());
      if(res) {
        output->copyMetaData(input);
        output->changed();
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(const HVecUS &input, HVecUS &output, const Point3i &size,
                                       int xradius, int yradius, int threshold, int depth, int fillValue);

  };

  /**
   * \class OpenStackLabel StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Open a labeled stack.
   *
   * This process perform a binary opening on each label, independently. As the
   * result of an opening is always within the original object, this is a
   * well-defined operation in the case of multiple label.
   */
  class mgxBase_EXPORT OpenStackLabel : public Process
  {
  public:
    OpenStackLabel(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Open by label");
      setDesc("Morphological opening on a labeled stack");
      setIcon(QIcon(":/images/Opening.png"));

      addParm("X Radius","X Radius","1");
      addParm("Y Radius","Y Radius","1");
      addParm("Z Radius","Z Radius","1");
      addParm("Round Nhbd","Use a round (Ellipsoidal) neighborhood","No",booleanChoice());
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY | STORE_LABEL))
        return false;
      Stack* s = currentStack();
      Store* input = s->currentStore();
      Store* output = s->work();
      bool res = run(s, input, output,
             parm("X Radius").toUInt(), parm("X Radius").toUInt(), parm("X Radius").toUInt(), stringToBool(parm("Round Nhbd")));
      if(res) {
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output,
                         uint xradius, uint yradius, uint zradius, bool roundNhbd);

  };

  /**
   * \class CloseStackLabel StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Close a labeled stack.
   *
   * This process perform a binary closing on each label, independently. As the
   * result of a closing is almost always larger than the original object, the
   * result will depend on the order on which the closing is performed. As
   * such, the result is not well-defined if the labels are too close to each
   * other.
   */
  class mgxBase_EXPORT CloseStackLabel : public Process
  {
  public:
    CloseStackLabel(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Close by label");
      setDesc("Morphological closure on a labeled stack");
      setIcon(QIcon(":/images/Closing.png"));

      addParm("X Radius","X Radius","1");
      addParm("Y Radius","Y Radius","1");
      addParm("Z Radius","Z Radius","1");
      addParm("Round Nhbd","Use a round (Ellipsoidal) neighborhood","No",booleanChoice());  // 3
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY | STORE_LABEL))
        return false;
      Stack* s = currentStack();
      Store* input = s->currentStore();
      Store* output = s->work();
      bool res = run(s, input, output,
              parm("X Radius").toUInt(), parm("Y Radius").toUInt(), parm("Z Radius").toUInt(), stringToBool(parm("Round Nhbd")));
      if(res) {
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output,
               uint xradius, uint yradius, uint zradius, bool roundNhbd);

  };

  /**
   * \class ApplyMaskToStack StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Apply a mask to a grey-level image. The image must be in the main store,
   * while the mask must be in the work store.
   *
   * The operations are:
   *
   * 1. Normal. Keep any value for which the mask is greater than the threshold. Any
   * other value is set to 0.
   * 2. Invert. Keep any value for which the mask is less than or equal to the
   * threshold. Any other value is set to 0.
   * 3. Combine. If the value in the mask is less than or equal to the
   * threshold, the value is replaced by the one of the image. Otherwise, the
   * value is the one in the mask.
   */
  class mgxBase_EXPORT ApplyMaskToStack : public Process
  {
  public:
    ApplyMaskToStack(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Apply Mask to Stack");
      setDesc("Apply the work mask to the main replacing work");
      setIcon(QIcon(":/images/Mask.png"));

      addParm("Mode","Mode","Normal", QStringList() << "Normal" << "Invert" << "Combine");
      addParm("Threshold","Threshold","0");
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY))
        return false;
      Stack* stack = currentStack();
      Store* input = stack->main();
      Store* output = stack->work();
      bool res = run(stack, input, output, parm("Mode"), parm("Threshold").toUInt());
      if(res) {
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output,
             const QString& mode, uint threshold);

  };

  /**
   * \class ApplyMaskLabels StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Apply a mask to labels. The labels must be in the main store, while the
   * mask must be in the work store.
   *
   * Labels are kept if they cover at least a voxel for which the mask is
   * greater than the threshold. Otherwise, they are erased. If the inverted
   * flag is set, labels are kept if they cover no voxel for which the mask is
   * greater than the threshold.
   */
  class mgxBase_EXPORT ApplyMaskLabels : public Process
  {
  public:
    ApplyMaskLabels(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Apply Mask to Labels");
      setDesc("Apply mask in work stack to labels in main stack, replacing work");
      setIcon(QIcon(":/images/MaskLabels.png"));

      addParm("Invert","Invert","No",booleanChoice());
      addParm("Threshold","Threshold","1");
    }

    bool run()
    {
      if(!checkState().store(STORE_NON_EMPTY))
        return false;
      Stack* stack = currentStack();
      Store* input = stack->main();
      Store* output = stack->work();
      bool res = run(stack, input, output, parm("Invert").toUInt(), stringToBool(parm("Threshold")));
      if(res) {
        input->hide();
        output->show();
        output->setLabels(true);
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output, bool invert, uint threshold);

  };

  /**
   * \class TrimStackToBezier StackProcessMorphology.hpp <StackProcessMorphology.hpp>
   *
   * Trim a stack by deleting all voxels over a specified distance from a Bezier. 
   * Alternatively, one can use it to create a mask.
   */
  class mgxBase_EXPORT TrimStackToBezier : public Process
  {
  public:
    TrimStackToBezier(const Process& process) : Process(process)
    {
      setName("Stack/Morphology/Trim Stack to Bezier");
      setDesc("Trim a stack by deleting all voxels over a specified distance from a Bezier");
      setIcon(QIcon(":/images/ClipStack.png"));

      addParm("Distance", "Distance from Bezier to keep voxels in um", "20.0");
      addParm("Points", "Points along the Bezier for distance calculation", "50");
      addParm("Mask", "Create a mask instead", "No", booleanChoice());
      addParm("Fill Value", "Voxel value for filling when creating mask", "65535");
      addParm("Axes", "Axes to use to generate test points", "u", QStringList() << "u" << "v" << "uv");
    }

    bool run()
    {
      Stack* stack = currentStack();
      Store* input = stack->main();
      Store* output = stack->work();
      bool res = run(stack, input, output, parm("Distance").toDouble(), parm("Points").toInt(), 
          stringToBool(parm("Mask")), parm("Fill Value").toInt(), parm("Axes"));
      if(res) {
        input->hide();
        output->show();
      }
      return res;
    }
    bool run(Stack* stack, const Store* input, Store* output, double distance, int points, 
                                            bool mask = false, int fillValue = 65535, QString axes = "u");

  };
  ///@}
}

#endif
