From 452fae93e06855e92a522e49caf1354186fdedcb Mon Sep 17 00:00:00 2001
From: even <philippe.even@loria.fr>
Date: Fri, 7 Dec 2018 15:42:52 +0100
Subject: [PATCH] Width control

---
 Article/method.tex                     | 16 ++++++++-----
 Article/notions.tex                    |  7 +++---
 Code/Seg/BSTools/bsdetectionwidget.cpp | 24 +++++++++++++++++++
 Code/Seg/BlurredSegment/bsdetector.h   | 24 ++++++++++++++++++-
 Code/Seg/BlurredSegment/bstracker.cpp  | 31 +++++++++++++++++++++----
 Code/Seg/BlurredSegment/bstracker.h    | 32 +++++++++++++++++++++++++-
 Methode/ctrl.tex                       |  6 +++--
 Methode/methode.tex                    | 12 ++++++----
 8 files changed, 130 insertions(+), 22 deletions(-)

diff --git a/Article/method.tex b/Article/method.tex
index 9a0dde9..c2ce3b9 100755
--- a/Article/method.tex
+++ b/Article/method.tex
@@ -46,15 +46,19 @@ applied to decide of the detection poursuit. In case of positive response,
 the position $C$ and direction $\vec{D}$ of this initial blurred segment
 are extracted.
 
-The fine track step consists on building and extending a blurred segment
+The fine tracking step consists on building and extending a blurred segment
 $\mathcal{B}_2$ based on points that correspond to local maxima of the
 image gradient, ranked by magnitude order, and with gradient direction
 close to a reference gradient direction at the segment first point.
-This step uses an adaptive directional scanner to extends the segment
-in appropriate direction.
-During that step a thinning procedure is run :
-the assigned width is progressively brought to
-the detected blurred segment minimal width.
+This step uses an adaptive directional scanner based on the found
+position $C$ direction $\vec{D}$ in order to extends the segment in the
+appropriate direction.
+After $N$ points are added without any augmentation of the segment minimal
+width, this width becomes the new assigned width so that the segment
+can not thicken any more. This procedure allows to control the blurred
+segment width based on the observation of its evolution in the vicinity
+of the input stroke.
+Setting $N=20$ shows a good behaviour on tested images.
 
 The fine track output segment is finally filtered to remove artifacts
 and outliers, and a solution blurred segment $\mathcal{B}_3$ is provided.
diff --git a/Article/notions.tex b/Article/notions.tex
index 399f61e..ee859f5 100755
--- a/Article/notions.tex
+++ b/Article/notions.tex
@@ -46,10 +46,11 @@ the assigned width $\varepsilon$, then the new input point is rejected.}
   \label{fig:bs}
 \end{figure}
 
-At the beginning, a large width $\varepsilon_{ini}$ is assigned to the
+At the beginning, a large width $\varepsilon$ is assigned to the
 recognition problem to allow the detection of large blurred segments.
-Then, when extending the blurred segment, this assigned width is
-gradually decremented to reach the detected blurred segment minimal width.
+Then, when no more aumentation of the minimal width is observed as the segment
+grows, the assigned width is fixed to the observed minimal width in order to
+avoid the incorporation of spurious outliers in further parts of the segment.
 
 \subsection{Directional scan}
 
diff --git a/Code/Seg/BSTools/bsdetectionwidget.cpp b/Code/Seg/BSTools/bsdetectionwidget.cpp
index 8f0e344..e0de37e 100755
--- a/Code/Seg/BSTools/bsdetectionwidget.cpp
+++ b/Code/Seg/BSTools/bsdetectionwidget.cpp
@@ -614,6 +614,8 @@ cout << "AUTO" << endl;
       {
         // Switches the progressive thinning
         detector.toggleThinning ();
+        if (detector.isThinningActivated () && detector.isThickenningOn ())
+          detector.toggleThickenning ();
         extract ();
         cout << "Thinning "
              << (detector.isThinningActivated () ? "on" : "off") << endl;
@@ -695,6 +697,28 @@ cout << "AUTO" << endl;
       }
       break;
 
+    case Qt::Key_Z :
+      if (event->modifiers () & Qt::ControlModifier)
+      {
+        // Switches the thickenning control
+        detector.toggleThickenning ();
+        if (detector.isThickenningOn () && detector.isThinningActivated ())
+          detector.toggleThinning ();
+        extract ();
+        cout << "Thickenning "
+             << (detector.isThickenningOn () ? "on" : "off") << endl;
+      }
+      else
+      {
+        // Tunes the thickenning limit
+        detector.incThickenningLimit (
+          (event->modifiers () & Qt::ShiftModifier ? -1 : 1));
+        extract ();
+        cout << "Thickenning limit = " << detector.getThickenningLimit ()
+             << " pixels" << endl;
+      }
+      break;
+
     case Qt::Key_1 :
       switchPixelAnalyzer ();
       break;
diff --git a/Code/Seg/BlurredSegment/bsdetector.h b/Code/Seg/BlurredSegment/bsdetector.h
index 95a38bb..c5444c0 100755
--- a/Code/Seg/BlurredSegment/bsdetector.h
+++ b/Code/Seg/BlurredSegment/bsdetector.h
@@ -349,10 +349,32 @@ public:
    */
   void switchOrthoScans ();
 
+  /**
+   * \brief Returns if the thickenning control is activated.
+   */
+  inline bool isThickenningOn () const { return bst2->isThickenningOn (); }
+
+  /**
+   * \brief Toggles the thickenning control.
+   */
+  inline void toggleThickenning () { bst2->toggleThickenning (); }
+
+  /**
+   * \brief Returns the thickenning limit.
+   */
+  inline int getThickenningLimit () const {
+    return bst2->getThickenningLimit (); }
+
+  /**
+   * \brief Increments the thickenning limit.
+   */
+  inline void incThickenningLimit (int val) { bst2->incThickenningLimit (val); }
+
   /**
    * \brief Returns if the thinning is activated.
    */
-  inline bool isThinningActivated () { return bst2->isThinningActivated (); }
+  inline bool isThinningActivated () const {
+    return bst2->isThinningActivated (); }
 
   /**
    * \brief Toggles the thinning strategy.
diff --git a/Code/Seg/BlurredSegment/bstracker.cpp b/Code/Seg/BlurredSegment/bstracker.cpp
index a5931ae..d76a5cd 100755
--- a/Code/Seg/BlurredSegment/bstracker.cpp
+++ b/Code/Seg/BlurredSegment/bstracker.cpp
@@ -12,6 +12,8 @@ const int BSTracker::MIN_SCAN = 8;
 const int BSTracker::DEFAULT_MAX_SCAN = 32;
 const int BSTracker::DEFAULT_FITTING_DELAY = 20;
 
+const int BSTracker::DEFAULT_THICKENNING_LIMIT = 20;
+
 const int BSTracker::DEFAULT_THINNING_DELAY = 20;
 const int BSTracker::DEFAULT_THINNING_SPEED = 2;
 const int BSTracker::DEFAULT_THINNING_REACH = 50;
@@ -37,7 +39,10 @@ BSTracker::BSTracker ()
   recordScans = false;
   orthoScan = false;
 
-  thinningOn = true;
+  thickenningOn = true;
+  thickenningLimit = DEFAULT_THICKENNING_LIMIT;
+
+  thinningOn = false;
   thinningDelay = DEFAULT_THINNING_DELAY;
   thinningSpeed.set (DEFAULT_THINNING_SPEED, 100);
   thinningReach.set (100 + DEFAULT_THINNING_REACH, 100);
@@ -241,6 +246,10 @@ BlurredSegment *BSTracker::fineTrack (const Pt2i &center, const Vr2i &scandir,
 
   BlurredSegmentProto bs (bswidth, pix[cand[0]]);
 
+  // Handles thickenning
+  bool thickenOn = thickenningOn;
+  int stableWidthCount = 0;
+
   // Handles thinning
   int count = 0;
   AbsRat maxw (bswidth * DEFAULT_THINNING_RESOLUTION,
@@ -261,19 +270,27 @@ BlurredSegment *BSTracker::fineTrack (const Pt2i &center, const Vr2i &scandir,
   while (scanningRight || scanningLeft)
   {
     count ++;
+    AbsRat sw = bs.segmentRationalWidth ();
+
+    // Handles thickenning
+    if (thickenOn && stableWidthCount >= thickenningLimit)
+    {
+      bs.setMaxWidth (sw);
+      thickenOn = false;
+    }
 
     // Handles thinning
     if (thon)
     {
       if (count > thinningDelay)
       {
-        AbsRat sw = bs.segmentRationalWidth ();
         AbsRat oldmaxw (maxw);
         maxw.attractsTo (sw, thinningSpeed);
-        sw.mult (thinningReach);
-        if (maxw.lessThan (sw))
+        AbsRat msw (sw);
+        msw.mult (thinningReach);
+        if (maxw.lessThan (msw))
         {
-          maxw.sticksTo (sw);
+          maxw.sticksTo (msw);
           if (oldmaxw.lessThan (maxw)) maxw.set (oldmaxw);
           thon = false;  // thinning deactivation
         }
@@ -322,8 +339,10 @@ BlurredSegment *BSTracker::fineTrack (const Pt2i &center, const Vr2i &scandir,
         nbc = gMap->localMax (cand, pix, normal);
         for (int i = 0; ! added && i < nbc; i++)
           added = bs.addRight (pix[cand[i]]);
+        stableWidthCount ++;
         if (added)
         {
+          if (sw.lessThan (bs.segmentRationalWidth ())) stableWidthCount = 0;
           rscan = count;
           if (rstop == 0) rstart = 0;
           else
@@ -368,8 +387,10 @@ BlurredSegment *BSTracker::fineTrack (const Pt2i &center, const Vr2i &scandir,
         nbc = gMap->localMax (cand, pix, normal);
         for (int i = 0; ! added && i < nbc; i++)
           added = bs.addLeft (pix[cand[i]]);
+        stableWidthCount ++;
         if (added)
         {
+          if (sw.lessThan (bs.segmentRationalWidth ())) stableWidthCount = 0;
           lscan = count;
           if (lstop == 0) lstart = 0;
           else
diff --git a/Code/Seg/BlurredSegment/bstracker.h b/Code/Seg/BlurredSegment/bstracker.h
index 58ce273..d4de005 100755
--- a/Code/Seg/BlurredSegment/bstracker.h
+++ b/Code/Seg/BlurredSegment/bstracker.h
@@ -189,10 +189,31 @@ public:
    */
   inline void setDynamicScans (bool onOff) { dynamicScans = onOff; }
 
+  /**
+   * \brief Returns if the thickenning control is activated.
+   */
+  inline bool isThickenningOn () const { return thickenningOn; }
+
+  /**
+   * \brief Toggles the thickenning control.
+   */
+  inline void toggleThickenning () { thickenningOn = ! thickenningOn; }
+
+  /**
+   * \brief Returns the thickenning limit.
+   */
+  inline int getThickenningLimit () const { return thickenningLimit; }
+
+  /**
+   * \brief Increments the thickenning limit.
+   */
+  inline void incThickenningLimit (int val) {
+    thickenningLimit += val; if (thickenningLimit < 1) thickenningLimit = 1; }
+
   /**
    * \brief Returns if the thinning is activated.
    */
-  inline bool isThinningActivated () { return thinningOn; }
+  inline bool isThinningActivated () const { return thinningOn; }
 
   /**
    * \brief Toggles the thinning strategy.
@@ -238,6 +259,10 @@ private :
   /* Count of points before activating the fitting on the detected segment. */
   static const int DEFAULT_FITTING_DELAY;
 
+  // Width thickenning default parameters.
+  /* Maximal count of points since last minimal width growing. */
+  static const int DEFAULT_THICKENNING_LIMIT;
+
   // Width thinning default parameters.
   /* Count of points before activating the width thinning. */
   static const int DEFAULT_THINNING_DELAY;
@@ -296,6 +321,11 @@ private :
   /** Minimal control width wrt detected segment width when thinning. */
   AbsRat thinningReach;
 
+  /** Segment thickening control modality. */
+  bool thickenningOn;
+  /** Count of expansion without width growing to stop the thickenning. */
+  int thickenningLimit;
+
   /** Gradient map. */
   VMap *gMap;
 
diff --git a/Methode/ctrl.tex b/Methode/ctrl.tex
index eff09ff..b6596d1 100755
--- a/Methode/ctrl.tex
+++ b/Methode/ctrl.tex
@@ -39,7 +39,8 @@ s && Ajuste la longueur tol\'er\'ee pour les sauts de d\'etection \\
 u && Relance la d\'etection sur la derni\`ere s\'election (update) \\
 w && Ajuste la consigne d'\'epaisseur du segment flou pour le suivi fin \\
 x && Ajuste la consigne d'\'epaisseur du segment flou pour le suivi rapide \\
-Ctrl-b && Commute le fond d'\'ecran de la fen\^etre principale. \\
+z && Ajuste le seuil du contr\^ole de la consigne d'\'epaisseur \\
+Ctrl-b && Commute le fond d'\'ecran de la fen\^etre principale \\
 Ctrl-d && Commute le test de densit\'e \\
 Ctrl-e && Commute la prise en compte de la direction du bord (trait / contour) \\
 Ctrl-f && Commute le pr\'e-filtrage (segment initial) \\
@@ -57,7 +58,8 @@ Ctrl-u && Commute l'affichage des bords des segments flous. \\
 Ctrl-v && Commute l'affichage du r\'esultat de la d\'etection en console (verbose) \\
 Ctrl-w && Commute l'ajustement de la consigne d'\'epaisseur sur le segment \\
 Ctrl-x && Commute le recentrage du scan sur le segment d\'etect\'e \\
-Ctrl-y && Commute l'affichage des pixels des segments flous. \\
+Ctrl-y && Commute l'affichage des pixels des segments flous \\
+Ctrl-z && Commute le contr\^ole de la consigne d'\'epaisseur \\
 1 && Commute la visu des segments (pixels) \\
 2 && Commute la visu de l'accumulateur \\
 3 && Commute la visu des profils \\
diff --git a/Methode/methode.tex b/Methode/methode.tex
index 7f4ed6d..9fccb59 100755
--- a/Methode/methode.tex
+++ b/Methode/methode.tex
@@ -189,10 +189,14 @@ un segment flou \`a partir des sommets de gradients, correctement orient\'es
 d\'ecroissantes, trouv\'es dans la barre.
 \item Aucun test de voisinage.
 \item Tol\'erance aux interruptions : 5 pixels.
-\item Amincissement (activ\'e par d\'efaut) : \`a partir de 20 balayages,
-la consigne d'\'epaisseur est r\'eduite de  2\% \`a chaque balayage
-jusqu'\`a atteindre une valeur minimale de 150\% de l'\'epaisseur du segment
-flou construit.
+\item Amincissement progressif (d\'esactiv\'e par d\'efaut) :
+\`a partir de 20 balayages, la consigne d'\'epaisseur est r\'eduite de 2\%
+\`a chaque balayage jusqu'\`a atteindre une valeur minimale de 150\% de
+l'\'epaisseur du segment flou construit.
+\item Contr\^ole de la consigne d'\'epaisseur (activ\'e par d\'efaut) :
+au bout de $N$ ($N=20$ par d\'efaut) ajouts de points sans influence sur la
+largeur minimale du segment flou, la consigne d'\'epaisseur est fix\'ee
+\`a la largeur minimale, de sorte que le segment ne peut plus s'\'epaissir.
 \end{itemize}
 \end{itemize}
 
-- 
GitLab