//
// 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 MESH_PROCESS_SEGMENTATION_HPP
#define MESH_PROCESS_SEGMENTATION_HPP

#include <Process.hpp>

class Ui_LoadHeatMap;

namespace mgx
{
  ///\addtogroup MeshProcess
  ///@{
  /**
   * \class SegmentMesh ProcessSegmentation.hpp <MeshProcessSegmentation.hpp>
   *
   * Segment the mesh using a seeded watershed algorithm.
   *
   * The mesh is assumed to already contain seeds for the watershed. For proper
   * use on this process, you should read the protocols and manuals published.
   */
  class mgxBase_EXPORT SegmentMesh : public Process 
  {
  public:
    SegmentMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Segmentation/Watershed Segmentation");
	  setDesc("Propagate labels on mesh using the watershed algorithm.");
	  setIcon(QIcon(":/images/SegmentMesh.png"));
	  
	  addParm("Steps","Number of propagation steps performed before updating display. Increase value to run faster.","50000");	
	}
  
    bool run()
    {
      if(!checkState().mesh())
        return false;
      return run(currentMesh(), parm("Steps").toUInt(), currentMesh()->activeVertices());
    }
  
    bool run(Mesh* mesh, uint maxsteps, const std::vector<vertex>& to_segment);
  
  };
  
  /**
   * \class SegmentClear ProcessSegmentation.hpp <MeshProcessSegmentation.hpp>
   *
   * Remove any segmentation from the current mesh.
   */
  class mgxBase_EXPORT SegmentClear : public Process 
  {
  public:
    SegmentClear(const Process& process) : Process(process) 
    {
	  setName("Mesh/Segmentation/Segmentation Clear");
	  setDesc("Clean all mesh labels.");
	  setIcon(QIcon(":/images/SegmentClear.png"));
	}
  
    bool run()
    {
      if(!checkState().mesh())
        return false;
      return run(currentMesh());
    }
  
    bool run(Mesh* mesh);

  };
  
  /**
   * \class MeshRelabel ProcessSegmentation.hpp <MeshProcessSegmentation.hpp>
   *
   * Relabel a segmented mesh.
   *
   * This process will relabel all the mesh. The labels are assigned in an
   * arbitrary, non-random, manner.
   */
  class mgxBase_EXPORT MeshRelabel : public Process 
  {
  public:
    MeshRelabel(const Process& process) : Process(process) 
    {
	  setName("Mesh/Segmentation/Relabel");
	  setDesc("Relabel a mesh randomly to use consecutive labels.");
	  setIcon(QIcon(":/images/Relabel.png"));
	  
	  addParm("Label Start","Starting label.","1");
	  addParm("Label Step","Increment step.","1");
	
		}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_LABEL))
        return false;
      Mesh* m = currentMesh();
      int start = parm("Label Start").toInt();
      int step = parm("Label Step").toInt();
      return run(m, start, step);
    }
  
    bool run(Mesh* m, int start, int step);

  };
  
  /**
   * \class GrabLabelsSegment ProcessSegmentation.hpp <MeshProcessSegmentation.hpp>
   *
   * Grab labels from other mesh (3D meshes).
   */
  class mgxBase_EXPORT GrabLabelsSegment : public Process 
  {
  public:
    GrabLabelsSegment(const Process& process) : Process(process) 
    {
	  setName("Mesh/Segmentation/3D Grab Labels");
	  setDesc("Grab labels from other mesh (3D meshes).");
	  setIcon(QIcon(":/images/GrabLabels.png"));
	  
	  addParm("Tolerance (µm)","Maximal distance between matching cells. ","5.0");	// 0	
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY, 0).mesh(MESH_NON_EMPTY, 1))
        return false;
  
      Mesh* mesh1, *mesh2;
      if(currentMesh() == mesh(0)) {
        mesh1 = mesh(0);
        mesh2 = mesh(1);
      } else if(currentMesh() == mesh(1)) {
        mesh2 = mesh(0);
        mesh1 = mesh(1);
      } else
        return false;
  
      bool res = run(mesh1, mesh2, parm("Tolerance (µm)").toFloat());
      return res;
    }
  
    bool run(Mesh* mesh1, Mesh* mesh2, float tolerance);

  };
  
  /**
   * \class LabelSelected ProcessSegmentation.hpp <MeshProcessSegmentation.hpp>
   *
   * Change the label of the selected vertices.
   */
  class mgxBase_EXPORT LabelSelected : public Process 
  {
  public:
    LabelSelected(const Process& process) : Process(process) 
    {
	  setName("Mesh/Segmentation/Label Selected Vertices");
	  setDesc("Label selected vertices with the same value, which can be a new label");
	  setIcon(QIcon(":/images/LabelSelected.png"));
	  
	  addParm("Label","Asign this label to all selected vertices.","0");	// 0	
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_LABEL))
        return false;
      return run(currentMesh(), parm("Label").toInt());
    }
  
    bool run(Mesh* mesh, int label);
  
  };
  
  /**
   * \class RelabelCells3D ProcessSegmentation.hpp <MeshProcessSegmentation.hpp>
   *
   * Relabel 3D cells. Each connected region will be given a unique label.
   */
  class mgxBase_EXPORT RelabelCells3D : public Process 
  {
  public:
    RelabelCells3D(const Process& process) : Process(process) 
    {
	  setName("Mesh/Segmentation/Relabel 3D Cells");
	  setDesc("Relabel 3D cells (connected regions). A start label of -1 is used to indicate continuing using current labels.");
	  setIcon(QIcon(":/images/CellFiles3D.png"));
	  
	  addParm("Label start","Starting label. Use -1 to continue from last current label.","-1");
	  addParm("Label step","Increment step.","1");
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      Mesh* mesh = currentMesh();
      bool res = run(mesh, parm("Label start").toInt(), parm("Label step").toInt());
      if(res)
        mesh->setShowLabel("Label");
      return res;
    }
  
    bool run(Mesh* mesh, int label_start, int label_step);

  };
  
  /**
   * \class MeshCombineRegions ProcessSegmentation.hpp <MeshProcessSegmentation.hpp>
   *
   * Combine over-segmented regions, based on mesh signal.
   */
  class mgxBase_EXPORT MeshCombineRegions : public Process
  {
  public:
    MeshCombineRegions(const Process& process) : Process(process) 
    {
	  setName("Mesh/Segmentation/Combine Labels");
	  setDesc("Combine over-segmented regions, based on mesh signal.");
	  setIcon(QIcon(":/images/Merge.png"));
	  
	  addParm("Border Distance (µm)","Half of the cell border width on the mesh.","1");	// 0
	  addParm("Threshold","Ratio of border signal over internal signal. If a border between 2 cells has a ratio below the threshold, the cells are fused.","1.5");	// 1	
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("Border Distance (µm)").toFloat(), parm("Threshold").toFloat());
    }
    
    bool run(Mesh* mesh, float borderDist, float Difference);

  };
  
  /**
   * \class MeshAutoSegment ProcessSegmentation.hpp <MeshProcessSegmentation.hpp>
   *
   * Auto-Segmentation of the mesh surface based on signal. Combines blurring, auto-seeding,
   * label propagation by watershed and fusion of over-segmented cells.
   */
  class mgxBase_EXPORT MeshAutoSegment : public Process
  {
  public:
    MeshAutoSegment(const Process& process) : Process(process) 
    {
	  setName("Mesh/Segmentation/Auto Segmentation");
	  setDesc("Auto-Segmentation of the mesh surface based on signal. Combines blurring, auto-seeding, label propagation by watershed and fusion of over-segmented cells.");
	  setIcon(QIcon(":/images/SegmentMesh.png"));
	  
	  addParm("Update","Option to update mesh drawing while running processes.","Yes",booleanChoice());
	  addParm("Normalize","Option to normalize mesh signal before merging the cells. Most useful in case of low contrast mesh signal.","Yes",booleanChoice());
	  addParm("Blur Cell Radius (µm)","Radius used for gaussian blur of mesh signal. In normal cases, should be equal to auto-seed radius.","2.0");
	  addParm("Auto-Seed Radius (µm)","Radius used for auto-seeding the mesh, based on local minima of signal. Should be equal to radius of smallest cells.","2.0");
	  addParm("Blur Borders Radius (µm)","Radius used for gaussian blur of signal on cell borders before watershed segmentation. Use small values (roughly, half the border width) to avoid border signal distortion.","1");
	  addParm("Normalization Radius (µm)","Radius used for local normalization of signal. Roughly, radius of largest cells.","5.0");
	  addParm("Border Distance (µm)","Half of the cell border width after blurring. Used for fusion of over-segmented cells.","1.0");
	  addParm("Combine Threshold","Ratio of border signal over internal signal. If a borders between 2 cells have a ratio below the threshold, the cells are fused.","1.8");
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), stringToBool(parm("Update")), stringToBool(parm("Normalize")), 
        parm("Blur Cell Radius (µm)").toFloat(), parm("Auto-Seed Radius (µm)").toFloat(), parm("Blur Borders Radius (µm)").toFloat(), parm("Normalization Radius (µm)").toFloat(), 
        parm("Border Distance (µm)").toFloat(), parm("Combine Threshold").toFloat());
    }
  
    bool run(Mesh* mesh, bool updateView, bool normalize, float gaussianRadiusCell, 
      float localMinimaRadius, float gaussianRadiusWall, float normalizeRadius, float borderDist, 
      float threshold);

  };
  ///@}
}

#endif
